Special Anniversary Black Friday: Get 30% off all training and 10% off all services Get a Quote


Type-Safe Identifiers with Symfony and Doctrine: Using Dedicated ID Classes

· Oskar Stark · 2 minutes to read
type-safety-uuid

Learn how to enhance type safety in Symfony and Doctrine by using dedicated ID classes like BookId and UserId instead of raw UUIDs. This approach prevents identifier mix-ups, improves code clarity, and ensures better integration with Symfony Messenger and repository methods. Explore practical examples and best practices for implementing type-safe identifiers in your Symfony applications.

When working with Symfony and Doctrine, using UUIDs as entity identifiers is a common approach. Traditionally, IDs are stored as simple integers or as raw Uuid objects. However, this can lead to type confusion, especially when working with Symfony Messenger or repository methods. A more robust and type-safe approach is to use dedicated ID classes.

Why Use Dedicated ID Classes?

Using a dedicated ID class, such as BookId or UserId, ensures that identifiers cannot be accidentally mixed up. This is particularly useful when dealing with Symfony Messenger messages, where multiple UUIDs might be passed. For example:

namespace App\Message;

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

With this approach, we ensure that a BookId is never confused with a UserId. If we were using raw UUIDs, the parameters could easily be swapped, leading to hard-to-debug issues.

Implementing a Dedicated ID Class

Here’s how you can define a dedicated ID class for a Book entity:

namespace App\Domain;

use Symfony\Component\Uid\Ulid;

final class BookId extends Ulid
{
}

Now, in your Book entity, you can use this class as the identifier:

namespace App\Entity;

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

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

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

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

    // ...
}

The needed Doctrine type

The last code example will end up with the following exception:

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

To fix that, a BookIdType is needed:

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

which needs to be registered and used:

# config/packages/doctrine.yaml
doctrine:
    dbal:
        types:
            App\Doctrine\Type\BookIdType: App\Doctrine\Type\BookIdType
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;
    }

    // ...
}

Type-Safe Repository Methods

Another advantage is type-safe repository methods. Instead of fetching entities by a generic string UUID, you can enforce type safety at the method level:

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

And define a meaningful exception:

namespace App\Exception;

use App\Domain\BookId;

final class BookNotFoundException extends \RuntimeException
{
    public static function withId(BookId $id): self
    {
        return new self(sprintf('Book with ID %s not found.', $id->toString()));
    }
}

Conclusion

Using dedicated ID classes in Symfony with Doctrine provides type safety, reducing the risk of identifier mix-ups and improving code clarity. This is especially useful when working with Symfony Messenger and repositories, where incorrect ID handling can lead to runtime errors. By implementing dedicated ID classes, you create a more robust and maintainable codebase.

Start using ID classes now!

At SensioLabs, we help teams implement best practices for maintainable and scalable softwareContact us today to learn more!

This might also interest you

The SensioLabs team celebrating the 20th anniversary of Symfony with balloons
Jules Daunay

The Story Continues: SensioLabs Celebrates Symfony's 20th Anniversary

Time flies, especially when you're busy shaping the future of development! The SensioLabs team has just reached a milestone with the anniversary of the Symfony framework. We marked the occasion at the office, but the party isn't over yet. The date is already set for an XXL celebration at SymfonyCon Amsterdam 2025, from November 27 to 28.

Read more
3 dog heads
Mathieu Santostefano

Bring Your Own HTTP client

Break free from rigid dependencies in your PHP SDKs. Learn how to use PSR-7, PSR-17, and PSR-18 standards along with php-http/discovery to allow users to bring their favorite HTTP client, whether it's Guzzle, Symfony HttpClient, or another. A must-read for PHP and Symfony developers.

Read more
Blue sign on a building with several Now What? letters
Thibaut Chieux

How To Prioritize Messages When Building Asynchronous Applications With Symfony Messenger

Asynchronous processing offers benefits like decoupled processes and faster response times, but managing message priorities can become a challenge. When dealing with tasks ranging from password resets to complex exports, ensuring timely delivery of critical messages is essential. This article explores common asynchronous processing issues and provides solutions using Symfony Messenger, allowing you to optimize your application without extensive refactoring.

Read more
SensioLabs University Courses announcing the new level 3 Master training course now available
Jules Daunay

Master Symfony: Unlock Expert Skills with Our Training

