diff --git a/index.md b/index.md index 8f3dcd7d27ad5bfb79925ace09a59b8e2525882f..62c56d7a8969be5c4e1d9db02a9c8d79a615ed2e 100644 --- a/index.md +++ b/index.md @@ -30,6 +30,11 @@ Maxime Veber └── Twig </pre> +Notes: +- Aucune idée de ce que fait l'application +- Tout ce qu'on reconnait, c'est des répertoires qui sont présents sur d'autres + applications. + --- ### Vous avez dit entité ? @@ -53,6 +58,7 @@ Notes: - Pas de validation - Pas de règles métier - Aucune logique (pas de tests nécessaires) +- Pas de setter pour id, il est setté par Doctrine après la persistence --- ### Nous aimons le DDD @@ -61,6 +67,9 @@ Notes: - Représenter les **règles métier** dans les entités - Avoir une API expressive +Notes: +- Il existe d'autres architectures, cf le talk sur le clean code de Romain Kuzniak + --- ### Doctrine n'a pas besoin de setters @@ -107,6 +116,9 @@ class Article Note: - Impossible de persister une entité invalide - Validation compliquée quand on a trop de propriétés +- Pour qu'on puisse utiliser des constructeurs sans que ça crée de conflit avec + Doctrine, Doctrine utilise de la réflection ou de la désérialisation pour +hydrater les entités. --- ### Les value objects @@ -134,6 +146,58 @@ Note: - Doctrine Embeddables - Custom types +--- +### Les embeddables + +```php +use Doctrine\ORM\Annotation as ORM; + +class NiceArticle +{ + private $uuid; + + /** @ORM\Embedded(class = "ArticleContent") */ + private $articleContent; +} +``` + +Note: +- À utiliser en cas de Value Object composite +- Des soucis avec la nullabilité, contournables avec un package +- Ne peuvent contenir des colonnes complexes + +--- +### Les custom types + +```php +use Doctrine\DBAL\Platforms\AbstractPlatform as P; + +final class ArticleContentType extends Type +{ + public function convertToPHPValue($value, P $p): ArticleId + { + return new ArticleId($value); + } + + public function convertToDatabaseValue($value, P $p): string + { + return (string) $value; + } +} +``` + +Note: +- Permet de contrôler qu'on respecte toujours les règles métier à + l'hydratation. Si ça crashe, c'est qu'il manque des migrations. +- La méthode `getName()` fait doublon avec le nom utilisé lors de + l'enregistrement du type dans le registre de type, et disparaître dès Doctrine 3 +- `ArticleId` devrait être une clé naturelle ou un UUID, le principal c'est de + pas avoir besoin de demander à la DB de le calculer, ça évite des attaques +pour cause d'ID devinables, et ça évite d'exposer le nombre d'entités présentes +dans une table. Ça évite aussi des collisions lorsque vous migrez des données +d'une base vers une autre, et que la nouvelle base peut elle aussi être +alimentée directement. + --- ### Les constructeurs nommés @@ -145,9 +209,119 @@ class BetterArticle return new self(new ArticleContent($content)); } } +``` + +Note: +- On peut faire plusieurs constructeurs nommés +- On peut passer le constructeur en privé pour encourager l'utilisation des + constructeurs nommés. + +--- +### Les repositories + +```php +final class DoctrineArticleRepository implements ArticleRepository +{ + public function __construct(EntityManagerInterface $entityManager) + { + $this->entityManager = $entityManager; + } + + public function latestArticles(int $size): iterable + { + $this->entityManager->createQueryBuilder() + ->select('a.*') + ->from(Article::class) + ->orderBy('a.createdAt', 'DESC') + ->setMaxResults($size) + ->getQuery() + ->getResults(); + } +} +``` + +Note: +- `Repository`, c'est un pattern, et c'est vous qui devriez en définir + l'interface. +- `EntityRepository` et ses méthodes magiques à éviter si vous voulez des type + hints de retour et donc de l'autocompletion. Il est maintenant possible d'en +faire des services: https://github.com/doctrine/DoctrineBundle/pull/727, mais +ça ne résout pas le problème. Si vous tenez à les utiliser, injectez les dans +vos repositories plutôt que de les étendre. +- Plus l'interface est grosse, plus elle devient difficile à implémenter, et le + code qui consomme l'API a rarement besoin de faire beaucoup d'appels, du +coup… slide suivant +--- +# Les Query functions + +```php +final class DoctrineGetLatestArticles implements GetLatestArticles +{ + public function __construct(EntityManagerInterface $entityManager) + { + $this->entityManager = $entityManager; + } + + public function __invoke(int $size): iterable + { + $this->entityManager->createQueryBuilder() + ->select('a.*') + ->from(Article::class) + ->orderBy('a.createdAt', 'DESC') + ->setMaxResults($size) + ->getQuery() + ->getResults(); + } +} +``` + +Note: +- Tout de suite beaucoup plus simple à réimplémenter en elasticsearch + +--- +# Les collections + +- elles permettent à Doctrine de repérer les changements +- elles permettent à Doctrine de faire du lazy-loading +- vous pouvez les éviter en type hintant `iterable` et en utilisant un tableau + pour les initialiser. +- si une collection comporte énormément d'éléments, c'est probablement une + relation à faire sauter + +--- +# Faire une jointure avec une relation uni-directionnelle + +```php +$queryBuilder + ->select('a.*') + ->from(Article::class) + ->innerJoin(Comment::class, 'c', Expr\Join::WITH, 'c.article_id = a.id') + ->where("c.content LIKE '%Doctrine%'") + ->getQuery() + ->getResults(); ``` +Note: +- utile si on a besoin de rajouter des conditions sans nécessiter d'hydrater + des objets de la classe jointe. + +--- +# Choisir la bonne API pour interroger la base de données + +- DQL Query Builder +- DQL +- SQL Query Builder +- SQL + +Note: +- Si vous n'avez pas besoin d'objets, le SQL peut s'avérer plus simple, plus + puissant, et plus performant (pas d'hydratation). +- Si vous voulez des objets mais ne pouvez pas utiliser du DQL, tournez vous + vers le ResultSetMapping +- Si vous n'avez pas besoin de faire des requêtes dynamiques, vous pouvez vous + passer du Query Builder. + --- # Emoji test