Bug if you want to have an AUTO_INCREMENT id and an UUID #5963

Closed
opened 2026-01-22 15:23:17 +01:00 by admin · 17 comments
Owner

Originally created by @Chemaclass on GitHub (May 7, 2018).

Originally assigned to: @Ocramius on GitHub.

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.

Originally created by @Chemaclass on GitHub (May 7, 2018). Originally assigned to: @Ocramius on GitHub. **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: ```php 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: ```php $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**: ```php 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`.
admin added the Won't FixInvalid labels 2026-01-22 15:23:17 +01:00
admin closed this issue 2026-01-22 15:23:17 +01:00
Author
Owner

@kalifg commented on GitHub (Oct 25, 2018):

There's no place in the Doctrine code that will use an IdentityGenerator for a non-identity field. Uses of IdentityGenerator::generate(): here, here and here; all deal with setting the identity fields.

I agree that if this is intentional, then it's a bug that the metadata loader still looks for the generator even if the field is not part of the identity.

This isn't UUID-specific, it goes for anything field type that you might want to generate automatically. The way things currently work, if you want to automate the generation of a non-identity field then you will need to use an event listener of some sort (I'd start with prePersist)

@kalifg commented on GitHub (Oct 25, 2018): There's no place in the Doctrine code that will use an `IdentityGenerator` for a non-identity field. Uses of `IdentityGenerator::generate()`: [here](https://github.com/doctrine/doctrine2/blob/dac1a169645a6b7ff155a5a82a8c36e40c357eb1/lib/Doctrine/ORM/UnitOfWork.php#L898), [here](https://github.com/doctrine/doctrine2/blob/95344d019fc4601dedd217e56bc969ba3404925e/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php#L284) and [here](https://github.com/doctrine/doctrine2/blob/e57be9da5e0c6bb31ac286d213e204784a34aa43/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php#L179); all deal with setting the identity fields. I agree that if this is intentional, then it's a bug that [the metadata loader still looks for the generator even if the field is not part of the identity](https://github.com/doctrine/doctrine2/blob/f0102a69f933646dff25f5fdf17bd20263f49bfc/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php#L297-L303). This isn't UUID-specific, it goes for anything field type that you might want to generate automatically. The way things currently work, if you want to automate the generation of a non-identity field then you will need to use an event listener of some sort (I'd start with `prePersist`)
Author
Owner

@kalifg commented on GitHub (Oct 25, 2018):

I'll also point out that the XML, Yaml and Database drivers do seem to isolate the setting of custom generators to id elements.

@kalifg commented on GitHub (Oct 25, 2018): I'll also point out that the XML, Yaml and Database drivers do seem to isolate the setting of custom generators to id elements.
Author
Owner

@kalifg commented on GitHub (Oct 25, 2018):

My code references were for 2.5 — master seems to have some heavy changes in it, but I believe it still has the same issue here

@kalifg commented on GitHub (Oct 25, 2018): My code references were for 2.5 — master seems to have some heavy changes in it, but I believe it still has the same issue [here](https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Mapping/Driver/Annotation/AnnotationDriver.php#L537)
Author
Owner

@Majkl578 commented on GitHub (Oct 25, 2018):

GeneratedValue currently only works for identifier fields on non-composite identified entities.

@Majkl578 commented on GitHub (Oct 25, 2018): GeneratedValue currently only works for identifier fields on non-composite identified entities.
Author
Owner

@metalmini commented on GitHub (Mar 8, 2019):

GeneratedValue currently only works for identifier fields on non-composite identified entities.

So, is this by design or a bug?

@metalmini commented on GitHub (Mar 8, 2019): > GeneratedValue currently only works for identifier fields on non-composite identified entities. So, is this by design or a bug?
Author
Owner

@Majkl578 commented on GitHub (Mar 8, 2019):

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.

@Majkl578 commented on GitHub (Mar 8, 2019): 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.
Author
Owner

@damianprzygodzki commented on GitHub (Apr 4, 2019):

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

@damianprzygodzki commented on GitHub (Apr 4, 2019): 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
Author
Owner

@Majkl578 commented on GitHub (Apr 4, 2019):

And what if you want UUID as Your IDENTITY?

I'd recommend using ramsey/uuid-doctrine:

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

    public function __construct(UuidInterface $id)
    {
        $this->id $id;
    }
}

It also provides UuidGenerator but I'd advise against that.

@Majkl578 commented on GitHub (Apr 4, 2019): > And what if you want UUID as Your IDENTITY? I'd recommend using [ramsey/uuid-doctrine](https://github.com/ramsey/uuid-doctrine): ```php /** * @ORM\Entity */ class Foo { /** * @ORM\Id * @ORM\Column(type="uuid") */ private $id; public function __construct(UuidInterface $id) { $this->id $id; } } ``` It also provides UuidGenerator but I'd advise against that.
Author
Owner

@damianprzygodzki commented on GitHub (Apr 4, 2019):

@Majkl578

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

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

    public function __construct(UuidInterface $id)
    {
        $this->id = $id;
    }
}

But it won't work.

@damianprzygodzki commented on GitHub (Apr 4, 2019): @Majkl578 ``` /** * @ORM\Entity */ class Foo { /** * @ORM\Id * @ORM\Column(type="uuid") */ private $id; /** * @ORM\GeneratedValue(strategy="SEQUENCE") * @ORM\Column(type="integer") */ private $counter; public function __construct(UuidInterface $id) { $this->id = $id; } } ``` But it won't work.
Author
Owner

