orm: Bug if you want to have an AUTO_INCREMENT id and an UUID

Scenario:

I want to have an AUTO_INCREMENT id from my DB layer, and also have a hash as Uuid.

If you have your entity like this:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Foo
{
    /**
     * @var int
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var Uuid
     * @ORM\Column(type="uuid", unique=true)
     * @ORM\GeneratedValue(strategy="CUSTOM")
     * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
     */
    private $uuid;
    // ...
}

When you try to persist a new entity:

$foo = new Foo();
$em->persist($foo);
$em->flush($foo);

Problem:

You will get an error saying:

“detail”: “An exception occurred while executing ‘INSERT INTO db_name.foo (id, hash) VALUES (?, ?)’ with params ["431548bf-1a89-4552-90dd-a1ee89b659f8", "36710515-8595-49c0-86b9-8c504b7fb243"]: Notice: Object of class Ramsey\Uuid\Uuid could not be converted to int”,

I was wondering why this was happening so I went deeper into Doctrine, and inside UnitOfWord::persistNew($class, $entity) I found something interesting: $idGen = $class->idGenerator; [line: 895] is a UuidGenerator!?

But shoulnd’t it be the generator for the id property the @ORM\Id | @ORM\GeneratedValue(strategy="IDENTITY") auto increment by default? Why it seems to be overrite it by the new Ramsey Uuid Generator?

After a little research I came with the conclusion that Doctrine annotations -in order to determine the idGenerator- it loads by order which is the idGenerator for the entity. Which means the last one (annotation) override the previous one.

I came with this solution:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

class Foo
{
    /**
     * @var Uuid
     * @ORM\Column(type="uuid", unique=true)
     * @ORM\GeneratedValue(strategy="CUSTOM")
     * @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
     */
    private $uuid;

    /**
     * IMPORTANT! This field annotation must be the last one in order to prevent 
     * that Doctrine will use UuidGenerator as $`class->idGenerator`!
     *
     * @var int
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;
    // ...
}

Conclusion: the solution was just moving as last annotation the id property from the entity which it is actually the real id (as AUTO_INCREMENT int from the DB) and still have the Uuid from Ramsey\Uuid\Uuid.

I think we should avoid this ordering problem in our entities files and takes the idGenerator from the property which has @ORM\Id.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 24
  • Comments: 17 (10 by maintainers)

Most upvoted comments

Only identifiers can have @GeneratedValue

This is currently by design but it could become supported in ORM 3.0. Note that in ORM 3.0 the UUID generator strategy will be removed, please use constructor injection for passing entity (uu)IDs.

Only identifiers can have @GeneratedValue

That’s true, but actually, an AUTO_INCREMENT can be applied only to one key in a table, but not necessarily the PK.

Consider this example:


DROP TABLE IF EXISTS `test_auto_incremental`;

CREATE TABLE `test_auto_incremental` (
  `uuid` VARCHAR(64)  PRIMARY KEY NOT NULL,
  `text` VARCHAR(255) DEFAULT NULL,
  `counter` INT(11) AUTO_INCREMENT NOT NULL,
  KEY `counter` (`counter`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO test_auto_incremental (uuid, `text`) VALUES (uuid(), 'Hello');
INSERT INTO test_auto_incremental (uuid, `text`) VALUES (uuid(), 'world');

SELECT * FROM `test_auto_incremental`;

So, technically Only identifiers can have @GeneratedValue is only for Doctrine by design, because at a lower level(SQL) we could say something like: Any Key can have a @GeneratedValue, but only one per Entity/Table.

In such case, this would allow this logic:

/** @ORM\Entity */
class Foo
{
    /**
     * @ORM\Id
     * @ORM\Column(type="uuid")
     */
    private $id;

    /**
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\Column(type="integer")
     */
    private $counter;

And what if you want UUID as Your IDENTITY and have internal AUTO GENERATED int in entity? It cries:

Could not convert database value “8” to Doctrine Type uuid