Doctrine 2.5, DDD, Entities and Identities

If you’re developing applications in a Domain-Driven Design style and using Doctrine 2.5, you may be struggling with implementing your entities identities, I mean, UserId, ProductId, and so on. Embeddables are cool, but limited. Surrogates are not necessary anymore. Here are some short tips and examples about how to proceed.

tl;dr

If using Doctrine 2.5, embeddables are not an option yet for implementing your identities. The best option is using Custom Types.

What’s new in Doctrine 2.5

An interesting new feature about Doctrine 2.5, is that now, it’s possible to use Objects as identifiers for Entities as long as they implement the magic method __toString(). So we can add __toString to our Identities Value Objects and use them in our mappings. Let’s see.

Introduction

Consider the context of Last Wishes. To sum up, users that have wishes. Users are identified by a UserId, a value object that holds a UUID. Wishes are identified by a WishId and have a reference to its owner, a UserId.

Our Identity VO

First of all, let’s see our current identity objects. Remember, we need to add the __toString() method. First, UserId.

namespace Lw\Domain\Model\User;

use Rhumsaa\Uuid\Uuid;

/**
 * Class UserId.
 */
class UserId
{
    /**
     * @var string
     */
    private $id;

    /**
     * @param string $id
     */
    public function __construct($id = null)
    {
        $this->id = null === $id ? Uuid::uuid4()->toString() : $id;
    }

    /**
     * @return string
     */
    public function id()
    {
        return $this->id;
    }

    /**
     * @param UserId $userId
     *
     * @return bool
     */
    public function equals(UserId $userId)
    {
        return $this->id() === $userId->id();
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->id();
    }
}

Now, WishId.

namespace Lw\Domain\Model\Wish;

use Rhumsaa\Uuid\Uuid;

/**
 * Class WishId.
 */
class WishId
{
    /**
     * @var string
     */
    private $id;

    /**
     * @param string $id
     */
    public function __construct($id = null)
    {
        $this->id = $id ?: Uuid::uuid4()->toString();
    }

    /**
     * @return string
     */
    public function id()
    {
        return $this->id;
    }

    /**
     * @param WishId $wishId
     *
     * @return bool
     */
    public function equals(WishId $wishId)
    {
        return $this->id() === $wishId->id();
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->id();
    }
}

Custom Types

Check the implementation of the Doctrine Custom Types. They inherit from GuidType, so their internal representation will be an UUID. We need to specify what’s the database back and forward conversion. Last, we need to register our custom types before use them.

First, a base type for the UserId and WishId custom types.

namespace Lw\Infrastructure\Domain\Model;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\GuidType;

class DoctrineEntityId extends GuidType
{
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return $value->id();
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        $className = $this->getNamespace().'\\'.$this->getName();

        return new $className($value);
    }
}

Now, the UserId custom type.

namespace Lw\Infrastructure\Domain\Model\User;

use Lw\Infrastructure\Domain\Model\DoctrineEntityId;

class DoctrineUserId extends DoctrineEntityId
{
    public function getName()
    {
        return 'UserId';
    }

    protected function getNamespace()
    {
        return 'Lw\Domain\Model\User';
    }
}

Time for the WishId custom type.

namespace Lw\Infrastructure\Domain\Model\Wish;

use Lw\Infrastructure\Domain\Model\DoctrineEntityId;

class DoctrineWishId extends DoctrineEntityId
{
    public function getName()
    {
        return 'WishId';
    }

    protected function getNamespace()
    {
        return 'Lw\Domain\Model\Wish';
    }
}

Last, custom types registration.

namespace Lw\Infrastructure\Persistence\Doctrine;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;

class EntityManagerFactory
{
    /**
     * @return EntityManager
     */
    public function build()
    {
        \Doctrine\DBAL\Types\Type::addType('UserId', 'Lw\Infrastructure\Domain\Model\User\DoctrineUserId');
        \Doctrine\DBAL\Types\Type::addType('WishId', 'Lw\Infrastructure\Domain\Model\Wish\DoctrineWishId');

        return EntityManager::create(
            array(
                'driver' => 'pdo_sqlite',
                'path' => __DIR__.'/../../../../../db.sqlite',
            ),
            Setup::createYAMLMetadataConfiguration([__DIR__.'/config'], true)
        );
    }
}

Mappings

Check now Doctrine mappings. See how we’re using our custom types in the “type” field.

Lw\Domain\Model\User\User:
  type: entity
  id:
    userId:
      column: id
      type: UserId
  table: lw_user
  repositoryClass: Lw\Infrastructure\Domain\Model\User\DoctrineUserRepository
  fields:
    email:
      type: string
    password:
      type: string

Lw\Domain\Model\Wish\Wish:
  type: entity
  table: lw_wish
  repositoryClass: Lw\Infrastructure\Domain\Model\Wish\DoctrineWishRepository
  id:
    wishId:
      column: id
      type: WishId
  fields:
    address:
      type: string
    content:
      type: text
    userId:
      type: UserId
      column: user_id

Hope it helps to all the DDD lovers!