Il arrive parfois que nous, développeurs, pensions qu’il n’est pas nécessaire de connaître ce qu’ils appellent les « design patterns » ou « patrons de conception ». Nous pensons parfois que nous n’avons pas besoin de cette théorie. Après des années d’expériences avec la faible maintenabilité de mon propre code et de celui de mes clients, j’ai exploré de nombreuses façons de découpler nos applications afin de créer des applications « enterprise ready » qui peuvent vivre pendant de nombreuses années. Via des exemples concrets, je vais vous présenter quelques design patterns qui peuvent vous aider à travailler sur une codebase propre, structurée et bien découplée.
5. I probably wrote that
<?php
include 'header.php';
include 'pages/'. $_GET['page'].'.php';
include 'footer.php';
6. At some point I used Symfony
class EntityController extends AbstractController
{
public function action(
EntityManagerInterface $entityManager,
string $identifier
) {
$entity = $entityManager->getRepository(Entity::class)
->find($identifier);
return $this->render('my-template.html.twig', [
'entity' => $entity,
]);
}
}
7. Then I updated my entity...
public function action(
EntityManagerInterface $entityManager,
Request $request,
string $identifier
) {
// ...
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$entity = $form->getData();
$entityManager->persist($entity);
$entityManager->flush();
}
}
// ...
}
8. Then I sent an email
// ...
if ($form->isValid()) {
$entity = $form->getData();
$entityManager->persist($entity);
$entityManager->flush();
$mailer->send(
(new Email())
->setTo('samuel.roze@gmail.com')
->setBody('Very long text here...')
->setFrom('My super application')
// ...
);
}
// ...
9. Well... that's long!
class EntityController extends AbstractController
{
public function action(
EntityManagerInterface $entityManager,
MailerInterface $mailer,
Request $request,
string $identifier
) {
$entity = $entityManager->getRepository(Entity::class)
->find($identifier);
$form = $this->createForm(EntityFormType::class, $entity);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$entity = $form->getData();
$entityManager->persist($entity);
$entityManager->flush();
$mailer->send(
(new Email())
->setTo('samuel.roze@gmail.com')
->setBody('Very long text here...')
->setFrom('My super application')
);
}
}
return $this->render('my-template.html.twig', [
'entity' => $entity,
]);
}
}
16. 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
29. Dispatching the event
class MyController
{
public function __construct(
EntityRepository $entityRepository,
EventDispatcherInterface $eventDispatcher
) { /* ... */ }
public function action(Request $request, string $identifier)
{
// ...
if ($form->isValid()) {
$this->entityRepository->save($entity);
$this->eventDispatcher->dispatch(
EntitySaved::NAME,
new EntitySaved($entity)
);
}
// ...
}
}
30. 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;
}
}
31. A listener
final class SendAnEmailWhenEntityIsSaved
{
public function __construct(MailerInterface $mailer)
{ /** ... **/ }
public function onEntitySaved(EntitySaved $event)
{
$this->mailer->send(/** something **/);
}
}
32. Wiring it
final class SendAnEmailWhenEntityIsSaved implements EventSubscriberInterface
{
// ...
public static function getSubscribedEvents()
{
return [
EntitySaved::NAME => 'onEntitySaved',
];
}
public function onEntitySaved(EntitySaved $event)
{
// ...
}
}
33. What is the point?
4 The controller just have to dispatch this event
4 We can create another listener easily
4 Much easier to group things per feature
38. We just have to save
class MyController
{
public function action(Request $request, $identifier)
{
// ...
if ($form->isValid()) {
$this->entityRepository->save($entity);
}
// ...
}
}
40. What do we have?
4 Easy-to-change entity storage
4 Events dispatched regarding of the storage
implementation
4 Easy-to-turn-on-and-off actions (on events)
4 Easily testable code!
42. 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
43. Maintainability tips
4 Distinct features in their own namespaces
4 Interfaces' names should reflect their
responsibilities
4 Implementations' names should reflect their
distinction