Definir explicitamente o Id com o Doctrine ao usar a estratégia “AUTO”

100

Minha entidade usa esta anotação para seu ID:

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

De um banco de dados limpo, estou importando registros existentes de um banco de dados mais antigo e tentando manter os mesmos IDs. Então, ao adicionar novos registros, quero que o MySQL incremente automaticamente a coluna ID como de costume.

Infelizmente, parece que o Doctrine2 ignora completamente o ID especificado.


Nova Solução

De acordo com as recomendações abaixo, o seguinte é a solução preferida:

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

Solução Antiga

Como o Doctrine se baseia no ClassMetaData para determinar a estratégia do gerador, ele deve ser modificado após o gerenciamento da entidade no EntityManager:

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

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

$this->em->flush();

Acabei de testar isso no MySQL e funcionou conforme o esperado, o que significa que entidades com um ID personalizado foram armazenados com esse ID, enquanto aquelas sem um ID especificado usaram o lastGeneratedId() + 1.

Eric
fonte
Você está usando a doutrina para importar os registros existentes?
rojoca
2
Eric, esquece ... Vejo o que você está tentando fazer. Você basicamente precisa de um @GeneratedValue (strategy = "ItDepends") :)
Wil Moore III
1
Uma coisa a se notar sobre isso é que parece que os geradores de Id que não são "isPostInsertGenerator" == true já terão sido executados. Você pode alterar o valor do ID após a persistência, no entanto, você perderá um número de sequência.
gview
15
A nova solução agora me permite definir o id em um dispositivo de doutrina. No entanto, usando $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); permite que o id seja definido e salvo. (MySQL).
jmoz
2
Essa nova solução não funciona no Symfony 3.0. Tive que usar$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Respostas:

51

Embora sua solução funcione bem com o MySQL, não consegui fazê-la funcionar com o PostgreSQL porque é baseado em sequência.

Tenho que adicionar esta linha para que funcione perfeitamente:

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

Cumprimentos,

Nicolasbui
fonte
Obrigado! O Doctrine melhorou um pouco desde que isso foi um problema, então aceitei sua resposta e atualizei meu tíquete original de acordo.
Eric
Obrigado e fico feliz em ajudar um pouco como posso :)
nicolasbui
2
isso definirá este gerador permanentemente? Posso adicionar um registro com ID forçado e, em seguida, deixá-lo usar IDs de incremento automático?
Pavel Dubinin
1
Posso confirmar que isso funciona com Symfony 3.2. O que eu não esperava, entretanto, era que o gerador tivesse que ser configurado após a execução $em->persist($entity).
bodo
29

Talvez o que a doutrina mudou, mas agora o caminho certo é:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Alexey B.
fonte
1
Esta ainda é uma informação relevante e funciona para o Doctrine 2.4.1, mas a segunda linha mencionada por @gphilip deve ser removida.
Mantas
Não funciona para Doctrine> 2.5 porque ClassMetadataé uma interface e portanto não pode ter nenhuma constante.
TiMESPLiNTER
Há uma classe ClassMetadata
Alexey B.
@gphilip A segunda linha é importante se você quiser que funcione com associações .
Taz de
1
Pode simplificar usando$metadata::GENERATOR_TYPE_NONE
fyrye de
7

No caso da entidade fazer parte de uma herança de tabela de classe, você precisa alterar o gerador de id nos metadados de classe para ambas as entidades (a entidade que você está persistindo e a entidade raiz)

Weregoat
fonte
Acho que o caso é que você só precisa especificar a entidade raiz. O metadatafactory verifica a herança ao determinar a estratégia de id.
Seth Battin
Na verdade, quando eu o adiciono apenas à entidade raiz, ele funciona perfeitamente. Quando eu adiciono a ambos, recebo SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failserros. Downvoted
ioleo
5

A nova solução funciona bem apenas quando TODAS as entidades têm id antes da inserção. Quando uma entidade tem ID e outra não - a nova solução está falhando.

Eu uso esta função para importar todos os meus dados:

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();
    }
}
Андрей Миндубаев
fonte
4

Solução para Doctrine 2.5 e MySQL

A "Nova solução" não funciona com Doctrine 2.5 e MySQL. Você tem que usar:

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

No entanto, só posso confirmar isso para o MySQL, porque ainda não experimentei nenhum outro DBMS.

espinha de peixe
fonte
1

Eu criei uma biblioteca para definir IDs futuros para entidades Doctrine. Ele reverte para a estratégia de geração de ID original quando todos os IDs enfileirados são consumidos para minimizar o impacto. Deve ser uma entrada fácil para testes de unidade, para que um código como este não precise ser repetido.

Villermen
fonte
1

Inspirado no trabalho de Villermen , criei a biblioteca tseho / doctrine -assign-identity que permite atribuir manualmente IDs a uma entidade Doctrine, mesmo quando a entidade usa as estratégias AUTO, SEQUENCE, IDENTITY ou UUID.

Você nunca deve usá-lo na produção, mas é muito útil para testes funcionais.

A biblioteca detectará automaticamente as entidades com um id atribuído e substituirá o gerador apenas quando necessário. A biblioteca terá um fallback no gerador inicial quando uma instância não tiver um ID atribuído.

A substituição do gerador ocorre em um Doctrine EventListener, sem necessidade de adicionar qualquer código adicional em seus acessórios.

Tseho
fonte