114 votes

Le EntityManager est fermé

[Doctrine\ORM\ORMException]   
L'EntityManager est fermé.  

Après avoir obtenu une exception DBAL lors de l'insertion de données, l'EntityManager se ferme et je ne parviens pas à le reconnecter.

J'ai essayé comme ceci mais je n'ai pas réussi à me connecter.

$this->em->close();
$this->set('doctrine.orm.entity_manager', null);
$this->set('doctrine.orm.default_entity_manager', null);
$this->get('doctrine')->resetEntityManager();
$this->em = $this->get('doctrine')->getEntityManager();

Quelqu'un a une idée de comment se reconnecter ?

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?

92voto

Gregsparrow Points 992

Ma solution.

Avant de faire quoi que ce soit, vérifiez :

if (!$this->entityManager->isOpen()) {
    $this->entityManager = $this->entityManager->create(
        $this->entityManager->getConnection(),
        $this->entityManager->getConfiguration()
    );
}

Toutes les entités seront sauvegardées. Mais c'est pratique pour une classe particulière ou certains cas. Si vous avez des services avec entitymanager injecté, il restera fermé.

0 votes

C'est bien mieux lorsque le conteneur DI lui-même n'est pas disponible. Merci.

2 votes

Vous voudrez peut-être également passer $this->entityManager->getEventManager() dans le 3ème paramètre.

1 votes

Note: $this->entityManager::create devrait être utilisé à la place, tout comme la déclaration de méthode : public static function create(...)

44voto

Francesco Casula Points 2508

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 :)

0 votes

Explenation fantastique. J'ai trouvé quelque chose de similaire et j'ai pensé que ce serait bien de contribuer à votre réponse. Merci beaucoup.

1 votes

En gros, chaque fois qu'il y a une exception est la clé ici. J'ai fait l'erreur de capturer et d'ignorer une exception sur flush(), donc l'EM était fermé et je n'en étais pas conscient. Je vais gérer les exceptions sur flush() différemment maintenant; merci beaucoup.

40voto

luisbg Points 434

Symfony 2.0:

$em = $this->getDoctrine()->resetEntityManager();

Symfony 2.1+:

$em = $this->getDoctrine()->resetManager();

6 votes

AVERTISSEMENT : resetEntityManager est obsolète depuis Symfony 2.1. Utilisez plutôt resetManager

0 votes

Est-ce que cela réinitialise également l'Unité de Travail ?

0 votes

@flu Étant donné que la classe EntityManager gère la classe UnitOfWork, je soupçonne que oui. Cependant, je n'ai pas testé cela donc je ne peux pas en être sûr.

29voto

Aldo Stracquadanio Points 2128

C'est un problème très délicat car, au moins pour Symfony 2.0 et Doctrine 2.1, il n'est en aucun cas possible de rouvrir l'EntityManager après sa fermeture.

La seule solution que j'ai trouvée pour surmonter ce problème est de créer votre propre classe de connexion DBAL, d'encapsuler celle de Doctrine et de fournir une gestion des exceptions (par exemple, en réesssayant plusieurs fois avant de renvoyer l'exception à l'EntityManager). C'est un peu bricoleur et je crains que cela puisse causer certaines incohérences dans les environnements transactionnels (c'est-à-dire je ne suis pas vraiment sûr de ce qui se passe si la requête qui échoue se trouve au milieu d'une transaction).

Un exemple de configuration pour suivre cette voie est le suivant :

doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        driver:   %database_driver%
        host:     %database_host%
        user:     %database_user%
        password: %database_password%
        charset:  %database_charset%
        wrapper_class: Your\DBAL\ReopeningConnectionWrapper

La classe devrait commencer plus ou moins comme ceci :

namespace Your\DBAL;

class ReopeningConnectionWrapper extends Doctrine\DBAL\Connection {
  // ...
}

Une chose très ennuyeuse est que vous devez remplacer chaque méthode de Connection en fournissant votre wrapper de gestion des exceptions. L'utilisation de fermetures peut soulager certaines difficultés là-bas.

17voto

JGrinon Points 1218

Vous pouvez réinitialiser votre EM ainsi

// réinitialiser le EM et tous les aias
$container = $this->container; 
$container->set('doctrine.orm.entity_manager', null);
$container->set('doctrine.orm.default_entity_manager', null);
// obtenir un nouveau EM
$em = $this->getDoctrine()->getManager();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X