Formations aurelearn

Utiliser le repository

Le ServiceEntityRepository

Lorsque Symfony vous crée un nouveau repository, vous remarquerez qu’il étend de la classe ServiceEntityRepository. Cette classe est générique, c’est-à-dire que ses valeurs peuvent être différentes en fonction du contexte dans lequel elle est exécutée.

Pour améliorer l’auto-complétion de votre IDE et la compréhension des outils d’analyse statique, on peut ajouter une annotation @extends à notre classe :

BookRepository.php

/**
* @extends ServiceEntityRepository<Book>
*
* @method Book|null find($id, $lockMode = null, $lockVersion = null)
* @method Book|null findOneBy(array $criteria, array $orderBy = null)
* @method Book[] findAll()
* @method Book[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BookRepository extends ServiceEntityRepository {
...
}

Cas d'utilisation

Le repository est le moyen le plus simple de requêter votre base de données. Mais il est généralement sous-utilisé, car nous avons tendance à utiliser les méthodes findAll et findBy directement dans nos controllers.

Ce n’est pas une solution parfaite puisque, dans une majorité de cas, lorsque nous récupérons notre entité depuis la base de données, nous n’avons en fait besoin que de certains champs. Nous pourrions donc éviter des temps de requête superflus en allant chercher uniquement les champs dont nous avons besoin.

Pour cela, nous pouvons utiliser le concept de ValueObject. Cette nomenclature de classe est destinée à créer des objets “bêtes” (dumb classes en anglais), qui ont comme seul rôle de représenter une structure de données pour la réutiliser.

Admettons que nous avons besoin de récupérer dans une entité Book seulement deux champs : title et releaseDate.

Création du ValueObject BookWithTitleAndRelease

Essayez de nommer vos ValueObject de manière compréhensible, même si le nom peut paraître long ce n’est pas grave, il vaut mieux qu’il soit explicite.

Si vous n’avez pas de dossier src/ValueObject, créez-le puis créez une classe BookWithTitleAndRelease à l’intérieur.

Le contenu de cette classe sera le suivant :

BookWithTitleAndRelease.php

<?php
declare(strict_types=1);
namespace App\ValueObject;
class BookWithTitleAndRelease
{
public string $title;
public \DateTimeImmutable $releaseDate;
public function __construct(string $title, \DateTimeImmutable $releaseDate)
{
$this->title = $title;
$this->releaseDate = $releaseDate;
}
}

Dans cet exemple nous n’utilisons pas de setters ni de getters car nous avons défini nos propriétés en public. Nous pourrions très bien les mettre en private et dans ce cas créer les setters/getters correspondants. Ici il s’agit seulement d’une préférence personnelle.

Création d'une nouvelle méthode dans le repository

Maintenant que nous avons préparé l’objet qui recevra les informations de la base de données, nous pouvons créer la méthode correspondante dans le repository BookRepository :

BookRepository.php

use App\ValueObject\BookWithTitleAndRelease;
class BookRepository extends ServiceEntityRepository
{
...
public function findBookWithTitleAndRelease(int $bookId): ?BookWithTitleAndRelease
{
$dto = BookWithTitleAndRelease::class;
/** @var array<int, BookWithTitleAndRelease> $result */
$result = $this
->createQueryBuilder('b')
->select("NEW $dto(b.title, b.releaseDate)")
->where('b.id = :bookId')
->setParameter('bookId', $bookId)
->getQuery()
->getArrayResult();
return $result;
}
}

Le mot-clé NEW dans le ->select permet à Doctrine d’hydrater automatiquement le DTO que l’on définit. Ainsi on peut lui passer en paramètre les champs voulus qui seront passés au constructeur de la classe (attention néanmoins, il faut que l’ordre des arguments soit respecté).

Ajouter la sérialisation

Un autre avantage des ValueObject est qu’ils permettent d’être sérialisés de manière très simple. La sérialisation vous permet, dans le cadre d’une API par exemple, de retourner les clés de votre JSON formatées d’une manière différente des propriétés de votre classe.

Dans notre exemple, nous souhaitons transformer notre propriété releaseDate en release_date au moment de l’envoi de la réponse :

BookWithTitleAndRelease.php

<?php
declare(strict_types=1);
namespace App\ValueObject;
use Symfony\Component\Serializer\Annotation\SerializedName;
class BookWithTitleAndRelease
{
#[SerializedName('title')]
public string $title;
#[SerializedName('release_date')]
public \DateTimeImmutable $releaseDate;
public function __construct(string $title, \DateTimeImmutable $releaseDate)
{
$this->title = $title;
$this->releaseDate = $releaseDate;
}
}

Vous devez installer le composant Serializer ↗ pour utiliser cette fonctionnalité :

shell

# Avec just
just composer require symfony/serializer
# Sans just
docker compose run --rm composer require symfony/serializer

© 2023 • Aurélien Devaux