C'est ainsi que j'ai résolu le problème Doctrine "L'entité Manager est fermée.". Essentiellement, chaque fois qu'il y a une exception (par exemple, une clé en double) ou qu'aucune donnée n'est fournie pour une colonne obligatoire, Doctrine fermera l'Entity Manager. Si vous souhaitez toujours interagir avec la base de données, vous devez réinitialiser l'Entity Manager en appelant la méthode resetManager()
comme mentionné par JGrinon.
Dans mon application, j'exécutais plusieurs consommateurs RabbitMQ qui faisaient tous la même chose : vérifier si une entité existait dans la base de données, la retourner si c'était le cas, la créer sinon, puis la retourner. Dans les quelques millisecondes entre la vérification de l'existence de cette entité et sa création, un autre consommateur a fait la même chose et a créé l'entité manquante, ce qui a entraîné une exception de clé en double (condition de course).
Cela a conduit à un problème de conception logicielle. Fondamentalement, ce que j'essayais de faire était de créer toutes les entités dans une seule transaction. Cela peut sembler naturel pour la plupart des gens, mais c'était conceptuellement incorrect dans mon cas. Considérez le problème suivant : je devais stocker une entité Match de football qui avait ces dépendances.
- un groupe (par exemple, Groupe A, Groupe B...)
- un tour (par exemple, demi-finales...)
- un lieu (c'est-à-dire le stade où se déroule le match)
- un statut de match (par exemple, mi-temps, fin de match)
- les deux équipes jouant le match
- le match lui-même
maintenant, pourquoi la création du lieu devrait-elle être dans la même transaction que le match ? Il se pourrait que je vienne de recevoir un nouveau lieu qui n'est pas dans ma base de données, donc je dois le créer en premier. Mais il se pourrait aussi que ce lieu puisse accueillir un autre match, donc un autre consommateur essaiera probablement également de le créer en même temps. Donc ce que j'ai dû faire était de créer d'abord toutes les dépendances dans des transactions séparées en m'assurant que je réinitialisais l'entity manager en cas d'exception de clé en double. Je dirais que toutes les entités là-bas, à l'exception du match, pourraient être définies comme "partagées" car elles pourraient éventuellement faire partie d'autres transactions dans d'autres consommateurs. Une chose qui n'est pas "partagée" là-dedans est le match lui-même qui ne sera probablement pas créé par deux consommateurs en même temps. Donc dans la dernière transaction, je m'attends à ne voir que le match et la relation entre les deux équipes et le match.
Tout cela a également entraîné un autre problème. Si vous réinitialisez l'Entity Manager, tous les objets que vous avez récupérés avant la réinitialisation sont totalement nouveaux pour Doctrine. Ainsi, Doctrine n'essaiera pas de les mettre à jour mais de les insérer ! Assurez-vous donc de créer toutes vos dépendances dans des transactions logiquement correctes, puis de récupérer tous vos objets de la base de données avant de les attribuer à l'entité cible. Considérez le code suivant comme exemple :
$group = $this->createGroupIfDoesNotExist($groupData);
$match->setGroup($group); // ce n'est PAS correct!
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
/**
* Si la création du lieu génère une exception de clé en double
* nous sommes obligés de réinitialiser l'Entity Manager afin de procéder
* à la création du tour et nous perdrons la référence du groupe.
* Cela signifie que Doctrine essaiera de persister le groupe comme nouveau
* même s'il est déjà présent dans la base de données.
*/
Voilà comment je pense que cela devrait être fait.
$group = $this->createGroupIfDoesNotExist($groupData); // première transaction, réinitialiser en cas de duplication
$venue = $this->createVenueIfDoesNotExist($venueData); // deuxième transaction, réinitialiser en cas de duplication
$round = $this->createRoundIfDoesNotExist($roundData); // troisième transaction, réinitialiser en cas de duplication
// nous récupérons toutes les entités directement de la base de données
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);
// nous les définissons enfin maintenant qu'aucune exception ne va se produire
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);
// relation entre le match et les équipes...
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);
$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);
$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);
// dernière transaction!
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();
J'espère que cela aide :)
1 votes
Pourquoi l'entityManager se ferme-t-il?
3 votes
@JaySheth Le gestionnaire d'entités pourrait se fermer après une exception DBAL, ou si vous appelez EntityManager->clear() avant un flush. J'ai vu certaines personnes utiliser des exceptions DBAL pour bifurquer le flux d'exécution, ce qui entraîne souvent une erreur de fermeture du gestionnaire d'entités. Si vous rencontrez cette erreur, il y a probablement un problème dans le flux d'exécution de votre programme.
7 votes
@AlanChavez - Je reçois cette erreur parce que j'utilise Doctrine pour écrire un drapeau sémaphore dans une table qui est accédée par plusieurs threads simultanément. MySQL signalera une erreur à l'une des deux threads concurrents essayant de créer le sémaphore, car la contrainte de clé signifie que seulement l'un d'eux peut réussir. À mon avis, il y a une faille dans Doctrine qui ne vous permet pas de gérer en toute sécurité les erreurs MySQL attendues. Pourquoi la connexion MySQL entière devrait-elle être déconnectée parce qu'une instruction INSERT a un conflit?
3 votes
Vous verrez également cette erreur si vous essayez de journaliser les exceptions dans une base de données dans le
app.exception_listener
mais que l'exception (comme une violation de contrainte) a fermé la connexion.