We, as developers, often think that we don’t have to or don’t need to know what are what they call design patterns. We think that we already know how to build a software and don’t need all this theory. Years after years, by having to deal with the low maintainability of my own codebases, I explored a lot of ways of decoupling applications, in order to have enterprise-grade software that last for years. With concrete examples, I want to share with you some design patterns and how they can help you to grow well structured and decoupled applications.
4. I probably wrote that
<?php
include 'header.php';
include 'pages/'. $_GET['page'].'.php';
include 'footer.php';
5. 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,
]);
}
}
6. 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);
}
}
// ...
}
7. 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(
// ...
);
}
// ...
15. Why do we refactor?
4 We want to reuse code
4 So we delegate the responsibilities
16. Why do we refactor?
4 We want to reuse code
4 So we delegate the responsibilities
4 And improve the readability
17. 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
18. 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
22. 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;
}
// ...
}
23. 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;
}
}
24. 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!')
);
}
}
}
25. /** @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!')
);
}
}
}
26. The XML bit
<service id="entity.repository.doctrine"
class="AppInfrastructureDoctrineEntityRepository">
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
27. 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" />
29. 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;
}
}
32. 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)
);
}
// ...
}
}
33. 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;
}
}
34. A listener
final class SendAnEmailWhenEntityIsSaved
{
public function __construct(MailerInterface $mailer)
{ /** ... **/ }
public function onEntitySaved(EntitySaved $event)
{
$this->mailer->send(/** something **/);
}
}
35. 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>
-
36. 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>
37. What is the point?
4 We can create another listener easily
4 We just have to dispatch this event
41. We just have to save
class MyController
{
public function myAction(Request $request, $identifier)
{
// ...
if ($form->isValid()) {
$this->entityRepository->save($entity);
}
// ...
}
}
45. 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
46. Maintainability
4 Distinct features in their own namespaces
4 Interfaces's names should reflect their
responsibilities
4 Implementations' names should reflect their
distinction