pas à l’origine ! Celui que vous venez tout juste d’écrire " Celui qui n’est pas testé # Celui qui fonctionne sur des technologies plus supportées $ Celui qui fonctionne en production mais qu’on a peur de changer %
un bug persistant et impactant Améliorer / simplifier le “design” général Optimiser les performances Pérenniser l’application dans le temps Remplacer une brique par une plus moderne Faire des montées en version de l’infrastructure … https://unsplash.com/@clemono
lequel vous l’avez trouvé Quels gains pour la tech ? Faciliter les futurs changements du code Réduire le risque de bugs Se concentrer davantage sur l’apport de valeur pour le métier https://unsplash.com/@orrbarone
bug Extraction de code dupliqué Remplacement d’une dépendance plus supportée Intégration d’une nouvelle fonctionnalité Abstraction de certains composants Ajout de tests unitaires ou fonctionnels Changement au niveau de l’infrastructure
avec les équipes métier Planifier un plan d’action longtemps à l’avance Communiquer avec toutes les parties concernées Avoir un environnement de préproduction Utiliser des feature flags pour beta tester S’outiller un maximum
well-written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is.” “With tests, we can change the behavior of our code quickly and verifiably. Without them, we really don’t know if our code is getting better or worse.” — Michael C. Feathers https://www.pmi.org/learning/library/legacy-it-systems-upgrades-11443
une procédure permettant de vérifier le bon fonctionnement d'une partie précise d'un logiciel ou d'une portion d'un programme (appelée « unité » ou « module »). ” — Wikipedia https://unsplash.com/@alexcioaba
n’a pas d’importance Indépendance vis-à-vis de l’environnement Répétition à l’infini dans les mêmes conditions Résultat binaire : réussite ou échec Couverture du “happy path” à minima Couverture des cas d’exception / d’erreur Couverture des cas à la marge … Isolated Repeatable Self-Validating Thorough
“smoke testing” Exécution de toutes les couches applicatives Validation de l’UI et des interactions Inconvénients Complexité de la mise en place Lenteur d’exécution (réseau, IO, etc.) Plus contraignant à écrire également
with the UI elements $client->request('GET', 'https://api-platform.com'); $client->clickLink('Getting started'); // Wait for an element to be present in the DOM (even if hidden) $crawler = $client->waitFor('#installing-the-framework'); // Alternatively, wait for an element to be visible $crawler = $client->waitForVisibility('#installing-the-framework'); echo $crawler->filter('#installing-the-framework')->text(); // Yeah, screenshot! $client->takeScreenshot('screen.png');
experience, users must be able to switch between supported languages. Scenario: A user is able to switch to English language. Given a guest user visits the French speaking website When they switch language to "english" Then they see "Create your account" as page title Scenario: A user is able to switch to French language. Given a guest user visits the English speaking website When they switch language to "français" Then they see "Créez votre compte" as page title
Client $client; private Crawler $crawler; public function __construct() { $this->client = Client::createChromeClient(); } #[Given('a guest user visits the English speaking website')] public function guestUserVisitsTheEnglishSpeakingWebsite(): void { $this->crawler = $this->client->request('GET', 'https://test.website.com'); } #[When('they switch language to :language')] public function theySwitchLanguageTo(string $language): void { $this->crawler = $this->client->clickLink($language); } #[Then('they see :title as page title')] public function theySeeAsPageTitle(string $title): void { if ($title !== $this->crawler->filter('title')->text()) { throw new \Exception('Title mismatch'); } } }
c’est quoi tous ces include / require ?! ” https://unsplash.com/@nate_dumlao “ Si seulement je pouvais exécuter tous ces scripts en une seule commande… ”
scripting language that is especially suited to web development. Fast, flexible and pragmatic, PHP powers everything from your blog & to the most popular websites in the world. TEXT; if (strncmp($text, 'PHP is a popular', strlen('PHP is a popular')) === 0) { echo 'Text starts with "PHP is a popular scripting language" string.'; } if (strpos($text, 'from your blog & to the most') !== false) { echo 'Text contains "from your blog & to the most" string.'; } $needle = 'in the world.'; $needleLength = strlen($needle); if ($needleLength <= strlen($text) && 0 === substr_compare($text, $needle, -$needleLength)) { echo 'Text ends with "in the world." string.'; }
'Text starts with "PHP is a popular scripting language"…'; } if (str_contains($text, 'from your blog & to the most')) { echo 'Text contains "from your blog & to the most" string.'; } if (str_ends_with($text, 'in the world.')) { echo 'Text ends with "in the world." string.'; } $ php71 polyfill-php80.php Text starts with "PHP is a popular scripting language" string. Text contains "from your blog & to the most" string. Text ends with "in the world." string.
return type specified\\.$#" count: 1 path: src/Controller/Authenticator.php - message: "#^Method App\\\\Controller\\\\Authenticator\\:\\:logout\\(\\) has no return type specified\\.$#" count: 1 path: src/Controller/Authenticator.php - message: "#^Method App\\\\Controller\\\\Customer\\:\\:edit\\(\\) has no return type specified\\.$#" count: 1 path: src/Controller/Customer.php - message: "#^Method App\\\\Controller\\\\Customer\\:\\:edit\\(\\) has parameter \\$id with no type specified\\.$#" count: 1 path: src/Controller/Customer.php - message: "#^Method App\\\\Controller\\\\Customer\\:\\:handleCreationFormSubmission\\(\\) has no return type specified\\.$#" count: 1 path: src/Controller/Customer.php - message: "#^Method App\\\\Controller\\\\Customer\\:\\:handleCreationFormSubmission\\(\\) has parameter \\$form with ...” count: 1 path: src/Controller/Customer.php - message: "#^Method App\\\\Controller\\\\Customer\\:\\:handleEditFormSubmission\\(\\) has no return type specified\\.$#" count: 1 path: src/Controller/Customer.php phpstan-baseline.neon
code mort Constructor property promotion Syntaxe moderne (array, closures, etc.) “ Early returns ” Privatisation des attributs et méthodes Renommage de fonctions et méthodes Migration pour Symfony, Twig, PHPUnit, Laravel, etc. Extensibilité : création / import de règles tierces …
la nomenclature du métier Exposer des constructeurs sémantiques Favoriser l’usage des ENUM Favoriser l’inversion des dépendances Définir des interfaces claires Eviter les modèles anémiques Contrôler les paramètres d’entrée Intégrer des librairies éprouvées …
des clients ? Quels types de clients ? Quels types de crédits ? Une compagnie d’assurance qui vend des produits d’assurance de crédit à des clients souscripteurs ? Une société de rachat de crédit ? Une société de vente de crédits à la consommation à des clients particuliers ? Une application de suivi des états financiers d’un client particulier ou professionnel ? Une application qui permet de prêter de l’argent entre amis proches ou membres d’une même famille ? Quel est le métier de cette base de code ?
= new Loan(...); $installmentAmount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = (new Installment()) ->setLoan($loan) ->setDueDate(new DateTimeImmutable('2024-10-05')) ->setAmount($installmentAmount) ->setStatus(InstallmentStatus::DUE); // Each day, a cronjob runs to update the status of the due/overdue installments $receivedPaymentDate = ...; if ($receivedPaymentDate instanceof DateTimeImmutable) { $installment->setPaymentDate($receivedPaymentDate); $installment->setStatus(InstallmentStatus::PAID); } // If payment is still unpaid after due date, its marked overdue $today = new DateTimeImmutable('today'); if ($installment->getStatus() === InstallmentStatus::DUE && $today > $installment->getDueDate()) { $installment->setStatus(InstallmentStatus::OVERDUE); }
= new Loan(...); $installmentAmount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = (new Installment()) ->setLoan($loan) ->setDueDate(new DateTimeImmutable('2024-10-05')) ->setAmount($installmentAmount) ->setStatus(InstallmentStatus::DUE);
= new Loan(...); $amount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = Installment::due($loan, '2024-10-05', $amount);
= new Loan(...); $amount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = Installment::due($loan, '2024-10-05', $amount); // Each day, a cronjob runs to update the status of the due/overdue installments $receivedPaymentDate = ...; if ($receivedPaymentDate instanceof DateTimeImmutable) { $installment->setPaymentDate($receivedPaymentDate); $installment->setStatus(InstallmentStatus::PAID); } // If payment is still unpaid after due date, its marked overdue $today = new DateTimeImmutable('today'); if ($installment->getStatus() === InstallmentStatus::DUE && $today > $installment->getDueDate()) { $installment->setStatus(InstallmentStatus::OVERDUE); }
= new Loan(...); $amount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = Installment::due($loan, '2024-10-05', $amount); // Each day, a cronjob runs to update the status // of the due/overdue installments $receivedPaymentDate = new DateTimeImmutable('2024-10-03'); if ($receivedPaymentDate instanceof DateTimeImmutable) { $installment->pay($receivedPaymentDate); } // If payment is still unpaid after due date, its marked overdue $today = new DateTimeImmutable('today'); if ($installment->getStatus() === InstallmentStatus::DUE && $today > $installment->getDueDate()) { $installment->setStatus(InstallmentStatus::OVERDUE); }
the status // of the due/overdue installments $receivedPaymentDate = new DateTimeImmutable('2024-10-03'); try { if ($receivedPaymentDate instanceof DateTimeImmutable) { $installment->pay($receivedPaymentDate); } } catch (DomainException $e) { $this->logger->warning($e->getMessage()); }
= new Loan(...); $amount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = Installment::due($loan, '2024-10-05', $amount); // Each day, a cronjob runs to update the status // of the due/overdue installments $receivedPaymentDate = new DateTimeImmutable('2024-10-03'); if ($receivedPaymentDate instanceof DateTimeImmutable) { $installment->pay($receivedPaymentDate); } // If payment is still unpaid after due date, its marked overdue $today = new DateTimeImmutable('today'); if ($installment->getStatus() === InstallmentStatus::DUE && $today > $installment->getDueDate()) { $installment->setStatus(InstallmentStatus::OVERDUE); }
bool { return $this->status === InstallmentStatus::DUE; } public function isOverdue(): bool { return $this->status === InstallmentStatus::OVERDUE; } public function overdue(): void { if (!$this->isDue()) { throw new DomainException('Installment must be due!'); } $this->status = InstallmentStatus::OVERDUE; } }
= new Loan(...); $amount = $this->loanService->getInstallmentAmount($loan); // On loan settlement, all its installments are calculated $installment = Installment::due($loan, '2024-10-05', $amount); // Each day, a cronjob runs to update the status // of the due/overdue installments $receivedPaymentDate = new DateTimeImmutable('2024-10-03'); if ($receivedPaymentDate instanceof DateTimeImmutable) { $installment->pay($receivedPaymentDate); } // If payment is still unpaid after due date, // it is marked overdue $today = new DateTimeImmutable('today'); if ($installment->isDue() && $today > $installment->getDueDate()) { $installment->overdue(); }
modules de niveau inférieur. Les abstractions ne doivent pas dépendre des détails d’implémentation ; les implémentations dépendent des abstractions. Inversion des dépendances
InstallmentRepository { public function byId(string $id): Installement; /** * @return Installment[] */ public function findByLoan(Loan $loan): array; /** * @return Installment[] */ public function findYearlyPaid(string $year): array; public function save(Installment $installment): void; }
ComputeYearlyLoanInstallmentReporting( new InMemoryInstallmentRepository(), ); // General use case $computer = new ComputeYearlyLoanInstallmentReporting( new DoctrineInstallmentRepository( new EntityManager(...) ), );