$30 off During Our Annual Pro Sale. View Details »

Un moteur bien huilé - Forum PHP 2022

Thibault
October 23, 2022

Un moteur bien huilé - Forum PHP 2022

Talk donné au Forum PHP 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

October 23, 2022
Tweet

More Decks by Thibault

Other Decks in Technology

Transcript

  1. Salut ! @t__richard - 13/10/2022 - 🎤 Forum PHP 2022

    🐭 - 01 / 45
  2. ⚙ Un moteur bien huilé @t__richard - 13/10/2022 - 🎤

    Forum PHP 2022 🐭 - 02 / 45
  3. ⚙ Un moteur bien huilé @t__richard - 13/10/2022 - 🎤

    Forum PHP 2022 🐭 - 03 / 45
  4. 📊 Sondage Qui a déjà : 🙋 Fais des demandes

    à un organisme social ? @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 04 / 45
  5. 📊 Sondage Qui a déjà : 🙋 Fais des demandes

    à un organisme social ? 🙋‍♀️ Loué un logement ? @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 05 / 45
  6. 📊 Sondage Qui a déjà : 🙋 Fais des demandes

    à un organisme social ? 🙋‍♀️ Loué un logement ? 🙋‍♂️ Demandé un prêt ? @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 06 / 45
  7. Veuillez nous fournir les documents suivants pièce d’identité 3 dernières

    fiches de paies justificatif de domicile avis d’imposition … @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 07 / 45
  8. 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 - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 08 / 45
  9. Implémenter ces règles pour un acteur du logement social @t__richard

    - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 09 / 45
  10. Thibault 🐇 💻 @t__richard - 13/10/2022 - 🎤 Forum PHP

    2022 🐭 - 10 / 45
  11. Thibault @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭

    - 11 / 45
  12. 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 - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 12 / 45
  13. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    13 / 45 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; }
  14. Le pattern Rules Engine @t__richard - 13/10/2022 - 🎤 Forum

    PHP 2022 🐭 - 14 / 45 Système qui évalue un ensemble de règles et détermine les actions à réaliser
  15. Implémentation maison @t__richard - 13/10/2022 - 🎤 Forum PHP 2022

    🐭 - 15 / 45
  16. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    16 / 45 interface DocumentRuleInterface { public function when(User $user): bool; public function then(User $user): Document; }
  17. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    17 / 45 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é"); } }
  18. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    18 / 45 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; } }
  19. Brancher le tout @t__richard - 13/10/2022 - 🎤 Forum PHP

    2022 🐭 - 19 / 45
  20. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    20 / 45 #[AutoconfigureTag('app.document_rule')] interface DocumentRuleInterface { public function when(User $user): bool; public function then(User $user): Document; }
  21. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    21 / 45 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; } }
  22. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    22 / 45 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)); } }
  23. Voici votre premier Rule Engine 🎉 @t__richard - 13/10/2022 -

    🎤 Forum PHP 2022 🐭 - 23 / 45
  24. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    24 / 45 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é"); } }
  25. Faire des calculs @t__richard - 13/10/2022 - 🎤 Forum PHP

    2022 🐭 - 25 / 45
  26. 📊 Sondage @t__richard - 13/10/2022 - 🎤 Forum PHP 2022

    🐭 - 26 / 45
  27. 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 - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 27 / 45
  28. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    28 / 45 #[AutoconfigureTag('app.calculation_rule')] interface CalculationRuleInterface { public function when(Operation $operation): bool; public function then(Operation $operation): void; }
  29. ⚠ Les slides suivantes peuvent heurter la sensibilité de certains

    spectateurs @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 29 / 45
  30. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    30 / 45 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); }
  31. Le chaînage @t__richard - 13/10/2022 - 🎤 Forum PHP 2022

    🐭 - 31 / 45
  32. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    32 / 45 #[AutoconfigureTag('app.calculation_rule')] interface CalculationRuleInterface { public function when(Operation $operation): bool; public function then(Operation $operation): void; public function getPriority(): int; }
  33. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    33 / 45 class CalculationEngine { /** * @param CalculationRuleInterface[] $rules */ public function __construct( #[TaggedIterator('app.calculation_rule', defaultPriorityMethod: 'getPriority')] private readonly iterable $rules, ) {} public function calculate(Operation $operation): void { foreach ($this->rules as $rule) { if ($rule->when($operation)) { $rule->then($operation); } } } }
  34. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    34 / 45 class GroupFixtures extends Fixture implements DependentFixtureInterface { public function load(ObjectManager $manager) { // ... } public function getDependencies() { return [ UserFixtures::class, ]; } }
  35. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    35 / 45 $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); }
  36. Ouvrir le champ des possibles @t__richard - 13/10/2022 - 🎤

    Forum PHP 2022 🐭 - 36 / 45
  37. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    37 / 45 class NombreDeLogementLLS implements CalculationRuleInterface { public function __construct(private readonly LogementRepository $logementRepository) { } public function then(Operation $operation): void { $operation->getCaracteristiquesTechniques()->setNombreDeLogementLLS( $this->logementRepository->countEmptyByOperation($operation) ); } ... }
  38. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    38 / 45 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() ); } ... }
  39. 📝 Une "todo list" @t__richard - 13/10/2022 - 🎤 Forum

    PHP 2022 🐭 - 39 / 45
  40. Dépôt du dossier 📝 affichage : 6 mois avant la

    Date de lancement opérationnel ☑ clôture : Date de Dépôt du dossier est renseignée alerte : ⚠ modérée : Date de lancement < 4 mois 🚨 élevée : Date de lancement < 2 mois 😱 critique : Date de lancement dépassée @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 40 / 45
  41. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    41 / 45 interface TaskRuleInterface { public function shouldDisplay(Operation $operation): bool; public function shouldClose(Operation $operation): bool; public function shouldAlert(Operation $operation): ?string; public function getTitle(): string; public function getSlug(): string; public function getHelp(): string; }
  42. @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 -

    42 / 45 final public function getTasks(Operation $operation): array { $tasks = []; foreach ($this->rules as $rule) { if ($rule->shouldDisplay($operation)) { if ($rule->shouldClose($operation)) { $tasks[] = TaskFactory::createClosedTask($rule->getTitle(), $rule->getSlug()); continue; } if (($niveauAlerte = $rule->shouldAlert($operation)) !== null) { $tasks[] = TaskFactory::createAlertTask($rule->getTitle(), $rule->getSlug(), $rule->getHelp(), $niveauAlerte); continue; } $tasks[] = TaskFactory::createPendingTask($rule->getTitle(), $rule->getSlug()); } } return $tasks; }
  43. Quelques chiffres 📄 47 + 92 documents (3300 LOC) 🧮

    306 règles de calcul (6300 LOC) ☑ 65 tâches (3000 LOC) @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 43 / 45
  44. Conclusion @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭

    - 44 / 45
  45. Merci pour votre attention 🤩 @t__richard Slack Symfony & Bref

    💌 thibault@widop.com @t__richard - 13/10/2022 - 🎤 Forum PHP 2022 🐭 - 45 / 45 A vos questions !