@Ocramius commented on GitHub (Apr 4, 2019):

Only identifiers can have @GeneratedValue

@Ocramius commented on GitHub (Apr 4, 2019): Only identifiers can have `@GeneratedValue`
Author
Owner

@Ocramius commented on GitHub (Apr 4, 2019):

Closing here. As mentioned above:

I agree that if this is intentional, then it's a bug that the metadata loader still looks for the generator even if the field is not part of the identity.

Please open a new issue with a schema validator patch (preventing usage of @GeneratedValue on non-identifiers or composite identifiers), if needed.

@Ocramius commented on GitHub (Apr 4, 2019): Closing here. As mentioned above: > I agree that if this is intentional, then it's a bug that the metadata loader still looks for the generator even if the field is not part of the identity. Please open a new issue with a schema validator patch (preventing usage of `@GeneratedValue` on non-identifiers or composite identifiers), if needed.
Author
Owner

@Chemaclass commented on GitHub (Apr 4, 2019):

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;
@Chemaclass commented on GitHub (Apr 4, 2019): > 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: ```sql 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: ```php /** @ORM\Entity */ class Foo { /** * @ORM\Id * @ORM\Column(type="uuid") */ private $id; /** * @ORM\GeneratedValue(strategy="SEQUENCE") * @ORM\Column(type="integer") */ private $counter; ```
Author
Owner

@Ocramius commented on GitHub (Apr 4, 2019):

Yes, it can, but the ORM will not re-fetch data from the underlying layer after INSERT/UPDATE, so this won't ever work.

Same applies for:

  • Default column values
  • Generated/virtual columns
  • Triggers
@Ocramius commented on GitHub (Apr 4, 2019): Yes, it can, but the ORM will not re-fetch data from the underlying layer after `INSERT`/`UPDATE`, so this won't ever work. Same applies for: * Default column values * Generated/virtual columns * Triggers
Author
Owner

@vince83110 commented on GitHub (Dec 27, 2021):

Having the same issue, I just want to have the oppositte : my IDs are Uuid, and I want a field "code" with an auto-increment.

I changed the order of fields in my Entity as mentioned, but still have the same error:

    /**
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="code", initialValue=1)
     * @ORM\Column(type="integer")
     * @Groups({"customer:read", "order:read"})
     */
    private int $code;

    /**
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     * @Groups({
     *     "order:read",
     *     "customer:read"
     * })
     */
    private Uuid $id;

I generate the UUid in the constructor, so no need of GeneratedValue strategy.

Any solution ? Thanks

@vince83110 commented on GitHub (Dec 27, 2021): Having the same issue, I just want to have the oppositte : my IDs are Uuid, and I want a field "code" with an auto-increment. I changed the order of fields in my Entity as mentioned, but still have the same error: ``` /** * @ORM\GeneratedValue(strategy="SEQUENCE") * @ORM\SequenceGenerator(sequenceName="code", initialValue=1) * @ORM\Column(type="integer") * @Groups({"customer:read", "order:read"}) */ private int $code; /** * @ORM\Id * @ORM\Column(type="uuid", unique=true) * @Groups({ * "order:read", * "customer:read" * }) */ private Uuid $id; ``` I generate the UUid in the constructor, so no need of GeneratedValue strategy. Any solution ? Thanks
Author
Owner

@derrabus commented on GitHub (Dec 27, 2021):

Please use GitHub discussions for support requests. Only few people monitor comments on issues that have been closed for years.

@derrabus commented on GitHub (Dec 27, 2021): Please use GitHub discussions for support requests. Only few people monitor comments on issues that have been closed for years.
Author
Owner

@ghandhikus commented on GitHub (Oct 17, 2023):

This bug still persists in newer versions of Symfony, it should be fixed ngl. Sequence generation is a must even if the primary key is a UUID and you want to generate an ID on top of that.

@ghandhikus commented on GitHub (Oct 17, 2023): This bug still persists in newer versions of Symfony, it should be fixed ngl. Sequence generation is a must even if the primary key is a UUID and you want to generate an ID on top of that.
Author
Owner

@ghandhikus commented on GitHub (Oct 17, 2023):

Workaround

For anyone wondering how to solve this, use something similar, but write your own code for sequence generation in the migration, make sure you default it in the migration itself too.

CREATE SEQUENCE table_id_seq INCREMENT BY 1 MINVALUE 1 START 1;
#[Column(
    // It is important not to insert null but to let the database use its own default
    insertable: false,
    // Probably is optional if you default it in the migration
    options: ['default' => 'nextval(\'table_id_seq\'::regclass)']
)]
private int $id;

Edit: if you are using schema:update for your unit testing database then remove default or consider using slower migrations for it

@ghandhikus commented on GitHub (Oct 17, 2023): ### Workaround For anyone wondering how to solve this, use something similar, but write your own code for sequence generation in the migration, make sure you default it in the migration itself too. ```sql CREATE SEQUENCE table_id_seq INCREMENT BY 1 MINVALUE 1 START 1; ``` ```php #[Column( // It is important not to insert null but to let the database use its own default insertable: false, // Probably is optional if you default it in the migration options: ['default' => 'nextval(\'table_id_seq\'::regclass)'] )] private int $id; ``` Edit: if you are using schema:update for your unit testing database then remove default or consider using slower migrations for it
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5963