Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
The Clean Architecture in PHP
Search
Kristopher Wilson
January 30, 2014
Technology
4.5k
23
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
The Clean Architecture in PHP
Kristopher Wilson
January 30, 2014
More Decks by Kristopher Wilson
See All by Kristopher Wilson
HHVM Makes Everything Stupid Fast
mrkrstphr
0
160
Doctrine ORM
mrkrstphr
0
270
What's new in PHP OOP?
mrkrstphr
1
340
Other Decks in Technology
See All in Technology
使いこなすために知っておきたい Azure SRE Agent アンチパターン
torumakabe
2
430
OpenSharing について熱く語る〜AI アセットの共有について〜
kameitomohiro
0
240
[AWS 秋のクラウドオペレーション祭り 2026]AWS DevOps Agentで変わるリリースと運用対応 ~リリース管理機能と Directed actions のご紹介~
furuton
4
1.1k
味覇で作るパスタが美味い - 20261001_yabaubuki.dev
livesense
PRO
0
110
go-sqletch
moznion
0
120
プロダクト価値を、 チームが使える判断軸に変える
vivion
0
140
予測不能な実行エージェントを安全にサクッと試せるサンドボックス環境の話
sansantech
PRO
0
130
AI時代に必要な脅威モデリング― 変化の速度に負けない理解とリスク判断
masakane55
5
1.2k
AIは爆速なのに、私が詰まっていた話 ― 音声入力と鳴くマスコットでボトルネックを削る
yama3133
0
560
12 個 Tool と、 4,096 Token
hlum
0
110
事業活動を AI Ready にする攻めと守りのデータエンジニアリング / data-engineering-for-ai-ready-business
pei0804
6
2.3k
ファミコンでPHPを動かす / PHP on the Famicom side b
tomzoh
0
140
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
55
10k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
400
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.8k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
2
400
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
What the history of the web can teach us about the future of AI
inesmontani
PRO
2
730
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
Balancing Empowerment & Direction
lara
6
1.3k
Design in an AI World
tapps
1
350
Prompt Engineering for Job Search
mfonobong
0
490
Transcript
The Clean Architecture
WE GOTS PROBLEMS
WE LIVE OR DIE BY THE FRAMEWORK
SO MANY COOL LIBRARIES
WE CAN'T TEST ANYTHING
CHANGE BREAKS EVERYTHING
HOW DO WE FIX THESE PROBLEMS?
THE CLEAN ARCHITECTURE Coined by Mr. Uncle Bob
THE ONION ARCHITECTURE Coined by Jeffrey Palermo
SOFTWARE IS COMPOSED OF LAYERS
MVC is a start of those layers: Model View Controller
MVC Kinda Sucks Rails pushed the "fat model, skinny controller"
mantra
MVC Kinda Sucks With only 3 layers, this becomes the
"obese model, skinny controller"
The Solution? More layers, obviously...
DOMAIN MODEL
Domain Model is Our Entities
Domain Model is Plain Old PHP Objects
Domain Model has No dependencies except for PHP
Domain Model is Transferable We should be able to drop
the Domain Model into any PHP project and have it work as intended.
DOMAIN SERVICES
Domain Services is dependent upon the Domain Model and nothing
else.
Domain Services is Plain Old PHP Objects
Domain Services use the Domain Model layer to do things.
Domain Services together, with Domain Services, create the Business Logic
Layer.
Business Logic the rules that define what the application does
and does not
Business Logic is relationships, processes, and data workflow
Business Logic is not processing forms, routing requests or creating
csv files
Business Logic is the stuff that stays the same regardless
of language, framework or interface
Application Services are services provided by your framework.
Application Services is session management, pagination, and authentication
Application Services utilize your Business Logic Layer in the context
of your application (web based vs desktop)
Application Services depends upon the Domain Model and Domain Services
to function
User Interface is stuff the user sees
User Interface is the HTML, JavaScript, CSS, Images, etc that
make up the user experience
User Interface is also the controller that serves the request.
User Interface M VC Data UI
Infrastructure are the mechanisms that give your Domain Model meaning
Infrastructure is responsible for hydrating and persisting the Data Model
Infrastructure connects with external resources to hydrate and persist the
data
Infrastructure vs UI are different layers, but on the same
level of the Onion
Infrastructure vs UI UI (Controllers) CANNOT talk to infrastructure
Infrastructure vs UI Controllers utilizing Infrastructure directly is like supergluing
ourselves to an implementation
External Data Sources old school thought: Databases are central to
an application. Apps revolve around the DB.
External Data Sources old school thought: Databases are central to
an application. Apps revolve around the DB.
External Data Sources Databases simply provide a means of hydrating
your Domain Model
External Data Sources Your Domain Model is central to your
application. It is the core of the Onion.
External Data Sources Our application does not depend on the
database.
External Data Sources Our application depends on data.
External Data Sources Where does that data come from? We
don't care!
External Data Sources API Files Relational Databases NoSQL Variants
External Data Sources Whatever.
So how does this work?
Dependency moves inward
Inversion of Control Inversion of control is taking the control
of dependencies away from the object, and giving it to some third party who provides an object with its dependencies.
Inversion of Control Some things can't know about other things,
but we can use interfaces to inject other things into some things. LOL.
Dependency Injection Simply inject a dependency into an object that
needs it, either view construction or using set methods.
Dependency Injection Previously... class CustomerController extends AbstractController { public function
indexAction() { $repository = new CustomerRepository(new EntityManager()); $customers = $repository->getAll(); return ['customers' => $customers]; } } Controllers shouldn't know what repositories are...
Dependency Injection Instead... class CustomerController extends AbstractController { protected $customersRepository;
public function __construct(CustomerRepositoryInterface $repo) { $this->customerRepository = $repo; } public function indexAction() { $customers = $repository->getAll(); return ['customers' => $customers]; } }
Dependency Injection A class simply asks for some object conforming
to the dependent interface. We don't care what we get, as long as it does what we want.
Domain Model = Entities class Customer extends AbstractEntity { protected
$name; protected $channel; public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } }
Domain Services ▪ Repositories ▪ Factories ▪ Services
Repositories ▪ Retrieve things from data sources ▪ Store things
in data sources
Repositories But Domain Services are too deep in the Onion
to know about data sources...
Repositories Arm yourself with interfaces!
Repositories Domain Services should provide a contract for the Infrastructure
Layer to adhere
Repositories interface CustomerRepositoryInterface extends RepositoryInterface { /** * @param int
$id * @return AbstractEntity */ public function getById($id); /** * @return array */ public function getAll(); }
Repositories Use Dependency Injection for components that cannot depend on
the Infrastructure Layer directly
Factories Factories create things. They know the business rules around
construction and adhere to them.
Factories As part of the Domain Services layer, they can
only depend on other Domain Services and the Domain Model
Factories class CustomerFactory { // ... public function create() {
$customer = new Customer(); $customer->setType($this->typeRepository->getByCode('R')); $customer->setCreditLimit(0); $customer->setCreditStatus($this->statusRepository->getByCode('P')); return $customer; } }
Factories Domain Services are your Business Logic Layer. They know
how to create things. Your controller doesn't know, but it does know data, and it does know the factory's phone number.
Services Services are classes that do things for you or
figure things out for you. They know about your data, and they know about your business rules.
Services Services are things like billing runs, usage statistic calculators,
permission checks, etc.
Services Works with data, but doesn't create (Factory), retrieve or
persist data (Repository).
Services namespace Uss\Domain\Services\Billing; class Billing { public function generateInvoices(\DateTime $invoiceDate)
{ $orders = $this->ordersRepository->getActiveBillingOrders($invoiceDate); foreach ($orders as $order) { $invoice = $this->invoiceFactory->create($order); $this->invoiceRepository->persist($invoice); } $this->invoiceRepository->flush(); } }
Services namespace Uss\Domain\Services\Billing; class Taxes { public function calculateTaxes(Invoice $invoice)
{ if ($this->isAccountTaxExempt($invoice->getAccount()) { return; } foreach ($invoice->getOrder()->getComponents() as $component) { $invoice->setTaxes( $this->taxRepository->getBy( [ 'type' => $component->getServiceType(), 'geocode' => $component->getLocation()->getGeocode() ] ); ); } } }
UI: Controllers ▪ Accept Data (GET, POST, PUT) ▪ Validate
Data ▪ Use the Domain Services Layer to do things ▪ Return a view, or redirect to another controller
UI: Controllers Do Not ▪ Contain business rules ▪ Access
the database ▪ Do things...
UI: Controllers Should ▪ Have a specific purpose ▪ Not
have many actions ▪ Have very short actions (methods)
UI: Controllers Should SOLID S = Single Responsibility Principle Every
class should have one, and only one, responsibility
UI: Controllers A PartnerController should only be concerned with managing
a Partner (View, Edit, Delete)
UI: Controllers If Partners have Contacts, Documents, and Deals, those
should all each be their own controllers. Single responsibility.
Small Controllers. Why?
class CustomersController extends AbstractActionController { public function __construct( AddressRepositoryInterface $addressRepository,
AgingHistoryRepositoryInterface $agingHistoryRepository, AliasRepositoryInterface $aliasRepository, SocialRepositoryInterface $socialRepository, ContactRepositoryInterface $contactRepository, CustomerRepositoryInterface $customerRepository, StatusRepositoryInterface $statusRepository, MaintSurvTicketRepositoryInterface $ticketRepository ) { $this->addressRepository = $addressRepository; $this->agingHistoryRepository = $agingHistoryRepository; $this->aliasRepository = $aliasRepository; $this->socialRepository = $socialRepository; $this->contactRepository = $contactRepository; $this->customerRepository = $customerRepository; $this->statusRepository = $statusRepository; $this->ticketRepository = $ticketRepository; } }
Have fun testing that.
Have fun refactoring that.
UI: Controllers Controllers are stupid. They should contain no logic
besides retrieving data and knowing which Domain Services to call.
UI: Controllers All business logic should go within the Domain
Services layer. All processing logic should go in the controller.
Dumb Controllers. Why? DRY. If business logic is in the
controller, we can't reuse it. Single Responsibility. A controller is only responsible for responding to requests and dispatching views.
Why are we doing all of this?
FRAMEWORK INDEPENDENCE
DATABASE INDEPENDENCE
EXTERNAL AGENCY INDEPENDENCE
USER INTERFACE INDEPENDENCE
TESTABLE