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
160
Doctrine ORM
mrkrstphr
0
260
What's new in PHP OOP?
mrkrstphr
1
340
Other Decks in Technology
See All in Technology
暗号化?某ファイルストレージはどうなるの!? 3rd Partyとうまく付き合う秘密度ラベル設計
kasada
0
210
Data Hubグループ 紹介資料
sansan33
PRO
0
3.2k
現場の暗黙知を継承するAIエージェント — 対話から生まれる長期記憶と Skills
atsukish
0
130
Digital Credentials API × OpenID4VP ブラウザ完結型本人確認の実装知見(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
340
internal/testlog で遊ぼう
rokuosan
0
350
Oracle AI Databaseデータベース・サービス: BaseDB/ExaDB-Dの可用性
oracle4engineer
PRO
1
960
「面白い!」を信じ抜け。激動の時代を貫く、オンリーワン・エンジニアの条件
kizawa2020
2
410
Microsoft MVP プログラムを紹介するから目指す人増えてくれ
tsubakimoto_s
0
140
dbt と Snowflake と tag
kevinrobot34
2
290
[RSJ26] Flow as Flow: Modeling Robot Velocity Fields as Probability Velocity Fields
keio_smilab
PRO
0
120
Contract One Engineering Unit 紹介資料
sansan33
PRO
0
20k
atproto spaces概要
yamarten
0
110
Featured
See All Featured
Technical Leadership for Architectural Decision Making
baasie
3
540
Unsuck your backbone
ammeep
672
58k
[SF Ruby Conf 2025] Rails X
palkan
2
1.3k
Six Lessons from altMBA
skipperchong
29
4.5k
Speed Design
sergeychernyshev
33
2k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.5k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Discover your Explorer Soul
emna__ayadi
2
1.3k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
360
Testing 201, or: Great Expectations
jmmastey
46
8.2k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
480
The Pragmatic Product Professional
lauravandoore
37
7.4k
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