Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
The Clean Architecture in PHP
Search
Kristopher Wilson
January 30, 2014
Technology
4.4k
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
150
Doctrine ORM
mrkrstphr
0
250
What's new in PHP OOP?
mrkrstphr
1
330
Other Decks in Technology
See All in Technology
新しい SLO が良い感じにハマっている話
z63d
5
2.2k
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
240
『三匹の子ぶた』から学ぶネットワークセキュリティの昔と今 / Network Security: Then and Now Through the Lens of The Three Little Pigs
nttcom
1
1.8k
ラジオの科学
frievea
0
280
toio・myCobotでフィジカルAIっぽいことを行うための検討(とりあえず調査) / フィジカルAI LT(IoTLTによる開催)
you
PRO
0
300
ブラウザ研修 2026
recruitengineers
PRO
5
800
DatadogのBits Chatが開発組織にもたらしたもの / What Bits Chat Has Brought Us
sms_tech
1
130
Redmine 7.0 新機能・機能強化解説(OSC2026京都ダイジェスト版)
vividtone
1
210
今こそ聞きたいソフトウェア設計 ドメイン駆動設計再入門
masuda220
PRO
17
6.9k
Bill One 開発エンジニア 紹介資料
sansan33
PRO
7
19k
サイバー捜査員研修(後半)
nomizone
1
810
【CEDEC2026】『ウマ娘 プリティーダービー』 英語版のキャラクターの方言や口調をローカライズするための創造的アプローチ
cygames
PRO
1
200
Featured
See All Featured
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
66
57k
Visualization
eitanlees
152
17k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
460
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
330
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
590
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Done Done
chrislema
186
16k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
Claude Code のすすめ
schroneko
67
230k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
Product Roadmaps are Hard
iamctodd
55
12k
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