Using Value Objects as entity ID in Doctrine
It’s really easy to apply DDD style ValueObject UUID ID’s in your Doctrine entities with custom Doctrine types and my tiny helper class available at Github.
Try not to use annotation mapping in your Doctrine entities. You want your core domain to be free of any infrastructure leaks.
Example domain aggregate root. Notice custom type named article_id. We will tell Doctrine how to handle this custom type later on.
<?php
namespace DDD\DomainBundle\Article\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class Article
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
* @ORM\Column(type="article_id")
*/
private $id;
public function __construct(ArticleId $id)
{
$this->id = $id;
}
public function getAggregateId(): ArticleId
{
return $this->id;
}
}
ArticleID
extends from AggregateRootId
to keep some common logic separated.
<?php
namespace DDD\DomainBundle\Article\Entity;
use Mikemix\ValueObjectId\Entity\AggregateRootId;
final class ArticleId extends AggregateRootId
{
}
All of this would not be possible without custom Doctrine mapping type. My AbstractUuidType
will handle all the hard work converting to and from the database, storing UUID optimized as binary(16)
. All you have to do is implement the getValueObjectClassName
method and return your Value Object’s FQCN.
<?php
namespace DDD\InfrastructureBundle\ORM\Type;
use DDD\DomainBundle\Article\Entity;
use Mikemix\ValueObjectId\ORM\Type\AbstractUuidType;
class ArticleIdType extends AbstractUuidType
{
const ARTICLE_ID = 'article_id';
public function getName()
{
return static::ARTICLE_ID;
}
protected function getValueObjectClassName(): string
{
return ArticleId::class;
}
}
Type can be easily registered in Symfony’s app/config/config.yml
file:
doctrine:
dbal:
types:
article_id: DDD\InfrastructureBundle\ORM\Type\ArticleIdType
Everything should be wired correctly, lets try it:
<?php
// persist
$article = new Article(new ArticleId(Uuid::uuid4()));
$em->persist($article);
$em->flush();
// retrieve
$article = $em->find(Article::class, 'a1a55638-674a-4d05-a92f-3cd28549ae6d');
//or
$article = $em->find(Article::class, new ArticleId('a1a55638-674a-4d05-a92f-3cd28549ae6d'));
Result:
DDD\DomainBundle\Article\Entity\Article Object
(
[id:DDD\DomainBundle\Article\Entity\Article:private] => DDD\DomainBundle\Article\Entity\ArticleId Object
(
[uuid:protected] => a1a55638-674a-4d05-a92f-3cd28549ae6d
)
)
Classess are available via Composer and can be required as dependency in your composer.json
file, but better idea would be to copy everything to your project. It’s always one dependency less.