Besoin d'un expert pour vous aider dans votre projet de développement Symfony ou PHP? Contactez-nous et obtenez un devis rapide


Identifiants sécurisés par type avec Symfony et Doctrine : utilisation de classes d’ID dédiées

· Oskar Stark · Temps de lecture: 2 minutes
type-safety-uuid

Apprenez à améliorer la sécurité des types dans Symfony et Doctrine en utilisant des classes d’ID dédiées comme BookId et UserId au lieu d’UUID bruts. Cette approche empêche la confusion des identifiants, améliore la clarté du code et assure une meilleure intégration avec Symfony Messenger et les méthodes de repository. Découvrez des exemples pratiques et les meilleures pratiques pour implémenter des IDs sécurisés par type dans vos applications Symfony.

.Lors de l'utilisation de Symfony et Doctrine, l'utilisation d'UUIDs comme identifiants d'entité est une approche courante. Traditionnellement, les IDs sont stockés sous forme de nombres entiers simples ou d'objets Uuid bruts. Cependant, cela peut entraîner une confusion des types, en particulier lors de l'utilisation de Symfony Messenger ou des méthodes de repository. Une approche plus robuste et plus sûre consiste à utiliser des classes d'ID dédiées.

Pourquoi utiliser des classes d'ID dédiées ?

L'utilisation d'une classe d'ID dédiée, comme BookId ou UserId, garantit que les identifiants ne peuvent pas être accidentellement confondus. Cela est particulièrement utile lors de la gestion de messages avec Symfony Messenger, où plusieurs UUIDs peuvent être passés. Par exemple :

namespace App\Message;

final readonly class MarkBookAsRead
{
    public function __construct(
        public BookId $bookId,
        public UserId $userId,
    ) {}
}

Avec cette approche, nous nous assurons qu'un BookId ne sera jamais confondu avec un UserId. Si nous utilisions des UUIDs bruts, les paramètres pourraient être facilement inversés, entraînant des erreurs difficiles à diagnostiquer.

Implémentation d'une classe d'ID dédiée

Voici comment définir une classe d'ID dédiée pour une entité Book :

namespace App\Domain;

use Symfony\Component\Uid\Ulid;

final class BookId extends Ulid
{
}

Désormais, dans votre entité Book, vous pouvez utiliser cette classe comme identifiant :

namespace App\Entity;

use App\Domain\BookId;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Book
{
    #[ORM\Id]
    #[ORM\Column(type: 'uuid', unique: true)]
    private BookId $id;

    public function __construct()
    {
        $this->id = new BookId();
    }

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

    // ...
}

Le type Doctrine nécessaire

Le dernier exemple de code entraînera l’exception suivante :

Cannot assign Symfony\Component\Uid\Ulid to property App\Entity\Book::$id of type App\Entity\BookiId

Pour corriger cela, un BookIdType est nécessaire :

namespace App\Doctrine\Type;

use App\Domain\BookId;
use Symfony\Bridge\Doctrine\Types\AbstractUidType;

final class BookIdType extends AbstractUidType
{
    public function getName(): string
    {
        return self::class;
    }

    protected function getUidClass(): string
    {
        return BookId::class;
    }
}

Ce type doit ensuite être enregistré et utilisé :

# config/packages/doctrine.yaml
doctrine:
    dbal:
        types:
            App\Doctrine\Type\BookIdType: App\Doctrine\Type\BookIdType

Dans l’entité Book :

namespace App\Entity;

+use App\Doctrine\Type\BookIdType;
use App\Domain\BookId;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Book
{
    #[ORM\Id]
-   #[ORM\Column(type: 'ulid', unique: true)]
+   #[ORM\Column(type: BookIdType::class, unique: true)]

    private BookId $id;

    public function __construct()
    {
        $this->id = new BookId();
    }

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

    // ...
}

Méthodes de repository avec typage fort

Un autre avantage est l'utilisation de méthodes de repository avec typage fort. Plutôt que de récupérer des entités via un string UUID générique, vous pouvez appliquer la sécurité des types au niveau des méthodes :

namespace App\Repository;

use App\Domain\BookId;
use App\Entity\Book;

final class BookRepository
{
    public function get(BookId $id): Book
    {
        $book = $this->find($id->toString());
        if (null === $book) {
            throw BookNotFoundException::withId($id);
        }
        return $book;
    }
}

Et définir une exception explicite :

namespace App\Exception;

use App\Domain\BookId;

final class BookNotFoundException extends \RuntimeException
{
    public static function withId(BookId $id): self
    {
        return new self(sprintf('Livre avec l’ID %s introuvable.', $id->toString()));
    }
}

Conclusion

L'utilisation de classes d'ID dédiées dans Symfony avec Doctrine améliore la sécurité des types, réduit le risque de confusion des identifiants et améliore la clarté du code. Cela est particulièrement utile lors de l'utilisation de Symfony Messenger et des méthodes de repository, où une mauvaise gestion des IDs peut entraîner des erreurs à l'exécution. En implémentant des classes d'ID dédiées, vous créez une base de code plus robuste et plus facile à maintenir.

Utilisez dès maintenant des classes d’ID

Chez SensioLabs, nous aidons les équipes à adopter les meilleures pratiques pour des logiciels maintenables et évolutifsContactez-nous dès maintenant pour en savoir plus !

Image