web-dev-qa-db-fra.com

Définissez explicitement Id avec Doctrine lors de l'utilisation de la stratégie "AUTO"

Mon entité utilise cette annotation pour son ID:

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

À partir d'une base de données propre, j'importe des enregistrements existants d'une base de données plus ancienne et j'essaie de conserver les mêmes ID. Ensuite, lors de l'ajout de nouveaux enregistrements, je souhaite que MySQL incrémente automatiquement la colonne ID comme d'habitude.

Malheureusement, il semble que Doctrine2 ignore complètement l'ID spécifié.


Nouvelle solution

Selon les recommandations ci-dessous, la solution préférée est la suivante:

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

Ancienne solution

Parce que Doctrine pivote hors de ClassMetaData pour déterminer la stratégie du générateur, il doit être modifié après la gestion de l'entité dans EntityManager:

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

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

$this->em->flush();

Je viens de tester cela sur MySQL et cela a fonctionné comme prévu, ce qui signifie que les entités avec un ID personnalisé ont été stockées avec cet ID, tandis que celles sans ID spécifié ont utilisé la lastGeneratedId() + 1.

92
Eric

Bien que votre solution fonctionne correctement avec MySQL, je n'ai pas réussi à la faire fonctionner avec PostgreSQL car elle est basée sur la séquence.

Je dois ajouter cette ligne pour qu'elle fonctionne parfaitement:

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

Meilleures salutations,

47
nicolasbui

Peut-être que ce qui doctrine a changé mais maintenant la bonne façon est:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
28
Alexey B.

Dans le cas où l'entité fait partie d'un héritage de table de classe vous devez changer le générateur d'id dans les métadonnées de classe pour les deux entités (l'entité que vous persistez et l'entité racine)

7
Weregoat

La nouvelle solution fonctionne correctement uniquement lorsque TOUTES les entités ont un identifiant avant l'insertion. Lorsqu'une entité a un ID et pas une autre - la nouvelle solution échoue.

J'utilise cette fonction pour importer toutes mes données:

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 pour Doctrine 2.5 et MySQL

La "Nouvelle solution" ne fonctionne pas avec Doctrine 2.5 et MySQL. Vous devez utiliser:

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

Cependant, je ne peux que confirmer cela pour MySQL, car je n'ai encore essayé aucun autre SGBD.

3
fishbone

J'ai créé ne bibliothèque pour définir les futurs ID des entités Doctrine. Il revient à la stratégie de génération d'ID d'origine lorsque tous les ID en file d'attente sont consommés pour minimiser l'impact. Il doit être un accès facile pour les tests unitaires afin que le code comme celui-ci ne doive pas être répété.

1
Villermen

Inspiré par le travail de Villermen , j'ai créé la bibliothèque tseho/doctrine-assigned-identity qui vous permet d'affecter manuellement des ID à une entité Doctrine , même lorsque l'entité utilise les stratégies AUTO, SEQUENCE, IDENTITY ou UUID.

Vous ne devez jamais l'utiliser en production mais c'est vraiment utile pour les tests fonctionnels.

La bibliothèque détectera automatiquement les entités avec un identifiant attribué et ne remplacera le générateur qu'en cas de besoin. La bibliothèque se repliera sur le générateur initial lorsqu'une instance n'a pas d'ID attribué.

Le remplacement du générateur se produit dans un Doctrine EventListener, pas besoin d'ajouter de code supplémentaire dans vos fixtures.

1
Tseho