# Ne soyez plus l'esclave de Doctrine

---
## Hello!

<div style="width: 50%; float: left;">
Grégoire Paris
</div>
<div style="width: 50%; float: right;">
Maxime Veber
</div>

---
### Une application classique

<pre style="width: 50%; float: left;">
.
├── AppBundle
    ├── Admin
    ├── AppBundle.php
    ├── Controller
    ├── DataFixtures
    ├── DependencyInjection
    ├── Entity
    ├── Form
    ├── Listeners
    ├── OAuth
    ├── Resources
    ├── Tag
    └── 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é ?

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

Notes:
- Pas de validation
- Pas de règles métier
- Aucune logique (pas de tests nécessaires)

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

---
### Doctrine n'a pas besoin de setters

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

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    // getters
}
```

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

    // getters
}
```

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

Note:
- Déportation de la validation dans les value objects
- Début d'arborescence
- 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

---
### Les constructeurs nommés

```php
class BetterArticle
{
    public static function createFromNative(string $content)
    {
        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 il vaut mieux définir les vôtres sous
  forme d'interface.
- 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

---
# Emoji test

💩

Note:
speaker notes FTW!