How I started to love what
they call
Design Patterns
@samuelroze
Samuel Roze
Software Enginner @ Inviqa
Founder @ 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 Symfony
class 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 refactoring
class 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
Adapter
interface EntityRepository
{
public function find($identifier) : Entity;
public function save(Entity $entity) : Entity;
}
Adapter
final 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;
}
// ...
}
Adapter
final 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="AppInfrastructureDoctrineEntityRepository">
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
The XML bit
<service id="entity.repository.doctrine"
class="AppInfrastructureDoctrineEntityRepository">
<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="AppInfrastructureDoctrineEntityRepository">
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
<service id="entity.repository" alias="entity.repository.doctrine" />
<service id="controller.entity"
class="AppBundleControllerEntityController">
<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 event
class MyController
{
public function myAction(Request $request, $identifier)
{
// ...
if ($form->isValid()) {
$this->entityRepository->save($entity);
$this->eventDispatcher->dispatch(
EntitySaved::NAME,
new EntitySaved($entity)
);
}
// ...
}
}
An event
class 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 listener
final 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="AppBundleControllerEntityController">
<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="AppBundleControllerEntityController">
<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="AppEntityListenerSendAnEmailWhenEntityIsSaved">
<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="AppEntityRepositoryDispatchAnEventWhenEntityIsSaved"
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 save
class 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
Maintainability
4 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

How I started to love design patterns

  • 1.
    How I startedto love what they call Design Patterns @samuelroze
  • 2.
    Samuel Roze Software Enginner@ Inviqa Founder @ ContinuousPipe.io 4 twitter.com/samuelroze 4 github.com/sroze 4 sroze.io
  • 3.
  • 4.
    I probably wrotethat <?php include 'header.php'; include 'pages/'. $_GET['page'].'.php'; include 'footer.php';
  • 5.
    At some pointI used Symfony class 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, ]); } }
  • 6.
    Then I updatedmy 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); } } // ... }
  • 7.
    Then I evensent an email // ... if ($form->isValid()) { $entity = $form->getData(); $doctrine->persist($entity); $doctrine->flush($entity); $mailer = $this->getContainer()->get('mailer'); $mailer->send( // ... ); } // ...
  • 8.
    And then... It becameunmaintanable
  • 9.
  • 10.
    A general reusablesolution to a commonly occurring problem within a given context. 1 Wikipedia
  • 12.
    All the designpatterns do the same thing: control the information flow. 1 Anthony Ferrara
  • 13.
    Let's do somerefactoring class 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, ]); } }
  • 14.
    Why do werefactor? 4 We want to reuse code
  • 15.
    Why do werefactor? 4 We want to reuse code 4 So we delegate the responsibilities
  • 16.
    Why do werefactor? 4 We want to reuse code 4 So we delegate the responsibilities 4 And improve the readability
  • 17.
    Why do werefactor? 4 We want to reuse code 4 So we delegate the responsibilities 4 And improve the readability 4 And therefore we ensure maintainability
  • 18.
    Why do werefactor? 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
  • 19.
  • 21.
    Adapter interface EntityRepository { public functionfind($identifier) : Entity; public function save(Entity $entity) : Entity; }
  • 22.
    Adapter final class DoctrineEntityRepositoryimplements 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; } // ... }
  • 23.
    Adapter final class DoctrineEntityRepositoryimplements EntityRepository { // ... public function save(Entity $entity) : Entity { $entity = $this->entityManager->merge($entity); $this->entityManager->persist($entity); $this->entityManager->flush($entity); return $entity; } }
  • 24.
    class EntityController extendsController { 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!') ); } } }
  • 25.
    /** @Route(service="controller.entity") **/ classEntityController { 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!') ); } } }
  • 26.
    The XML bit <serviceid="entity.repository.doctrine" class="AppInfrastructureDoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /> </service>
  • 27.
    The XML bit <serviceid="entity.repository.doctrine" class="AppInfrastructureDoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /> </service> <service id="entity.repository" alias="entity.repository.doctrine" />
  • 28.
    The XML bit <serviceid="entity.repository.doctrine" class="AppInfrastructureDoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /> </service> <service id="entity.repository" alias="entity.repository.doctrine" /> <service id="controller.entity" class="AppBundleControllerEntityController"> <argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="mailer" /> </service>
  • 29.
    Store the entityin-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; } }
  • 30.
  • 32.
    Dispatching the event classMyController { public function myAction(Request $request, $identifier) { // ... if ($form->isValid()) { $this->entityRepository->save($entity); $this->eventDispatcher->dispatch( EntitySaved::NAME, new EntitySaved($entity) ); } // ... } }
  • 33.
    An event class EntitySavedextends Event { const NAME = 'entity.saved'; private $entity; public function __construct(Entity $entity) { $this->entity = $entity; } public function getEntity() : Entity { return $this->entity; } }
  • 34.
    A listener final classSendAnEmailWhenEntityIsSaved { public function __construct(MailerInterface $mailer) { /** ... **/ } public function onEntitySaved(EntitySaved $event) { $this->mailer->send(/** something **/); } }
  • 35.
    Because we wantsome XML <service id="controller.entity" class="AppBundleControllerEntityController"> <argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="event_dispatcher" /> </service> -
  • 36.
    Because we wantsome XML <service id="controller.entity" class="AppBundleControllerEntityController"> <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="AppEntityListenerSendAnEmailWhenEntityIsSaved"> <argument type="service" id="mailer" /> <tag name="kernel.event_listener" event="entity.saved" /> </service>
  • 37.
    What is thepoint? 4 We can create another listener easily 4 We just have to dispatch this event
  • 38.
  • 39.
    final class DispatchAnEventWhenEntityIsSavedimplements 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; } }
  • 40.
    Decorates the repository <serviceid="entity.repository.dispatch_an_event_when_saved" class="AppEntityRepositoryDispatchAnEventWhenEntityIsSaved" decorates="entity.repository"> <argument type="service" id="entity.repository.dispatch_an_event_when_saved.inner" /> <argument type="service" id="event_dispatcher" /> </service>
  • 41.
    We just haveto save class MyController { public function myAction(Request $request, $identifier) { // ... if ($form->isValid()) { $this->entityRepository->save($entity); } // ... } }
  • 42.
  • 43.
    What do wehave? 4 Flexible entity storage 4 Events dispatched regarding the storage 4 Optional actions on events 4 Easily testable code!
  • 44.
    Good practices, design patterns,they just help us to enable change
  • 45.
    How to applythat... 4 Closes the door! 4 Uses final keyword 4 private properties 4 Create extension points when required 4 Prevent in case of
  • 46.
    Maintainability 4 Distinct featuresin their own namespaces 4 Interfaces's names should reflect their responsibilities 4 Implementations' names should reflect their distinction
  • 47.