使用“ AUTO”策略时,显式地使用 Doctrine 设置 Id

我的实体使用这个注释来表示它的 ID:

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

从一个干净的数据库,我导入现有的记录从一个旧的数据库,并试图保持相同的 ID。然后,在添加新记录时,我希望 MySQL 像往常一样自动增加 ID 列。

不幸的是,Doctrine2似乎完全忽略了指定的 ID。


新的解决方案

根据以下建议,以下是可取的解决办法:

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

老办法

因为 Doctrine 离开 ClassMetaData 来确定生成器策略,所以在管理 EntityManager 中的实体之后必须对其进行修改:

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


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


$this->em->flush();

我只是在 MySQL 上测试了一下,结果和预期的一样,这意味着具有自定义 ID 的实体使用该 ID 存储,而那些没有指定 ID 的实体使用 lastGeneratedId() + 1

45275 次浏览

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

Perhaps what doctrine changed but now right way is:

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

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)

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

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.

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.

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.