diff --git a/hello.css b/hello.css index eba93159b831eca444f0074a64af30645288d2e6..04375f3ab85977d3299c2e4871836303e9a6869b 100644 --- a/hello.css +++ b/hello.css @@ -1,5 +1,5 @@ -.reveal pre { - font-size: 0.45em; +.reveal pre code { + max-height: none; } .reveal section img { diff --git a/index.html b/index.html deleted file mode 100644 index f67732aad79d46775cc1e8fd31cf69f534a237e9..0000000000000000000000000000000000000000 --- a/index.html +++ /dev/null @@ -1,39 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta charset="utf-8"> - <link rel="stylesheet" href="css/reveal.css"> - <link rel="stylesheet" href="css/theme/white.css"> - <link rel="stylesheet" href="lib/css/zenburn.css"> - <title>Ne soyez plus l'esclave de Doctrine</title> - <!-- Printing and PDF exports --> - <script> - console.log('hello'); - var link = document.createElement( 'link' ); - link.rel = 'stylesheet'; - link.type = 'text/css'; - link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css'; - document.getElementsByTagName( 'head' )[0].appendChild( link ); - </script> - </head> - <body> - <div class="reveal"> - <div class="slides"> - <section data-markdown="index.md"> - </section> - </div> - </div> - <script src="lib/js/head.min.js"></script> - <script src="js/reveal.js"></script> - <script> - Reveal.initialize({ - dependencies: [ - { src: 'plugin/markdown/marked.js' }, - { src: 'plugin/markdown/markdown.js' }, - { src: 'plugin/notes/notes.js', async: true }, - { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } } - ] - }); - </script> - </body> -</html> diff --git a/index.md b/index.md index 9f39671edadbb642876c4e40a7e9e67fe64bc8ef..bbc889b1f099cf200a718aec5575319eec79ab48 100644 --- a/index.md +++ b/index.md @@ -13,12 +13,12 @@ Maxime Veber<br /> </div> --- -### Nous parlons de Doctrine +### Nous parlons de Doctrine ORM  - Conçu par **Benjamin Eberlei** -- Maintenu par **Marco Pivetta** +- Maintenu par **Marco Pivetta** et **Guilherme Blanco** - Boosté par **Michael Moravec** et **Luís Cobucci** 👏 @@ -26,31 +26,7 @@ Maxime Veber<br /> Notes: Juste pour dire que nous n'avons rien à voir avec la team de Doctrine et qu'il faut les remercier. ---- -### Une application classique -``` -. -├── AppBundle - ├── Admin - ├── AppBundle.php - ├── Controller - ├── DataFixtures - ├── DependencyInjection - ├── Entity - ├── Form - ├── Listeners - ├── OAuth - ├── Resources - ├── Tag - └── Twig -``` - -Notes: -- "Pour tous ceux qui aiment les applications comme cela, cette présentation n'est pas pour vous désolé" -- 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é ? @@ -61,28 +37,46 @@ class TypicalArticle private $id; private $content; - public function getId() { return $this->id; } - public function getContent() { return $this->content; } + public function getId() + { + return $this->id; + } + + public function getContent() + { + return $this->content; + } + public function setContent($content) { $this->content = $content; + return $this; } } ``` +_modèle anémique_ + 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 + +--- +<blockquote class="twitter-tweet" data-lang="fr"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/doctrine3?src=hash&ref_src=twsrc%5Etfw">#doctrine3</a> docs now differentiate between anemic and rich entities! \o/<a href="https://t.co/cL00Ilh0DX">https://t.co/cL00Ilh0DX</a><br><br>Thanks <a href="https://twitter.com/Pierstoval?ref_src=twsrc%5Etfw">@Pierstoval</a>!</p>— �̥͙͔͓͙͇̙�̷̫̱͎͖�͉�͓̮̥�̛̳̙̗͍�̴̺ (@Ocramius) <a href="https://twitter.com/Ocramius/status/975399920202080256?ref_src=twsrc%5Etfw">18 mars 2018</a></blockquote> +<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> + + --- ### Nous aimons le DDD - Séparer le **domaine** de l'infrastructure - Représenter les **règles métier** dans les entités - Avoir une API expressive +- Respecter l'encapsulation Notes: - Il existe d'autres architectures, cf le talk sur le clean code de Romain Kuzniak @@ -101,7 +95,15 @@ class NotThatTypicalArticle $this->content = $content; } - // getters + public function getId() + { + return $this->id; + } + + public function getContent() + { + return $this->content; + } } ``` @@ -126,7 +128,15 @@ class Article $this->content = $content; } - // getters + public function getId() + { + return $this->id; + } + + public function getContent() + { + return $this->content; + } } ``` @@ -175,34 +185,90 @@ class NiceArticle /** @ORM\Embedded(class = "ArticleContent") */ private $articleContent; + + public function __construct(ArticleContent $articleContent) + { + $this->uuid = Uuid::generate(); + $this->articleContent = $articleContent; + } } ``` +Instanciation: +```php +new NiceArticle(new ArticleContent('This is a very short but nice article')); +``` + Note: - À utiliser en cas de Value Object composite - Des soucis avec la nullabilité, contournables avec un package - Ne peuvent contenir des colonnes complexes +--- +## [tarifhaus/doctrine-nullable-embeddable](https://github.com/tarifhaus/doctrine-nullable-embeddable) + +Nécessite un setter. + +--- +### Les constructeurs nommés + +```php +class BetterArticle +{ + public static function createFromNative(string $content) + { + return new self(new ArticleContent($content)); + } +} +``` + +Instanciation: +```php +BetterArticle::createFromNative('This is a very short but nice article'); +``` + +Note: +- On peut faire plusieurs constructeurs nommés +- On peut passer le constructeur en privé pour encourager l'utilisation des + constructeurs nommés. + --- ### Les custom types ```php -use Doctrine\DBAL\Platforms\AbstractPlatform as P; +class ArticleId extends Uuid { } +``` + +```php +use Doctrine\DBAL\Platforms\AbstractPlatform; -final class ArticleContentType extends Type +final class ArticleIdType extends Type { - public function convertToPHPValue($value, P $p): ArticleId + public function convertToPHPValue($value, AbstractPlatform $platform): ArticleId { return new ArticleId($value); } - public function convertToDatabaseValue($value, P $p): string + public function convertToDatabaseValue($value, AbstractPlatform $platform): string { return (string) $value; } + + public function getName() + { + return 'article_id'; + } } ``` +```yaml +# config/packages/doctrine.yaml +doctrine: + dbal: + types: + article_id: App\Infrastructure\Persistence\ArticleIdType +``` + 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. @@ -216,32 +282,43 @@ d'une base vers une autre, et que la nouvelle base peut elle aussi être alimentée directement. --- -### Les constructeurs nommés +### Les repositories + +- Vos repositories en service facilement +- Les repositories non pollués par toutes les méthodes par défaut + +_Sisi, c'est possible._ + +--- +### Repository as a Service + +Depuis DoctrineBundle 1.8.0 (novembre 2017) : ```php -class BetterArticle +class ArticleRepository extends ServiceEntityRepository { - public static function createFromNative(string $content) + public function __construct(RegistryInterface $registry) { - return new self(new ArticleContent($content)); + parent::__construct($registry, Article::class); } + + // your methods } ``` -Note: -- On peut faire plusieurs constructeurs nommés -- On peut passer le constructeur en privé pour encourager l'utilisation des - constructeurs nommés. +✔ Enregistrement en tant que service simple --- ### Les repositories ```php -final class DoctrineArticleRepository implements ArticleRepository +final class DoctrineArticleRepository implements ArticleRepository, ServiceEntityRepositoryInterface { - public function __construct(EntityManagerInterface $entityManager) + private $entityManager; + + public function __construct(ManagerRegistry $registry) { - $this->entityManager = $entityManager; + $this->entityManager = $registry->getManagerForClass(Article::class); } public function latestArticles(int $size): iterable @@ -257,6 +334,8 @@ final class DoctrineArticleRepository implements ArticleRepository } ``` +✔ Repository simplifié + Note: - `Repository`, c'est un pattern, et c'est vous qui devriez en définir l'interface. @@ -299,16 +378,24 @@ Note: --- ### 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 +- Elles permettent à Doctrine de repérer les changements +- Elles permettent à Doctrine de faire du lazy-loading + +Ignorez les : +- champs initialisés en arrays +- utilisez le type **iterable** compatible avec `Collection` et `array` + + +--- +# Evitez les OneToMany + +- Leur utilisation est très rare +- Elles ont un impact important sur les performances --- ### Faire une jointure avec une relation uni-directionnelle +Récupérer tous les articles dont les commentaires contiennent Doctrine **sans les commentaires**. ```php $queryBuilder ->select('a.*') @@ -326,10 +413,24 @@ Note: --- ### Choisir la bonne API pour interroger la base de données -- DQL Query Builder -- DQL -- SQL Query Builder -- SQL +<table> + <tr> + <th></th> + <th>Récupération d'objets</th> + <th>Récupération de scalars</th> + </tr> + <tr> + <th>Clause conditionnelle</th> + <td>Doctrine\ORM\QueryBuilder</td> + <td>Doctrine\DBAL\Query\QueryBuilder</td> + </tr> + <tr> + <th>Clause non conditionnelle</th> + <td>DQL</td> + <td>SQL</td> + </tr> +</table> + Note: - Si vous n'avez pas besoin d'objets, le SQL peut s'avérer plus simple, plus @@ -340,25 +441,51 @@ Note: passer du Query Builder. --- -# Random Protips +<!-- .slide: data-background="./iwantmore.gif" --> --- -### Personnaliser l'instanciation +### Result Set Mapping Builder - +```php +$rsm = new ResultSetMappingBuilder($entityManager); -Note: -- Symfony configure l'entity manager à l'aide d'un configurator qu'il faut - décorer si on veut redéfinir la ClassMetadataFactory +$rsm->addRootEntityFromClassMetadata(Comment::class, 'c'); + +$rsm->addJoinedEntityFromClassMetadata(Article::class, 'a', 'c', 'article', ['uuid' => 'article_uuid']); + +$query = $entityManager->createNativeQuery( +<<<'SQL' + SELECT *, a.uuid AS article_uuid, c.uuid AS comment_uuid + FROM comment c + INNER JOIN article ON c.article_uuid = a.uuid + WHERE article_uuid = ? +SQL +, $rsm); +$query->setParameter(1, $article->getUuid()); + +// Comment instances +$comments = $query->getResult(); +``` + +--- +# Protips + +--- +### `__clone` & `__wakeup` + +Ces deux méthodes sont utilisées par Doctrine 2 + +Mais pas par Doctrine 3 + +--- +# Support de MariaDB dans Doctrine 3 --- <!-- .slide: data-background="./explosion2.gif" --> # La configuration YAML est dépréciée --- -# Emoji test - -💩 +# Thanks! Note: speaker notes FTW! diff --git a/iwantmore.gif b/iwantmore.gif new file mode 100644 index 0000000000000000000000000000000000000000..c9e15044ffa1c613d26d67408cc7ff2d596d211e Binary files /dev/null and b/iwantmore.gif differ diff --git a/reveal.json b/reveal.json new file mode 100644 index 0000000000000000000000000000000000000000..5de2cd576f6ceb31afd80b22f7da8fc87060ea77 --- /dev/null +++ b/reveal.json @@ -0,0 +1,4 @@ +{ + "width": "100%", + "height": "100%" + } \ No newline at end of file