how i started to love design patterns
TRANSCRIPT
How I started to love what they call
Design Patterns@samuelroze
Samuel Roze
Software Enginner @ InviqaFounder @ ContinuousPipe.io
4 twitter.com/samuelroze
4 github.com/sroze
4 sroze.io
I started years ago...
I probably wrote that<?php
include 'header.php';include 'pages/'. $_GET['page'].'.php';include 'footer.php';
At some point I used Symfonyclass EntityController extends Controller{ public function myAction($identifier) { $entity = $this->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository(Entity::class) ->find($identifier);
return $this->render('my-template.html.twig', [ 'entity' => $entity, ]); }}
Then I updated my entity...public function myAction(Request $request, $identifier){ // ... if ($request->isMethod('POST')) { $form->handleRequest($request);
if ($form->isValid()) { $entity = $form->getData();
$doctrine->persist($entity); $doctrine->flush($entity); } } // ...}
Then I even sent an email// ...
if ($form->isValid()) { $entity = $form->getData();
$doctrine->persist($entity); $doctrine->flush($entity);
$mailer = $this->getContainer()->get('mailer'); $mailer->send( // ... );}
// ...
And then...It became unmaintanable
Design Patterns
A general reusable solution to a commonly occurring problem within a given
context.1
Wikipedia
All the design patterns do the same thing: control the
information flow.1
Anthony Ferrara
Let's do some refactoringclass EntityController extends Controller{ public function myAction(Request $request, $identifier) { $entity = $this->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository(Entity::class) ->find($identifier);
$form = $this->createForm(EntityFormType::class, $entity); $form->handleRequest($request);
if ($form->isValid()) { $entity = $form->getData();
$doctrine->persist($entity); $doctrine->flush($entity);
$mailer = $this->getContainer()->get('mailer'); $mailer->send( \Swift_Message::newInstance() ->setBody('Hey, something was updated!') ); }
return $this->render('my-template.html.twig', [ 'entity' => $entity, ]); }}
Why do we refactor?4 We want to reuse code
Why do we refactor?4 We want to reuse code
4 So we delegate the responsibilities
Why do we refactor?4 We want to reuse code
4 So we delegate the responsibilities
4 And improve the readability
Why do we refactor?4 We want to reuse code
4 So we delegate the responsibilities
4 And improve the readability
4 And therefore we ensure maintainability
Why do we refactor?4 We want to reuse code
4 So we delegate the responsibilities
4 And improve the readability
4 And therefore we ensure maintainability
4 By doing so we enable change
Adapter
Adapterinterface EntityRepository{ public function find($identifier) : Entity;
public function save(Entity $entity) : Entity;}
Adapterfinal class DoctrineEntityRepository implements EntityRepository{ private $entityManager;
public function __construct(EntityManager $entityManager) { $this->entityManager = $entityManager; }
public function find($identifier) : Entity { if (null === ($entity = $this->getDoctrineRepository()->find($identifier))) { throw new EntityNotFound(); }
return $entity; }
// ...}
Adapterfinal class DoctrineEntityRepository implements EntityRepository{ // ...
public function save(Entity $entity) : Entity { $entity = $this->entityManager->merge($entity);
$this->entityManager->persist($entity); $this->entityManager->flush($entity);
return $entity; }}
class EntityController extends Controller{ public function myAction(Request $request, $identifier) { $entity = $this->getEntityRepository()->find($identifier);
// ...
if ($form->isValid()) { $this->getEntityRepository()->save($entity);
$mailer = $this->getContainer()->get('mailer'); $mailer->send( \Swift_Message::newInstance() ->setBody('Hey, something was updated!') ); } }}
/** @Route(service="controller.entity") **/class EntityController{ public function __construct(EntityRepository $entityRepository, /** ... **/) { /** ... **/ }
public function myAction(Request $request, $identifier) { $entity = $this->entityRepository->find($identifier);
// ...
if ($form->isValid()) { $this->entityRepository->save($entity);
$this->mailer->send( \Swift_Message::newInstance() ->setBody('Hey, something was updated!') ); } }}
The XML bit<service id="entity.repository.doctrine" class="App\Infrastructure\DoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /></service>
The XML bit<service id="entity.repository.doctrine" class="App\Infrastructure\DoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /></service>
<service id="entity.repository" alias="entity.repository.doctrine" />
The XML bit<service id="entity.repository.doctrine" class="App\Infrastructure\DoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /></service><service id="entity.repository" alias="entity.repository.doctrine" />
<service id="controller.entity" class="AppBundle\Controller\EntityController">
<argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="mailer" /></service>
Store the entity in-memory?final class InMemoryRepositoryEntity implements EntityRepository{ private $entities = [];
public function find($identifier) : Entity { if (!array_key_exists($identifier, $this->entities)) { throw new EntityNotFound(); }
return $this->entities[$identifier]; }
public function save(Entity $entity) : Entity { $this->entities[$entity->getIdentifier()] = $entity;
return $entity; }}
Event Dispatcher
Dispatching the eventclass MyController{ public function myAction(Request $request, $identifier) { // ... if ($form->isValid()) { $this->entityRepository->save($entity); $this->eventDispatcher->dispatch( EntitySaved::NAME, new EntitySaved($entity) ); } // ... }}
An eventclass EntitySaved extends Event{ const NAME = 'entity.saved';
private $entity;
public function __construct(Entity $entity) { $this->entity = $entity; }
public function getEntity() : Entity { return $this->entity; }}
A listenerfinal class SendAnEmailWhenEntityIsSaved{ public function __construct(MailerInterface $mailer) { /** ... **/ }
public function onEntitySaved(EntitySaved $event) { $this->mailer->send(/** something **/); }}
Because we want some XML<service id="controller.entity" class="AppBundle\Controller\EntityController">
<argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="event_dispatcher" /></service>
-
Because we want some XML<service id="controller.entity" class="AppBundle\Controller\EntityController">
<argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="event_dispatcher" /></service>
<service id="entity.listener.send_mail_when_saved" class="App\Entity\Listener\SendAnEmailWhenEntityIsSaved"> <argument type="service" id="mailer" />
<tag name="kernel.event_listener" event="entity.saved" /></service>
What is the point?4 We can create another listener easily
4 We just have to dispatch this event
Decorator
final class DispatchAnEventWhenEntityIsSaved implements EntityRepository{ public function __construct( EntityRepository $decoratedRepository, EventDispatcherInterface $eventDispatcher ) { /** ... **/ }
public function find($identifier) : Entity { return $this->decoratedRepository($identifier); }
public function save(Entity $entity) : Entity { $saved = $this->decoratedRepository->save($entity);
$this->eventDispatcher->dispatch( EntitySaved::NAME, new EntitySaved($saved) );
return $saved; }}
Decorates the repository<service id="entity.repository.dispatch_an_event_when_saved" class="App\Entity\Repository\DispatchAnEventWhenEntityIsSaved" decorates="entity.repository">
<argument type="service" id="entity.repository.dispatch_an_event_when_saved.inner" />
<argument type="service" id="event_dispatcher" /></service>
We just have to saveclass MyController{ public function myAction(Request $request, $identifier) { // ... if ($form->isValid()) { $this->entityRepository->save($entity); } // ... }}
Decorates all the things!
What do we have?4 Flexible entity storage
4 Events dispatched regarding the storage
4 Optional actions on events
4 Easily testable code!
Good practices, design patterns, they just help us
to enable change
How to apply that...4 Closes the door!
4 Uses final keyword
4 private properties
4 Create extension points when required
4 Prevent in case of
Maintainability4 Distinct features in their own namespaces
4 Interfaces's names should reflect their responsibilities
4 Implementations' names should reflect their distinction
Thank you!@samuelroze
continuouspipe.io
https://joind.in/talk/187a4