Slide 1

Slide 1 text

Structurer une applica-on mé#er avec Symfony Romaric Drigon @ Apéro PHP, AFUP Poi5ers, 26/09/2019

Slide 2

Slide 2 text

Romaric Drigon So-ware engineer @ ASIT VD !

Slide 3

Slide 3 text

Le projet : une suite du style ERP

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Quelques chiffres • 8 développeurs !""""""" • ini0alement, 6 mois • 59 769 lignes de PHP écrites (sans les tests, Behat) • 584 classes • 1 611 fichiers

Slide 6

Slide 6 text

Ce que l'on veut éviter...

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Les stratégies mises en place

Slide 10

Slide 10 text

1/ « Diviser pour mieux régner »

Slide 11

Slide 11 text

Bounded contexts Source : Mar+n Fowler

Slide 12

Slide 12 text

Symfony 2 et 3 : bundles Symfony 4 : sous-dossiers et sous-namespaces # config/packages/doctrine.yaml doctrine: orm: mappings: App\Admin: is_bundle: false type: annotation dir: '%kernel.project_dir%/src/Admin/Entity' prefix: 'App\Admin\Entity' alias: App\Admin App\Catalog: is_bundle: false type: annotation dir: '%kernel.project_dir%/src/Catalog/Entity' prefix: 'App\Catalog\Entity' alias: App\Catalog

Slide 13

Slide 13 text

2/ Un modèle riche

Slide 14

Slide 14 text

Modèle anémique

Slide 15

Slide 15 text

Fat managers

Slide 16

Slide 16 text

Logique mé+er

Slide 17

Slide 17 text

3/ Documenter le modèle

Slide 18

Slide 18 text

Ubiquitous language

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Et des pa)erns pour nous aider

Slide 21

Slide 21 text

“A pa&ern is not a cookbook. It lets you start from a base of experience to develop your solu;on, and it gives you some language to talk about what you are doing.” — Eric Evans, Domain-Driven Design

Slide 22

Slide 22 text

4/ Évènements

Slide 23

Slide 23 text

Les avantages des évènements • perme&ent de découpler • évitent les répé**ons • les listeners sont des classes rela3vement pe*tes et simples • plus facilement testables • intégrés à Symfony (EventDispatcher)

Slide 24

Slide 24 text

Mon code après avoir u0lisé des évènements

Slide 25

Slide 25 text

4/ Évènements 4'/ Domain events

Slide 26

Slide 26 text

/** * @ORM\Entity * @ORM\HasLifecycleCallbacks() */ class User { // On stocke les évènements dans l'entité protected $events = []; public function enable() { if (self::STATUS_ENABLED !== $status) { // L'évènement est émis par l'entité $this->events[] = new UserEnabled($this->id, $this->email); } $this->status = self::STATUS_ENABLED; } /** * Au besoin, utiliser les lifecycle callbacks (update, remove). * @ORM\PreRemove */ public function onRemove() { $this->events[] = new UserRemoved($this->id, $this->name); } // Retourne et vide $events public function popEvents(): array { /* ... */ } }

Slide 27

Slide 27 text

class DomainEventsCollector implements EventSubscriber { private $events = []; // Domain events mis en attente private $eventDispatcher; // Event Dispatcher Symfony à injecter // On déclare un EventSubscriber Doctrine - aussi à faire sur postUpdate, postRemove... public function getSubscribedEvents() { return [Events::postPersist]; } public function postPersist(LifecycleEventArgs $event) { if (!$event->getEntity() instanceof User) { return; } foreach ($event->getEntity()->popEvents() as $event) { $this->events[spl_object_hash($event)] = $event; // On indexe par hash de l'objet, pour dé-dupliquer } } // Méthode qui sera à appeler (depuis nos controllers, depuis un ResponseListener...) public function dispatchCollectedEvents(): void { $events = $this->events; $this->events = []; foreach ($events as $event) { $this->eventDispatcher->dispatch(get_class($event), $event); // Syntaxe pour SF <= 4.3 } if ($this->events) { // Des listeners peuvent ré-emettre des évènements $this->dispatchCollectedEvents(); } } }

Slide 28

Slide 28 text

Code complet sur h"ps:/ /romaricdrigon.github.io/2019/08/09/domain-events

Slide 29

Slide 29 text

5/ Command bus

Slide 30

Slide 30 text

Un exemple - notre cas Un explorateur de fichier en Javascript : Toutes les requêtes sont envoyées en AJAX à une route du backend.

Slide 31

Slide 31 text

Ce que l'on veut éviter (bis)...

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Un bus de commande "...an object is used to encapsulate all informa5on needed to perform an ac5on..." — une commande "...a communica+on system that transfers data between components..." — un bus

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Mise en place Nous avons u)lisé Tac)cian, par Ross Tuck. Un bundle Symfony est disponible. De nombreuses implémenta1ons existent, comme SimpleBus, de Ma9hias Noback, ou encore avec le composant Messenger de Symfony.

Slide 36

Slide 36 text

Une commande namespace App\Model\Command; use App\Entity\File; class RemoveCommand { private $file; public function __construct(File $file) { $this->file = $file; } public function getFile(): File { return $this->file; } }

Slide 37

Slide 37 text

Le handler namespace App\Handler; use App\Entity\File; use App\Model\Command\RemoveCommand; class RemoveHandler { private $entityManager; // À injecter public function handle(RemoveCommand $removeCommand): string { $item = $removeCommand->getFile(); $this->entityManager->remove($file); $this->entityManager->flush(); return $file->getUuid(); // Un handler peut retourner une valeur } }

Slide 38

Slide 38 text

Envoi de la commande dans le bus /** * @ParamConverter("file") */ public function apiAction(File $file) { $command = new RemoveCommand($file); $uuid = $this->get('tactician.commandbus')->handle($command); return new JsonResponse(['uuid' => $uuid]); }

Slide 39

Slide 39 text

Un mois plus tard, nous avions 25 commandes et handlers (~3 500 lignes de code)

Slide 40

Slide 40 text

Encore quelques détails... • l'u%lisateur a-t-il le droit de faire ce2e ac%on sur ce fichier ? • valider que la requête est valide • l'exécuter • logguer (audit) • me2re à jour les quotas de l'u%lisateur • etc.

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Quelques exemples de middlewares Pour Tac)cian : • Doctrine : exécute le handler au sein d'une seule transac5on • Logger : loggue tout dans Monolog • Valida5on (fourni avec le bundle Symfony) • Security, pour l'autorisa5on • et les vôtres !

Slide 43

Slide 43 text

Conclusion

Slide 44

Slide 44 text

Bilan sur les stratégies • vocabulaire commun: indispensable ! • sauf si vous travaillez seul dans une gro1e • en4tés riches : très très recommandé • les en4tés resteront très longtemps dans le projet • ne soyez pas l'esclave de Doctrine ou de vos formulaires

Slide 45

Slide 45 text

Bilan sur les pa,erns • + code plus facile à comprendre • + moins de répé44ons • + productivité++ • + organisa4on plus facile en équipe (moins de conflits) • + plus facile à tester • - besoin d'explica4ons au début • Aidez-moi à faire connaître ces pa4erns !

Slide 46

Slide 46 text

Merci de votre a,en.on ! h"ps:/ /romaricdrigon.github.io/

Slide 47

Slide 47 text

One more thing...