Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Clean Architecture in PHP

The Clean Architecture in PHP

Kristopher Wilson

January 30, 2014
Tweet

More Decks by Kristopher Wilson

Other Decks in Technology

Transcript

  1. MVC Kinda Sucks With only 3 layers, this becomes the

    "obese model, skinny controller"
  2. Domain Model is Transferable We should be able to drop

    the Domain Model into any PHP project and have it work as intended.
  3. External Data Sources old school thought: Databases are central to

    an application. Apps revolve around the DB.
  4. External Data Sources old school thought: Databases are central to

    an application. Apps revolve around the DB.
  5. External Data Sources Your Domain Model is central to your

    application. It is the core of the Onion.
  6. 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.
  7. Inversion of Control Some things can't know about other things,

    but we can use interfaces to inject other things into some things. LOL.
  8. Dependency Injection Simply inject a dependency into an object that

    needs it, either view construction or using set methods.
  9. 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...
  10. 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]; } }
  11. 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.
  12. 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; } }
  13. Repositories interface CustomerRepositoryInterface extends RepositoryInterface { /** * @param int

    $id * @return AbstractEntity */ public function getById($id); /** * @return array */ public function getAll(); }
  14. Factories As part of the Domain Services layer, they can

    only depend on other Domain Services and the Domain Model
  15. 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; } }
  16. 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.
  17. 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.
  18. 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(); } }
  19. 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() ] ); ); } } }
  20. 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
  21. UI: Controllers Should ▪ Have a specific purpose ▪ Not

    have many actions ▪ Have very short actions (methods)
  22. UI: Controllers Should SOLID S = Single Responsibility Principle Every

    class should have one, and only one, responsibility
  23. UI: Controllers If Partners have Contacts, Documents, and Deals, those

    should all each be their own controllers. Single responsibility.
  24. 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; } }
  25. UI: Controllers Controllers are stupid. They should contain no logic

    besides retrieving data and knowing which Domain Services to call.
  26. UI: Controllers All business logic should go within the Domain

    Services layer. All processing logic should go in the controller.
  27. 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.