Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Un moteur bien huilé - Apéro PHP 06/2022

Un moteur bien huilé - Apéro PHP 06/2022

Talk donné au Super Apéro PHP le 23 juin 2022.

Le Rule Engine est un puissant pattern qui nous permet d’implémenter des systèmes complexes de manière découplée, extensible et testable.

Nous allons voir comment nous avons implémenté des centaines de règles métier dans une application Symfony tout en gardant un code simple et lisible, notamment grâce à l’injection de dépendances.

Thibault

June 23, 2022
Tweet

More Decks by Thibault

Other Decks in Programming

Transcript

  1. 📊 Sondage Qui a déjà : 🙋 Fais des demandes

    à un organisme social ? @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 05 / 40
  2. 📊 Sondage Qui a déjà : 🙋 Fais des demandes

    à un organisme social ? 🙋‍♀️ Loué un logement ? @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 06 / 40
  3. 📊 Sondage Qui a déjà : 🙋 Fais des demandes

    à un organisme social ? 🙋‍♀️ Loué un logement ? 🙋‍♂️ Demandé un prêt ? @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 07 / 40
  4. Veuillez nous fournir les documents suivants pièce d’identité 3 dernières

    fiches de paies justificatif de domicile avis d’imposition … @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 08 / 40
  5. Veuillez nous fournir les documents suivants pièce d’identité si français,

    pièce d’identité si étranger, titre de séjour 3 dernières fiches de paies si salarié, 3 dernières fiches de paies si indépendant, bilan comptable si chômeur, attestation de droits @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 09 / 40
  6. Implémenter ces règles pour un acteur du logement social @t__richard

    - 23/06/2022 - Super Apéro PHP 🍻 - 10 / 40
  7. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 11

    / 40 class DocumentsManager { public function getDocuments(User $user): array { $documents = []; if ($user->isFrench()) { $documents[] = new Document('piece d\'identité'); } else { $documents[] = new Document('titre de séjour'); } if ($user->isEmployee()) { $documents[] = new Document('3 dernières fiches de paies'); } else if ($user->isIndependent()) { $documents[] = new Document('bilan comptable'); } else if ($user->isUnemployed()) { $documents[] = new Document('attestation de droits'); } return $documents; } }
  8. Le pattern Rules Engine @t__richard - 23/06/2022 - Super Apéro

    PHP 🍻 - 12 / 40 Système qui évalue un ensemble de règles et détermine les actions à réaliser
  9. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 14

    / 40 interface DocumentRuleInterface { public function when(User $user): bool; public function then(User $user): Document; }
  10. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 15

    / 40 class IdentityRule implements DocumentRuleInterface { public function when(User $user): bool { return $user->isFrench(); } public function then(User $user): Document { return new Document("Piece d'identité"); } }
  11. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 16

    / 40 class DocumentEngine { /** * @param DocumentRuleInterface[] $rules */ public function __construct(private readonly iterable $rules) { } public function getDocuments(User $user): array { $documents = []; foreach ($this->rules as $rule) { if ($rule->when($user)) { $documents[] = $rule->then($user); } } return $documents; } }
  12. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 18

    / 40 #[AutoconfigureTag('app.document_rule')] interface DocumentRuleInterface { public function when(User $user): bool; public function then(User $user): Document; }
  13. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 19

    / 40 class DocumentEngine { /** * @param DocumentRuleInterface[] $rules */ public function __construct( #[TaggedIterator('app.document_rule')] private readonly iterable $rules, ) {} public function getDocuments(User $user): array { $documents = []; foreach ($this->rules as $rule) { if ($rule->when($user)) { $documents[] = $rule->then($user); } } return $documents; } }
  14. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 20

    / 40 class DocumentController extends AbstractController { public function __construct(private readonly DocumentEngine $engine) { } #[Route('/documents')] public function listDocuments(): Response { $user = $this->getUser(); return new JsonResponse($this->engine->getDocuments($user)); } }
  15. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 22

    / 40 class IdentityRule implements DocumentRuleInterface { public function when(User $user): bool { return $user->isFrench(); } public function then(User $user): Document { return new Document("Piece d'identité"); } }
  16. Taux de vacance moyen structurel Nombre de logements LLS ou

    équivalent ayant été vacants pendant au moins 3 mois au cours des 12 mois précédent la date retenue, divisé par le Nombre de LLS ou équivalent à démolir par bâtiment @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 25 / 40
  17. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 26

    / 40 #[AutoconfigureTag('app.calculation_rule')] interface CalculationRuleInterface { public function when(Operation $operation): bool; public function then(Operation $operation): void; }
  18. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 27

    / 40 public function then(Operation $operation): void { $nombreDeLogementLLS = $operation->getCaracteristiquesTechniques()->getNombreDeLogementLLS(); $batimentsDemolis = $operation->getCaracteristiquesTechniques()->getBatimentsDemolis(); $totalBatimentsDemolisLLS = 0; foreach ($batimentsDemolis as $batimentDemoli) { $totalBatimentsDemolisLLS += $batimentDemoli->getNombreLLS(); } if ($totalBatimentsDemolisLLS !== 0) { $operation->getCaracteristiquesTechniques()->setTauxDeVacanceMoyenStructurel($nombreDeLogementLLS / $totalBatimentsDemolisLLS); } $operation->getCaracteristiquesTechniques()->setTauxDeVacanceMoyenStructurel(null); }
  19. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 29

    / 40 #[AutoconfigureTag('app.calculation_rule')] interface CalculationRuleInterface { public function when(Operation $operation): bool; public function then(Operation $operation): void; public function getPriority(): int; }
  20. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 30

    / 40 class CalculationEngine { /** * @param CalculationRuleInterface[] $rules */ public function __construct( #[TaggedIterator('app.calculation_rule', defaultPriorityMethod: 'getPriority')] private readonly iterable $rules, ) {} public function getDocuments(Operation $operation): void { foreach ($this->rules as $rule) { if ($rule->when($operation)) { $rule->then($operation); } } } }
  21. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 31

    / 40 $nombreDeLogementLLS = $operation->getCaracteristiquesTechniques()->getNombreDeLogementLLS(); public function then(Operation $operation): void { $batimentsDemolis = $operation->getCaracteristiquesTechniques()->getBatimentsDemolis(); $totalBatimentsDemolisLLS = 0; foreach ($batimentsDemolis as $batimentDemoli) { $totalBatimentsDemolisLLS += $batimentDemoli->getNombreLLS(); } if ($totalBatimentsDemolisLLS !== 0) { $operation->getCaracteristiquesTechniques()->setTauxDeVacanceMoyenStructurel($nombreDeLogementLLS / $totalBatimentsDemolisLLS); } $operation->getCaracteristiquesTechniques()->setTauxDeVacanceMoyenStructurel(null); }
  22. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 32

    / 40 class NombreDeLogementLLS implements CalculationRuleInterface { public function __construct(private readonly LogementRepository $logementRepository) { } public function then(Operation $operation): void { $operation->getCaracteristiquesTechniques()->setNombreDeLogementLLS( $this->logementRepository->countEmptyByOperation($operation) ); } ... }
  23. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 33

    / 40 class NombreDeLogementLLS implements CalculationRuleInterface { public function __construct(private readonly HttpClientInterface $apiClient) { } public function then(Operation $operation): void { $operation->getCaracteristiquesTechniques()->setNombreDeLogementLLS( $this->apiClient->request('GET', '/logements-vacants/'.$operation->getId())->getContent() ); } ... }
  24. Dépôt du dossier 📝 affichage : 6 mois avant la

    Date de lancement opérationnel ☑ clôture : Date de Dépôt est renseignée alerte : ⚠ modérée : Date de début de démarrage < 6 mois 🚨 élevée : Date de début de démarrage < 3 mois 😱 critique : Date de début de démarrage dépassée @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 35 / 40
  25. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 36

    / 40 interface TaskRuleInterface { public function apply(Operation $operation): ?Task; public function shouldDisplay(Operation $operation): bool; public function shouldClose(Operation $operation): bool; public function shouldAlert(Operation $operation): ?string; }
  26. @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 37

    / 40 final public function apply(Operation $operation): ?Task { if ($this->shouldDisplay($operation)) { if ($this->shouldClose($operation)) { return TaskFactory::createClosedTask($this->getTitle(), $this->getSlug()); } if (($niveauAlerte = $this->shouldAlert($operation)) !== null) { return TaskFactory::createAlertTask($this->getTitle(), $this->getSlug(), $this->getHelp(), $niveauAlerte); } return TaskFactory::createPendingTask($this->getTitle(), $this->getSlug()); } return null; }
  27. Quelques chiffres 61 documents 95 documents bis 306 règles de

    calcul 65 tâches @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 38 / 40
  28. Merci pour votre attention 🤩 @t__richard Slack Symfony & Bref

    💌 [email protected] @t__richard - 23/06/2022 - Super Apéro PHP 🍻 - 40 / 40 A vos questions !