103
votes

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.

7
Are you using doctrine to import the existing records? - rojoca
Eric, nevermind...I see what you are trying to do. You basically need a @GeneratedValue(strategy="ItDepends") :) - Wil Moore III
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
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
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

7 Answers

53
votes

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());

32
votes

Perhaps what doctrine changed but now right way is:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
7
votes

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)

5
votes

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();
    }
}
4
votes

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.

1
votes

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.

1
votes

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.