If you apply DDD in a Symfony project you’ll need to store value objects using Doctrine. We have two options to do that, using embeddables or using custom types, let’s see when we should use one or the other.

Embeddables

Embeddables are ideal for simple value objects that are formed by scalar types.

An important constraint is that they can’t be nullable in Doctrine 2 (probably will be in Doctrine 3) so this could key to decide between embeddables or custom types.

Following the example used in the official documentation if we have a VO like could be Address that is form by: street, postalCode, city and country, all of them as simple strings.

/** @Entity */
class User
{
    /** @Embedded(class = "Address") */
    private $address;
}

/** @Embeddable */
class Address
{
    /** @Column(type = "string") */
    private $street;

    /** @Column(type = "string") */
    private $postalCode;

    /** @Column(type = "string") */
    private $city;

    /** @Column(type = "string") */
    private $country;
}

Custom Mapping Types

Custom mapping types are meant to be used when you want to do special stuff when reading from the database or when you want to store it to the database itself.

Following the previous example let’s imagine we want to have some logic to serialize the data inside the VO so we could do something like that:

namespace App\Module\Address\Infrastructure\Persistence\Doctrine;

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use App\Module\Address\Domain\Address;

class AddressType extends Type
{
    const TYPE = 'address';

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        return Address::fromString($value);
    }

    /**
     * @param Address $value
     */
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return $value->toString();
    }

    public function getSQLDeclaration(array $field, AbstractPlatform $platform)
    {
        return $platform->getJsonTypeDeclarationSQL($field);
    }

    public function getName()
    {
        return self::TYPE;
    }
}

Check the official documentation here.