116

My entity uses this annotation for it's ID:

/**
 * @orm:Id
 * @orm:Column(type="integer")
 * @orm:GeneratedValue(strategy="AUTO")
 */
protected $id;

From a clean database, I'm importing in existing records from an older database and trying to keep the same IDs. Then, when adding new records, I want MySQL to auto-increment the ID column as usual.

Unfortunately, it appears Doctrine2 completely ignores the specified ID.


New Solution

Per recommendations below, the following is the preferred solution:

$this->em->persist($entity);

$metadata = $this->em->getClassMetaData(get_class($entity));
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
$metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());

Old Solution

Because Doctrine pivots off of the ClassMetaData for determining the generator strategy, it has to be modified after managing the entity in the EntityManager:

$this->em->persist($entity);

$metadata = $this->em->getClassMetaData(get_class($entity));
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);

$this->em->flush();

I just tested this on MySQL and it worked as expected, meaning Entities with a custom ID were stored with that ID, while those without an ID specified used the lastGeneratedId() + 1.

Nickolaus
  • 4,785
  • 4
  • 38
  • 60
Eric
  • 2,037
  • 2
  • 16
  • 14
  • Are you using doctrine to import the existing records? – rojoca Mar 14 '11 at 17:35
  • No, unfortunately the data is being pulled in via an external API as JSON, then mapped to Entity classes. – Eric Mar 14 '11 at 19:32
  • Eric, does this not work @GeneratedValue(strategy="NONE")? – Wil Moore III Mar 14 '11 at 20:33
  • 2
    Eric, nevermind...I see what you are trying to do. You basically need a @GeneratedValue(strategy="ItDepends") :) – Wil Moore III Mar 14 '11 at 20:35
  • 1
    One thing to note about this, is that it seems that Id generators that are not "isPostInsertGenerator" == true, will have already run. You can change the value of the ID after the persist, however, you'll lose a sequence number. – gview Apr 11 '12 at 20:26
  • 16
    The new solution does now allow me to set the id in a doctrine fixture. However, using $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE); allows the id to be set and saved. (MySQL). – jmoz Dec 18 '12 at 15:27
  • The new solution did not work for me with MySQL. I had to get it working with your old solution above. Thanks. – addex03 Jun 05 '13 at 16:30
  • If you are explicitly writing the entity class name string, do not include a leading slash. Full horror story here: http://stackoverflow.com/questions/24454899/doctrine2-unable-to-override-generated-value-strategy?lq=1 – Seth Battin Jul 21 '14 at 03:41
  • New solution is not working in 2.3 – nvvetal Feb 16 '15 at 14:18
  • Please note that the `id` in the entity cannot be manually set to `0`. You have to use greater number. When 0 is specified the generator will still use AUTO strategy. Tested in doctrine 2.4 – Szymon Sadło Feb 02 '16 at 09:40
  • 2
    That new solution does not work in Symfony 3.0. I had to use `$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);` – piotrekkr Mar 10 '16 at 21:44
  • @piotrekkr thanks for the tip, that worked for me also on Symfony 3.3 – Graph Jul 24 '17 at 15:35
  • To make this work I had to update the generator _before_ calling $this->em->persist() – Craige Oct 05 '17 at 19:25

7 Answers7

56

Although your solution work fine with MySQL, I failed to make it work with PostgreSQL as It's sequence based.

I've to add this line to make it work perfectly :

$metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());

Enlico
  • 23,259
  • 6
  • 48
  • 102
nicolasbui
  • 620
  • 7
  • 5
  • Thanks! Doctrine has improved a bit since this was first an issue, so I've accepted your answer & updated my original ticket accordingly. – Eric Aug 23 '12 at 03:44
  • Thank you and I'm happy to help a bit as I can :) – nicolasbui Dec 05 '12 at 18:20
  • 3
    will this set this generator permanently? Can I add one record with forced ID and then let it use autoincrement ids? – Pavel Dubinin Jun 25 '14 at 05:38
  • 1
    I can confirm that this works with Symfony 3.2. What I did not expect however, was that the generator has to be set _after_ executing `$em->persist($entity)`. – bodo Apr 06 '17 at 16:30
32

Perhaps what doctrine changed but now right way is:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
lxg
  • 12,375
  • 12
  • 51
  • 73
