Skip to content

Instantly share code, notes, and snippets.

@webdevilopers
Last active March 14, 2022 11:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webdevilopers/1ad09ce341ab81ccfc588841e1d1f223 to your computer and use it in GitHub Desktop.
Save webdevilopers/1ad09ce341ab81ccfc588841e1d1f223 to your computer and use it in GitHub Desktop.
Using Value Objects as Custom Doctrine DBAL Type as Identifier in Symfony
# Doctrine Configuration
doctrine:
dbal:
driver: "%database_driver%"
host: "%database_host%"
server_version: 5.1
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver, add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# path: "%database_path%"
types:
guttercolorid: Sps\DormerCalculation\Infrastructure\Persistence\Doctrine\GutterColorIdType
<?php
namespace Sps\DormerCalculation\Infrastructure\Persistence\Doctrine;
use Doctrine\ORM\EntityRepository;
/**
* Class DoctrineGutterColorRepository
* @package Sps\DormerCalculation\Infrastructure\Persistence\Doctrine
*/
final class DoctrineGutterColorRepository
extends EntityRepository
implements GutterColorRepository
{}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Sps\DormerCalculation\Domain\Model\Gutter\GutterColor" table="gutter_color"
repository-class="Sps\DormerCalculation\Infrastructure\Persistence\Doctrine\DoctrineGutterColorRepository">
<id type="guttercolorid" name="id" />
<field name="name" column="name" length="50" />
</entity>
</doctrine-mapping>
<?php
namespace Sps\DormerCalculation\Domain\Model\Gutter;
use Sps\DormerCalculation\Domain\Model\Gutter\GutterColorId;
/**
* Class GutterColor
* @package Sps\DormerCalculation\Domain\Model\Gutter
*/
final class GutterColor
{
/** @var GutterColorId $id */
private $id;
/** @var string $name */
private $name;
}
<?php
namespace Sps\DormerCalculation\Infrastructure\Persistence\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\GuidType;
use Doctrine\DBAL\Types\Type;
use Sps\DormerCalculation\Domain\Model\Gutter\GutterColorId;
/**
* Class GutterColorIdType
* @package Sps\DormerCalculation\Infrastructure\Persistence\Doctrine
*/
#final class GutterColorIdType extends Type
final class GutterColorIdType extends GuidType
{
const GUTTER_COLOR_ID = 'guttercolorid';
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getGuidTypeDeclarationSQL($fieldDeclaration);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (empty($value)) {
return null;
}
if ($value instanceof GutterColorId) {
return $value;
}
return GutterColorId::fromInteger($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (empty($value)) {
return null;
}
return $value->toInteger();
}
public function getName()
{
return self::GUTTER_COLOR_ID;
}
/**
* {@inheritdoc}
*
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
* @return boolean
*/
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
@webdevilopers
Copy link
Author

I tried several generator strategies for the custom type. Still I get the following error when passing a value object to the repository:

$this->gutterColorRepository->find(GutterColorId::fromInteger(1));

Catchable Fatal Error: Object of class Sps\DormerCalculation\Domain\Model\Gutter\GutterColorId could not be converted to string

Any ideas @carlosbuenosvinos, @tgalopin, @Ocramius maybe ?

@webdevilopers
Copy link
Author

Solved as discussed on twitter:

it’s possible to use Objects as identifiers for Entities as long as they implement the magic method __toString()

@yvoyer
Copy link

yvoyer commented Mar 10, 2019

why not just use a none strategy, set the Id to the string value of your VO. this would avoid the need to define a new type, and would still work in query, as long as you use the toInteger().

ie.

// entity
private $id; // int
public function __construct(GutterColorId $id) 
{
    $this->id = $id->toInteger();
}
public function getId(): GutterColorId
{
    return GutterColorId::fromInt($this->id);
}

or another option if you wish to keep the property as an object, us @Embedable thought I never tried it on a Identity.

@Kwadz
Copy link

Kwadz commented Mar 13, 2022

Hi @webdevilopers, could you share the class GutterColorId? I'm interested in the way you convert UUID string to binary.
I'm also wondering how the binary the is declared for Doctrine. Do you use Ramsey\Uuid\Doctrine\UuidBinaryType ?
Thanks and keep up the good work!

@webdevilopers
Copy link
Author

Hi @Kwadz ,

wow, this gist is almost 3 years old. I currently don't maintain the related code any more.
Regarding Doctrine ORM I converted most of my Models to event-sourced Aggregate Roots (A+ES).

All I can offer is the last commit I did on that class. but I can not confirm that it works.

<?php

namespace Sps\DormerConstruction\Infrastructure\Persistence\Doctrine;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Sps\DormerConstruction\Domain\Model\GutterColor\GutterColorId;

final class GutterColorIdType extends Type
{
    const GUTTER_COLOR_ID = 'gutter_color_id';

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

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        if (empty($value)) {
            return null;
        }

        if ($value instanceof GutterColorId) {
            return $value;
        }

        return GutterColorId::fromInteger($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if (empty($value)) {
            return null;
        }

        return $value->toInteger();
    }

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

    /**
     * {@inheritdoc}
     *
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
     * @return boolean
     */
    public function requiresSQLCommentHint(AbstractPlatform $platform)
    {
        return true;
    }
}

In this example the value object uses a simple integer.

<?php

namespace Sps\DormerConstruction\Domain\Model\GutterColor;

final class GutterColorId
{
    private int $id;

    private function __construct(int $id)
    {
        $this->id = $id;
    }

    public function __toString(): string
    {
        return (string)$this->id;
    }

    public static function fromInteger(int $id) : GutterColorId
    {
        return new self($id);
    }

    public function toInteger(): int
    {
        return $this->id;
    }
}

Since we switched to event sourcing we are indeed using Ramsey UUID exclusively.

Hope it helps!

@webdevilopers
Copy link
Author

PS.: Are you on twitter?!

@Kwadz
Copy link

Kwadz commented Mar 13, 2022

It's much simpler than I expected :)

Actually I found this article about the conversion that uses preg_replace. But yes of course a simple cast should be enough. Thanks for opening my eyes!

Yes I have twitter, I've just subscribed to your page ;)

@webdevilopers
Copy link
Author

I favorited that page some years ago too. 😇

Yes, actually it is pretty easier. I think it work out-of-the-box with this @ramsey library extension:
https://github.com/ramsey/uuid-doctrine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment