Définir explicitement l'ID avec Doctrine lors de l'utilisation de la stratégie «AUTO»

100

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 identifiants. Ensuite, lors de l'ajout de nouveaux enregistrements, je veux 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, ce qui suit est la solution préférée:

$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

Étant donné que Doctrine pivote hors de ClassMetaData pour déterminer la stratégie du générateur, elle doit être modifiée après avoir géré 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é étaient stockées avec cet ID, tandis que celles sans ID spécifié utilisaient le lastGeneratedId() + 1.

Eric
la source
Utilisez-vous la doctrine pour importer les documents existants?
rojoca
2
Eric, tant pis ... Je vois ce que tu essaies de faire. Vous avez essentiellement besoin d'un @GeneratedValue (strategy = "ItDepends") :)
Wil Moore III
1
Une chose à noter à ce sujet, c'est qu'il semble que les générateurs d'ID qui ne sont pas "isPostInsertGenerator" == true, auront déjà fonctionné. Vous pouvez modifier la valeur de l'ID après la persistance, mais vous perdrez un numéro de séquence.
gview
15
La nouvelle solution me permet maintenant de définir l'id dans un élément de doctrine. Cependant, en utilisant $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); permet de définir et d'enregistrer l'identifiant. (MySQL).
jmoz
2
Cette nouvelle solution ne fonctionne pas dans Symfony 3.0. Je devais utiliser$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Réponses:

51

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 une séquence.

Je dois ajouter cette ligne pour que cela fonctionne parfaitement:

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

Meilleures salutations,

nicolasbui
la source
Merci! La doctrine s'est un peu améliorée depuis qu'il s'agissait d'un problème, j'ai donc accepté votre réponse et mis à jour mon ticket d'origine en conséquence.
Eric
Merci et je suis heureux d'aider un peu que je peux :)
nicolasbui
2
cela définira-t-il ce générateur de façon permanente? Puis-je ajouter un enregistrement avec un ID forcé, puis le laisser utiliser des identifiants d'auto-incrémentation?
Pavel Dubinin
1
Je peux confirmer que cela fonctionne avec Symfony 3.2. Ce à quoi je ne m'attendais pas, cependant, c'est que le générateur doit être réglé après l' exécution $em->persist($entity).
bodo
29

Peut-être ce que la doctrine a changé, mais maintenant la bonne manière est:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Alexey B.
la source
1
Ce sont toujours des informations pertinentes et fonctionnent pour Doctrine 2.4.1, mais la deuxième ligne mentionnée par @gphilip doit être supprimée.
Mantas
Ne fonctionne pas pour Doctrine> 2.5 car ClassMetadata s'agit d'une interface et ne peut donc pas avoir de constantes.
TiMESPLiNTER
Il existe une classe ClassMetadata
Alexey B.
@gphilip La deuxième ligne est importante si vous voulez qu'elle fonctionne avec des associations .
Taz
1
Peut simplifier en utilisant $metadata::GENERATOR_TYPE_NONE
fyrye
7

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)

Chèvre-garou
la source
Je pense que le cas est qu'il vous suffit de spécifier l'entité racine. La metadatafactory vérifie l'héritage lors de la détermination de la stratégie d'identifiant.
Seth Battin
En fait, lorsque je l'ajoute uniquement à l'entité racine, cela fonctionne parfaitement. Quand je l'ajoute aux deux, j'obtiens des SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failserreurs. Downvote
ioleo
5

La nouvelle solution ne fonctionne correctement que lorsque TOUTES les entités ont un identifiant avant l'insertion. Lorsqu'une entité a un ID et une autre pas, 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();
    }
}
Андрей Миндубаев
la source
4

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 le confirmer pour MySQL, car je n'ai encore essayé aucun autre SGBD.

arête
la source
1

J'ai créé une 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. Cela devrait être une entrée facile pour les tests unitaires afin que du code comme celui-ci n'ait pas à être répété.

Villermen
la source
1

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

Vous ne devriez 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 replie sur le générateur initial lorsqu'une instance n'a pas d'identifiant attribué.

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

Tseho
la source