# Ne soyez plus l'esclave de Doctrine

---
## Hello!

<div style="width: 50%; float: left;">
Grégoire Paris<br />
Software Engineer<br />
<span id="gregoire">![PhotoDeGregoire](./PARIS-150x150.jpg)</span><br />
![LogoUniversCine](./universcine.png) ➡ ![LogoManomano](./manomano.png)
</div>
<div style="width: 50%; float: right;" id="maxime">
Maxime Veber<br />
Backend developer<br />
![PhotoDeMaxime](./MaximeNB.png)
</div>

---
### Nous parlons de Doctrine ORM

<img src="./doctrine.svg" />

- Conçu par **Benjamin Eberlei**
- 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.

---
## Vous avez dit entité ?

```php
class TypicalArticle
{
    private $id;
    private $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


---

<div class="tweet" data-src="https://twitter.com/Ocramius/status/975399920202080256"></div>


---
## ️❤️ Domain Driven Design ❤️

- Représenter les **règles métier** dans les entités
- Avoir une API expressive
- Respecter l'encapsulation
- 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

```php
class NotThatTypicalArticle
{
    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

---
## Les règles métier

```php
class Article
{
    private $id;
    private $content;

    public function __construct(string $content)
    {
        if (empty($content)) {
            throw new ArticleHasNoContent();
        }
        $this->content = $content;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getContent()
    {
        return $this->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 value objects

```php
class ArticleContent
{
    private $content;
    private $lastModification;

    public function __construct(string $content)
    {
        if (empty($content)) {
            throw new ArticleHasNoContent();
        }
        $this->content = $content;
        $this->lastModification = new \DatetimeImmutable();
    }
}
```

Instanciation:
```php
new NiceArticle(new ArticleContent('This is a very short but nice article'));
```

Note:
- Déportation de la validation dans les value objects
- Début d'arborescence
- Doctrine Embeddables
- Custom types

---
## 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 embeddables

```php
use Doctrine\ORM\Annotation as ORM;

class NiceArticle
{
    private $uuid;

    /** @ORM\Embedded(class = "ArticleContent") */
    private $articleContent;

    public function __construct(ArticleContent $articleContent)
    {
        $this->uuid = Uuid::generate();
        $this->articleContent = $articleContent;
    }
}
```

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

Nécessite un setter.

---
## Les custom types

```php
class ArticleId extends Uuid { }
```

```php
final class ArticleIdType extends Type
{
    public function convertToPHPValue($value, AbstractPlatform $platform): ArticleId
    {
        return new ArticleId($value);
    }

    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.
- 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 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 ArticleRepository extends ServiceEntityRepository
{
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, Article::class);
    }

    // your methods
}
```

✔ Enregistrement en tant que service simple

---
## Les repositories

```php
final class DoctrineArticleRepository implements ArticleRepository, ServiceEntityRepositoryInterface
{
    private $entityManager;

    public function __construct(ManagerRegistry $registry)
    {
        $this->entityManager = $registry->getManagerForClass(Article::class);
    }

    public function latestArticles(int $size): iterable
    {
        $this->entityManager->createQueryBuilder()
          ->select('a.*')
          ->from(Article::class)
          ->orderBy('a.createdAt', 'DESC')
          ->setMaxResults($size)
          ->getQuery()
          ->getResults();
    }
}
```

✔ Repository simplifié

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

---

```php
// Récupère votre repository d'article custom !
$repository = $entityManager->getRepository(Article::class);
```

---
## 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'

    App\Infrastructure\Article\DoctrineGetLatestArticles:
        arguments:
            $entityManager: '@doctrine.orm.default_entity_manager'
```

---
## À quoi servent les collections ?

- 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`

```php
class FarBetterArticle
{
    private $comments;

    public function __construct()
    {
        $this->comments = [];
    }

    public function getComments(): iterable
    {
        return $this->comments;
    }
}
```

---
## Evitez les One-To-Many

- 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.*')
  ->from(Article::class)
  ->innerJoin(Comment::class, 'c', Expr\Join::WITH, 'c.article_id = a.id')
  ->where("c.content LIKE '%Doctrine%'")
  ->getQuery()
  ->getResults();
```

&nbsp;

<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.

---
## Choisir la bonne API pour interroger la base de données

<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>
</table>


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.

---
<!-- .slide: data-background="./iwantmore.gif" -->

---
## Result Set Mapping

```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();
```

SQL + Objets

---
# Protips

---
## Les dépendances circulaires entre paquets

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

---
## `__clone` & `__wakeup`

Ces deux méthodes sont utilisées par Doctrine 2

Mais pas par Doctrine 3

---
# Support de MariaDB dans Doctrine&nbsp;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>