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
仕様駆動開発、導入半年。「本当に速くなってるの?」にデータで答える / AICon2026_hirakawa
rakus_dev
0
150
Genie Ontologyは銀の弾丸かを考える / Is Genie Ontology a Silver Bullet?
nttcom
0
420
誤解だらけの開発生産性 / Myths and Misconceptions about Developer Productivity
i35_267
2
830
Network Firewallやっていき!
news_it_enj
0
170
AI時代のPlaywright活用(システムテストを自動化する ー 実行エンジンにPla ywrightを選んだ理由)
ynisqa1988
1
680
ruby.wasmとPicoRuby.wasmに対応した仮想DOMライブラリを作ってる話 #kaigieffect_kaigi
sue445
PRO
0
150
CIで使うClaude
iwatatomoya
0
290
Multicaで30個のミニプロジェクトをAIエージェント運用して見えてきたこと
eiei114
0
270
AIレビューはどこまで任せられるのか?自動化と人が背負うレビューの境界
sansantech
PRO
3
1.1k
個人開発で育てる「大規模設計の苗床」 - AI時代の1人開発から始める業務への知識接続 / The Seedbed for Large-Scale Design - From AI-Era Solo Projects to Professional Knowledge
bitkey
PRO
1
280
穢れた技術選定について
watany
18
5.7k
41歳でAWSが好きすぎてITエンジニアになったおっさんの話
yama3133
0
140
Featured
See All Featured
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
260
Done Done
chrislema
186
16k
Producing Creativity
orderedlist
PRO
348
40k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
220
Exploring anti-patterns in Rails
aemeredith
3
440
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
420
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
350
Agile that works and the tools we love
rasmusluckow
331
22k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
450
Design in an AI World
tapps
1
260
Chasing Engaging Ingredients in Design
codingconduct
0
240
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