# 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 />
  <div id="gregoire_company">
    <div id="universcine">
      ![LogoUniversCine](./universcine_light.svg)
    </div>
    <div id="universcine">➡ </div>
    <div id="manomano">
      ![LogoManomano](./Logo_MAnoMano_HD.svg)
    </div>
  </div>
</div>
<div style="width: 50%; float: right;" id="maxime">
Maxime Veber<br />
Backend developer<br />
![PhotoDeMaxime](./MaximeNB.png)
</div>

---
<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 Article
{
    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 Article
{
    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(): int
    {
        return $this->id;
    }

    public function getContent(): string
    {
        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 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.

---
## Les value objects

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

---
## Les constructeurs nommés

```php
class Article
{
    public static function createFromNative(string $content)
    {
        return new self(new ArticleContent($content));
    }
}
```

Instanciation:
```php
new Article(new ArticleContent('This is a very short but nice article'));
// devient
Article::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\Types\Type;

final class ArticleContentType extends Type
{
    public function convertToPHPValue($value, AbstractPlatform $platform): ArticleContent
    {
        return new ArticleContent($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform): string
    {
        return (string) $value;
    }

    public function getName()
    {
        return 'article_content';
    }
}
```

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

class Article
{
    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;

class Article
{
    private $uuid;

    /** @ORM\Embedded(class = "ArticleContent", nullable=true) */
    private $articleContent;
}
```

<span style="font-size: 1em;">❌</span>

---
### tarifhaus/doctrine-nullable-embeddable

Nécessite un setter.


---
## 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);
    }

    // your methods…
}
```

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

---
## Et 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 Article
{
    private $comments;

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

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

---
## Éviter le mapping One-To-Many

- Permet d'éviter des collections inutiles
- 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();
```

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


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

---
# Protips

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

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

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