Newer
Older
# Ne soyez plus l'esclave de Doctrine
---
## Hello!
<div style="width: 50%; float: left;">
Software Engineer<br />
<span id="gregoire"></span><br />
</div>
<div id="universcine">➡ </div>
<div id="manomano">

</div>
</div>
<div style="width: 50%; float: right;" id="maxime">
Maxime Veber<br />
Backend developer<br />

- Maintenu par **Marco Pivetta** et **Guilherme Blanco**
- Boosté par **Michael Moravec** et **Luís Cobucci**
👏
Notes: Juste pour dire que nous n'avons rien à voir avec la team de Doctrine et qu'il faut les remercier.
public function getId()
{
return $this->id;
}
public function getContent()
{
return $this->content;
}
public function setContent($content)
{
$this->content = $content;
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
<div class="tweet" data-src="https://twitter.com/Ocramius/status/975399920202080256"></div>
- Représenter les **règles métier** dans les entités
- Avoir une API expressive
- Séparer le **domaine** de l'infrastructure
Notes:
- Il existe d'autres architectures, cf le talk sur le clean code de Romain Kuzniak
## Doctrine n'a pas besoin de setters
private $id;
private $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getId()
{
return $this->id;
}
public function getContent()
{
return $this->content;
}
Notes:
- intégrité du domaine
- Pas de setters par défaut, ni Doctrine ni sf n'en ont besoin
```php
class Article
{
private $id;
private $content;
public function __construct(string $content)
{
if (empty($content)) {
throw new ArticleHasNoContent();
}
$this->content = $content;
}
}
```
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 UUID
- Universally unique
- Indevinables
- Ne nécessitent pas de base de données
- `ramsey/uuid` à la rescousse
Notes:
- Idéalement, prendre une clé naturelle.
- Doctrine a un type `guid` qui correspond au type `uuid` dans Postgres,
jetez-y un oeil.
```php
class ArticleContent
{
private $content;
public function __construct(string $content)
{
if (empty($content)) {
throw new ArticleHasNoContent();
}
$this->content = $content;
}
}
```
Note:
- Déportation de la validation dans les value objects
- Début d'arborescence
- Doctrine Embeddables
- Custom types
---
## Instanciation avec des value object
Instanciation:
```php
use App\Domain\Article\Article;
use App\Domain\Article\ArticleContent;
use App\Domain\Article\ArticleTitle;
new Article(
new ArticleTitle('my title'),
new ArticleContent('This is a very short but nice article'),
new \DateTimeImmutable(),
);
```
Notes:
- ça peut vite devenir très pénible
## Les constructeurs nommés
```php
public static function createFromNative(string $title, string $content): self
return new self(
new ArticleTitle($title),
new ArticleContent($content),
new \DateTimeImmutable()
);
}
}
```
Note:
- On peut faire plusieurs constructeurs nommés
- On peut passer le constructeur en privé pour encourager l'utilisation des
constructeurs nommés.
---
## Persister des VO avec Doctrine
- les custom types
- les embeddables
final class ArticleContentType extends Type
public function convertToPHPValue($value, AbstractPlatform $platform): ArticleContent
public function convertToDatabaseValue($value, AbstractPlatform $platform): string
{
return (string) $value;
}
```yaml
# config/packages/doctrine.yaml
doctrine:
dbal:
types:
article_content: App\Infrastructure\Persistence\ArticleContentType
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
---
## Les embeddables
```php
use Doctrine\ORM\Annotation as ORM;
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
{
private $uuid;
/** @ORM\Embedded(class = "ArticleContent") */
private $articleContent;
public function __construct(ArticleContent $articleContent)
{
$this->uuid = Uuid::generate();
$this->articleContent = $articleContent;
}
}
```
```php
/** @ORM\Embeddable() **/
class ArticleContent
{
/** @ORM\Column() **/
private $content;
/** @ORM\Column() **/
private $lastModification;
}
```
Note:
- À utiliser en cas de Value Object composite
- Des soucis avec la nullabilité, contournables avec un package
- Ne peuvent contenir des colonnes complexes
---
## Les embeddables nullables
```php
use Doctrine\ORM\Annotation as ORM;
{
private $uuid;
/** @ORM\Embedded(class = "ArticleContent", nullable=true) */
private $articleContent;
}
```
<span style="font-size: 1em;">❌</span>
---
- Vos repositories en service facilement
- Les repositories non pollués par toutes les méthodes par défaut
Depuis DoctrineBundle 1.8.0 (novembre 2017) :
class ArticleRepository extends ServiceEntityRepository
public function __construct(RegistryInterface $registry)
parent::__construct($registry, Article::class);
final class DoctrineArticleRepository implements ArticleRepository, ServiceEntityRepositoryInterface
private $entityManager;
public function __construct(ManagerRegistry $registry)
$this->entityManager = $registry->getManagerForClass(Article::class);
- `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
---
```php
// Récupère votre repository d'article custom !
$repository = $entityManager->getRepository(Article::class);
```
---
## Les repositories, ça peut grossir
```php
interface ArticleRepository
{
public function latestArticles(int size): iterable;
public function mostReadArticles(int size): iterable;
public function mostCommentedArticles(int size): iterable;
public function byTopic(ArticleTopic $topic): iterable;
public function findRelated(Article $article): iterable;
// more and more methods…
}
```
---
## Les Query functions
Quand les repositories ne respectent pas l'ISP
```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
- Interface Segregation Principle
## Aliasing d'interfaces
```yaml
services:
App\Domain\Article\GetLatestArticles:
'@App\Infrastructure\Article\DoctrineGetLatestArticles'
Note:
- Inutile si il n'y a qu'une seule implémentation et que la feature de
discovery est configurée sur un namespace commun.
- Elles permettent à Doctrine de repérer les changements
- Elles permettent à Doctrine de faire du lazy-loading
---
## Ignorez les collections
- Champs initialisés en tableaux
- Utilisez le type `iterable` compatible avec `Collection` et `array`
{
private $comments;
public function __construct()
{
$this->comments = [];
}
public function getComments(): iterable
{
return $this->comments;
}
}
```
- Leur utilisation est très rare
- Elles ont un impact important sur les performances
Note:
- Ne pas faire de relations par réflexe, surtout bidirectionnelles
#### 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.*')
->from(Article::class)
->innerJoin(Comment::class, 'c', Expr\Join::WITH, 'c.article_id = a.id')
->where("c.content LIKE '%Doctrine%'")
->getQuery()
->getResults();
```
<video data-autoplay height="400" src="./UntidyCraftyCanadagoose.webm">
</video>
Note:
- utile si on a besoin de rajouter des conditions sans nécessiter d'hydrater
des objets de la classe jointe.
---
# Doctrine impose peu de choses
- `final` possible pour les méthodes en version 3
- `__clone` & `__wakeup` implémentables librement en version 3
---
<!-- .slide: data-background="./iwantmore.gif" -->
---
# Protips
<table>
<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>
<tr>
<th>Résultat</th>
<th>Objets</th>
<th>Scalaires</th>
</tr>
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.
```php
$rsm = new ResultSetMappingBuilder($entityManager);
$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();
```
Problème :
- Je développe un système d'adresse pour tous mes projets
- L'adresse est liée à mon utilisateur par une relation
<img src="./relationcirculaire.svg" style="width: 90%" />
---
## Resolve target entities
```yaml
doctrine:
orm:
resolve_target_entities:
Lib\Address\UserInterface: My\Project\Model\User
```
---
# What's next
# Support de MariaDB dans Doctrine 3
---
<!-- .slide: data-background="./explosion2.gif" -->
# La configuration YAML est dépréciée
<div style="width: 50%; float: left;">
<h1>Thanks!</h1>
</div>
<div style="width: 50%; float: right;" class="no-img-marge verticalAlign">
<table>
<tr>
<td style="padding-bottom: 50px">Grégoire Paris</td>
<td><img src="./twi1.svg" width="45" /> greg0ire</td>
</tr>
<tr>
<td style="padding-top: 50px">Maxime Veber</td>
<td><img src="./twi1.svg" width="45" /> nekdev</td>
</tr>
</table>
</div>