Alexey B.
  • 11,965
  • 2
  • 49
  • 73
  • 1
    This is still relevant information and works for Doctrine 2.4.1, but the second line as mentioned by @gphilip should be removed. – Mantas Dec 10 '13 at 21:22
  • Does not work for Doctrine >2.5 because `ClassMetadata` is an interface and therefor can not have any constants. – TiMESPLiNTER May 14 '15 at 21:33
  • There is a class [ClassMetadata](https://github.com/doctrine/doctrine2/blob/v2.5.0/lib/Doctrine/ORM/Mapping/ClassMetadata.php) – Alexey B. May 15 '15 at 07:58
  • @gphilip The second line is important if you want it to [work with associations](http://stackoverflow.com/q/31594338/709626). – Taz Aug 09 '15 at 12:25
  • 1
    Can simplify by using `$metadata::GENERATOR_TYPE_NONE` – Will B. Jun 07 '16 at 17:33
7

In case the entity is part of a class table inheritance you need to change the id-generator in the class metadata for both entities (the entity you are persisting and the root entity)

Weregoat
  • 79
  • 1
  • 1
  • I think the case is that you only need to specify the root entity. The metadatafactory checks inheritance when determining the id strategy. – Seth Battin Jul 21 '14 at 03:38
  • In fact, when I add it only to root entity it works flawlessly. When I add it to both, I'm getting `SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails` errors. Downvoted – ioleo Sep 13 '14 at 11:53
6

New solution works fine only when ALL entities have id before insert. When one entity has ID and another one does not - new solution is failing.

I use this function for import all my data:

function createEntity(\Doctrine\ORM\EntityManager $em, $entity, $id = null)
{
    $className = get_class($entity);
    if ($id) {
        $idRef = new \ReflectionProperty($className, "id");
        $idRef->setAccessible(true);
        $idRef->setValue($entity, $id);

        $metadata = $em->getClassMetadata($className);
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */
        $generator = $metadata->idGenerator;
        $generatorType = $metadata->generatorType;

        $metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
        $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);

        $unitOfWork = $em->getUnitOfWork();
        $persistersRef = new \ReflectionProperty($unitOfWork, "persisters");
        $persistersRef->setAccessible(true);
        $persisters = $persistersRef->getValue($unitOfWork);
        unset($persisters[$className]);
        $persistersRef->setValue($unitOfWork, $persisters);

        $em->persist($entity);
        $em->flush();

        $idRef->setAccessible(false);
        $metadata->setIdGenerator($generator);
        $metadata->setIdGeneratorType($generatorType);

        $persisters = $persistersRef->getValue($unitOfWork);
        unset($persisters[$className]);
        $persistersRef->setValue($unitOfWork, $persisters);
        $persistersRef->setAccessible(false);
    } else {
        $em->persist($entity);
        $em->flush();
    }
}
Maximilian Ast
  • 3,369
  • 12
  • 36
  • 47
  • thanks sire for this helpful snippet (I used it in my fixtures that should use the same than a production table Ids) – Adrien G Feb 01 '23 at 13:14
4

Solution for Doctrine 2.5 and MySQL

The "New solution" doesn't work with Doctrine 2.5 and MySQL. You have to use:

$metadata = $this->getEntityManager()->getClassMetaData(Entity::class);
$metadata->setIdGenerator(new AssignedGenerator());
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_‌​NONE);

However I can only confirm that for MySQL,because I haven't tried any other DBMS yet.

fishbone
  • 3,140
  • 2
  • 37
  • 50
  • Use section: use Doctrine\ORM\Id\AssignedGenerator; use Doctrine\ORM\Mapping\ClassMetadata; and please recheck ClassMetadata::GENERATOR_TYPE_‌​NONE‌​, because in IDE is showed strange symbols between TYPE_‌ and NONE – Oleh Diachenko Aug 03 '22 at 07:24
1

I have created a library to set future IDs for Doctrine entities. It reverts to the original ID generation strategy when all queued IDs are consumed to minimize impact. It should be an easy drop-in for unit tests so that code like this doesn't have to be repeated.

Villermen
  • 815
  • 2
  • 13
  • 28
1

Inspired by Villermen work, I created the library tseho/doctrine-assigned-identity which allows you to manually assign IDs to a Doctrine entity, even when the entity uses the stategies AUTO, SEQUENCE, IDENTITY or UUID.

You should never use it in production but it's really useful for functional tests.

The library will detect automatically the entities with an assigned id and replace the generator only when needed. The library will fallback on the initial generator when an instance does not have an assigned id.

The replacement of the generator occurs in a Doctrine EventListener, no need to add any additional code in your fixtures.

Tseho
  • 306
  • 2
  • 5