Take your Symfony proficiency from good to great with the new Level 3 training course at SensioLabs! Master complex topics, optimize performance, and become a Symfony expert.

Read more
Two images: on the left many cars stuck in a traffic jam with the sign "All directions" above, on the right a blue car moving forward alone on the highway with the sign "Service Subscriber" and a Symfony logo above
Steven Renaux

Symfony Lazy Services with Style: Boost DX using Service Subscribers

Boost your Symfony app's performance and developer experience! Learn how to use Service Subscribers and traits for lazy service loading to reduce eager instantiation, simplify dependencies, and create modular, maintainable code.

Read more
Poster of Guillaume Loulier presentation
Salsabile El-Khatouri

A Symfony Training at SensioLabs: Behind The Scenes

What does Symfony training at SensioLabs look like? Find out in this interview with Guillaume Loulier, a passionate developer and trainer, who tells us all about the official Symfony training courses.

Read more
Toy factory production line
Silas Joisten

Supercharging Symfony Testing with Zenstruck Foundry

Zenstruck Foundry has revolutionized the way we write tests in Symfony. In this post, you’ll learn how expressive factories, isolated test data, and a smoother developer experience helped us streamline our testing workflow and boost productivity.

Read more
Photo speaker meetup AI Symfony
Jules Daunay

Symfony and AI: the video is now available

What about Symfony and Artificial Intelligence (AI)? This was the theme of the exclusive event organized by SensioLabs in partnership with Codéin on October 3rd. With the added bonus of feedback from a development project combining Symfony and AI. If you missed the event, check out the video now available for free on our Youtube channel.

Read more
2025 a year of celebrations for PHP with windows about API Platform, PHP, AFUP and Symfony
Jules Daunay

2025: a year of anniversaries for PHP, AFUP, Symfony and API Platform

2025 is going to be a big year for anniversaries. We will be celebrating the 20th anniversary of Symfony, the 30th anniversary of PHP, the 25th anniversary of AFUP and the 10th anniversary of API Platform. For SensioLabs, this is a major milestone that proves the longevity of the technologies in our ecosystem. We are proud to celebrate these anniversaries with the community all year long.

Read more
SymfonyDay Chicago 2025
Simon André

SymfonyDay Chicago 2025: A Celebration of Community

On March 17th, the Symfony community met in Chicago for SymfonyDay Chicago 2025. The event, held on St. Patrick's Day, was both a celebration of Symfony and a moment to support Ryan Weaver in his fight against cancer. It was more than just a conference — it was a gathering around a valued member of the community.

Read more
Summer Sales: 25% off Trainings
Salsabile El-Khatouri

This Summer, Stand on Top of the Symfony Podium

Summer is going to be a blast with the Euro Cup and the Paris 2024 Olympics. While you’re enjoying the sports, why not challenge yourself with the latest versions of Symfony and PHP? Throughout the summer, SensioLabs is offering the tools you need to level up your performance and make your mark as a top Symfony developer. Ready, set, go!

Read more
Oskar Stark joins SensioLabs De
Jules Daunay

SensioLabs Germany Announces New Managing Director: Oskar Stark

Oskar Stark, a member of the Symfony Core team, has been appointed Managing Director of SensioLabs Germany. This new addition to the team will foster the growth of SensioLabs Germany, the official subsidiary of SensioLabs in Germany, to the benefit of German-speaking Symfony users.

Read more
SymfonyLive 2024
Jules Daunay

SymfonyLive Paris 2024: Two Days of Conference and Fun.

On March 28th and 29th, the SensioLabs team and the French-speaking Symfony community gathered at the Cité Internationale Universitaire in Paris for SymfonyLive Paris 2024. Were you not at this must-attend event? We’ll summarize it for you, but only because it’s you!

Read more
Some speaker
Jules Daunay

Discover our Symfony events in February 2024

February was a busy month for SensioLabs regarding events related to Symfony. We attended Symfony meetups and conferences in France, Switzerland, England and finally Canada. We invite you to join us in this article for our review of February. Enjoy reading!

Read more
Symfony 7 Certification
Salsabile El-Khatouri

Symfony 7 Courses Are Now Available!

Symfony 7 has been released in November 2023. As the creator of Symfony, SensioLabs provides the official training courses on this new version of the framework. Discover our new Symfony 7 training programs in this article.

Read more
Image