web-dev-qa-db-fra.com

EntityManager est fermé

[Doctrine\ORM\ORMException]   
The EntityManager is closed.  

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

J'ai essayé comme ça mais ça n'a pas eu de connexion.

$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 comment se reconnecter?

58
Ueli

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

Le seul moyen que j’ai trouvé pour résoudre ce problème est de créer votre propre classe de connexion DBAL, d’envelopper la classe Doctrine et de gérer les exceptions (par exemple, plusieurs tentatives avant de renvoyer l’exception au EntityManager). C’est un peu hacky et je crains que cela puisse causer des incohérences dans les environnements transactionnels (c’est-à-dire que je ne suis pas vraiment sûr de ce qui se passe si la requête défaillante est au milieu d’une transaction).

Voici un exemple de configuration à suivre:

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 connexion en fournissant votre wrapper de gestion des exceptions. L'utilisation de fermetures peut soulager certaines douleurs.

23
Aldo Stracquadanio

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 enregistrées. Mais c'est pratique pour une classe particulière ou certains cas. Si vous avez des services avec entitymanager injecté, il reste fermé.

53
Gregsparrow

Symfony 2.0 :

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

Symfony 2.1+ :

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

Vous pouvez réinitialiser votre EM afin

// reset the EM and all aias
$container = $this->container;
$container->set('doctrine.orm.entity_manager', null);
$container->set('doctrine.orm.default_entity_manager', null);
// get a fresh EM
$em = $this->getDoctrine()->getManager();
17
JGrinon

C’est ainsi que j’ai résolu la doctrine "Le EntityManager est fermé." issue . En gros, chaque fois qu’il ya une exception (par exemple, une clé en double), Doctrine ferme le gestionnaire d’entités. Si vous souhaitez toujours interagir avec la base de données, vous devez réinitialiser le gestionnaire d'entité en appelant la méthode resetManager() comme indiqué par JGrinon.

Dans mon application, j'utilisais plusieurs consommateurs RabbitMQ qui faisaient tous la même chose: vérifier si une entité était présente dans la base de données, si oui la renvoyer, sinon la créer puis la renvoyer . En quelques millisecondes entre les vérifications si cette entité existait déjà et en le créant, un autre consommateur a fait de même et a créé l'entité manquante, ce qui a pour conséquence que l'autre consommateur s'expose à une exception de clé dupliquée.

Cela a conduit à un problème de conception de logiciel. Essentiellement, j’essayais de créer toutes les entités en une transaction. Cela peut sembler naturel à la plupart des gens, mais mon concept était définitivement faux. Considérez le problème suivant: je devais stocker une entité de match de football qui avait ces dépendances.

  • un groupe (c'est-à-dire groupe A, groupe B ...)
  • un tour (c'est-à-dire demi-finales)
  • un lieu (c'est-à-dire un stade où se déroule le match)
  • un statut de match (c'est-à-dire mi-temps, plein temps)
  • les deux équipes jouant le match
  • le match lui-même

Maintenant, pourquoi la création de site devrait-elle être dans la même transaction que le match? Il se peut que je vienne de recevoir un nouveau lieu qui ne figure pas dans ma base de données. Je dois donc d'abord le créer. Mais il se peut également que ce lieu puisse organiser un autre match afin qu'un autre consommateur essaie probablement de le créer également au même moment. Je devais donc d'abord créer toutes les dépendances dans des transactions distinctes en m'assurant de réinitialiser le gestionnaire d'entités avec une exception de clé dupliquée. Je dirais que toutes les entités à côté de la correspondance pourraient être définies comme "partagées" car elles pourraient potentiellement faire partie d'autres transactions chez d'autres consommateurs. Quelque chose qui n'est pas "partagé" dans ce jeu est la correspondance elle-même qui ne sera probablement pas créée par deux consommateurs en même temps. Donc, lors de 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 conduit à un autre problème. Si vous réinitialisez Entity Manager, tous les objets que vous avez récupérés avant la réinitialisation sont totalement nouveaux pour Doctrine. Donc, Doctrine n'essaiera pas d'exécuter UPDATE sur eux mais un INSÉRER! Assurez-vous donc de créer toutes vos dépendances dans des transactions logiquement correctes, puis récupérez tous vos objets dans la base de données avant de les définir sur l'entité cible. Considérez le code suivant comme exemple:

$group = $this->createGroupIfDoesNotExist($groupData);

$match->setGroup($group); // this is NOT OK!

$venue = $this->createVenueIfDoesNotExist($venueData);

$round = $this->createRoundIfDoesNotExist($roundData);

/**
 * If the venue creation generates a duplicate key exception
 * we are forced to reset the entity manager in order to proceed
 * with the round creation and so we'll loose the group reference.
 * Meaning that Doctrine will try to persist the group as new even
 * if it's already there in the database.
 */

Donc, voici comment je pense que cela devrait être fait.

$group = $this->createGroupIfDoesNotExist($groupData); // first transaction, reset if duplicated
$venue = $this->createVenueIfDoesNotExist($venueData); // second transaction, reset if duplicated
$round = $this->createRoundIfDoesNotExist($roundData); // third transaction, reset if duplicated

// we fetch all the entities back directly from the database
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);

// we finally set them now that no exceptions are going to happen
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);

// match and teams relation...
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);

$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);

$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);

// last transaction!
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();

J'espère que ça aide :)

17
Francesco Casula

Dans le contrôleur.

L'exception ferme le gestionnaire d'entités. Cela crée des problèmes pour l'insertion en vrac. Pour continuer, il faut le redéfinir.

/** 
* @var  \Doctrine\ORM\EntityManager
*/
$em = $this->getDoctrine()->getManager();

foreach($to_insert AS $data)
{
    if(!$em->isOpen())
    {
        $this->getDoctrine()->resetManager();
        $em = $this->getDoctrine()->getManager();
    }

  $entity = new \Entity();
  $entity->setUniqueNumber($data['number']);
  $em->persist($entity);

  try
  {
    $em->flush();
    $counter++;
  }
  catch(\Doctrine\DBAL\DBALException $e)
  {
    if($e->getPrevious()->getCode() != '23000')
    {   
      /**
      * if its not the error code for a duplicate key 
      * value then rethrow the exception
      */
      throw $e;
    }
    else
    {
      $duplication++;
    }               
  }                      
}
4
Vadim

Dans Symfony 4.2 + vous devez utiliser le paquet:

composer require symfony/proxy-manager-bridge

sinon, vous obtenez l'exception:

Resetting a non-lazy manager service is not supported. Declare the "doctrine.orm.default_entity_manager" service as lazy.  

Alors vous pouvez réinitialiser le entityManager comme ceci:

services.yaml:

App\Foo:
    - '@doctrine.orm.entity_manager'
    - '@doctrine'

Foo.php:

use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;


 try {
    $this->entityManager->persist($entity);
    $this->entityManager->flush();
} catch (DBALException $e) {
    if (!$this->entityManager->isOpen()) {
        $this->entityManager = $this->doctrine->resetManager();
    }
}
3
Sebastian Viereck

Pour ma part, j’ai trouvé que ce problème se produisait dans une commande d’importation par lots car une boucle try/catch capturait une erreur SQL (avec em->flush()) pour laquelle je n’avais rien fait. Dans mon cas, c’est parce que j’essayais d’insérer un enregistrement avec une propriété non nullable laissée à null.

En règle générale, cela provoquait une exception critique et l'arrêt de la commande ou du contrôleur, mais je ne faisais que consigner ce problème et poursuivre. L'erreur SQL avait entraîné la fermeture du gestionnaire d'entités.

Vérifiez dans votre fichier dev.log toute erreur stupide SQL comme celle-ci, car cela pourrait être de votre faute. :)

1
Adambean

Essayez d'utiliser:

$em->getConnection()->[setNestTransactionsWithSavepoints][1](true);

avant de commencer une transaction.

Sur la méthode Connection::rollback, il vérifie la propriété nestTransactionsWithSavepoints .

1
zechim

C’est vraiment un problème ancien, mais j’ai eu juste le même problème. Je faisais quelque chose comme ça:

// entity
$entityOne = $this->em->find(Parent::class, 1);

// do something on other entites (SomeEntityClass)
$this->em->persist($entity);
$this->em->flush();
$this->em->clear();

// and at end I was trying to save changes to first one by
$this->em->persist($entityOne);
$this->em->flush();
$this->em->clear();

Le problème était que clairement supprimer toutes les entités, y compris la première et jeter une erreur Le EntityManager est fermé.

Dans mon cas solution je devais simplement clarifier le type d’entité et laisser $entityOne toujours sous EM:

$this->em->clear(SomeEntityClass::class);
0
Nikola Loncar

Symfony v4.1.6

Doctrine v2.9.0

Processus d'insertion de doublons dans un référentiel

  1. Accédez à un registre dans votre dépôt

//begin of repo

/** @var RegistryInterface */
protected $registry;

public function __construct(RegistryInterface $registry)
{
    $this->registry = $registry;
    parent::__construct($registry, YourEntity::class);
}

</ pre> </ code>

  1. Enveloppez le code risqué dans la transaction et le responsable reposé en cas d'exception

//in repo method
$em = $this->getEntityManager();

$em->beginTransaction();
try {
    $em->persist($yourEntityThatCanBeDuplicate);
    $em->flush();
    $em->commit();

} catch (\Throwable $e) {
    //Rollback all nested transactions
    while ($em->getConnection()->getTransactionNestingLevel() > 0) {
        $em->rollback();
    }

    //Reset the default em
    if (!$em->isOpen()) {
        $this->registry->resetManager();
    }
}

</ pre> </ code>

0

J'ai rencontré le même problème lors du test des modifications dans Symfony 4.3.2

J'ai abaissé le niveau de log à INFO

Et a refait le test

Et le journal a montré ceci:

console.ERROR: Error thrown while running command "doctrine:schema:create". Message: "[Semantical Error] The annotation "@ORM\Id" in property App\Entity\Common::$id was never imported. Did you maybe forget to add a "use" statement for this annotation?" {"exception":"[object] (Doctrine\\Common\\Annotations\\AnnotationException(code: 0): [Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation? at C:\\xampp\\htdocs\\dirty7s\\vendor\\doctrine\\annotations\\lib\\Doctrine\\Common\\Annotations\\AnnotationException.php:54)","command":"doctrine:schema:create","message":"[Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation?"} []

Cela signifie qu'une erreur dans le code provoque le:

Doctrine\ORM\ORMException: The EntityManager is closed.

C'est donc une bonne idée de consulter le journal

0
Babak Bandpey

J'ai eu ce problème. C'est comme ça que je l'ai réparé.

La connexion semble se fermer lorsque vous essayez de vider ou de persister. Essayer de le rouvrir est un mauvais choix car cela crée de nouveaux problèmes. J'ai essayé de comprendre pourquoi la connexion était fermée et j'ai constaté que je faisais trop de modifications avant la persistance. 

persist () a précédemment résolu le problème.

0
user3046563

J'ai trouvé un article intéressant sur ce problème

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

Doctrine 2 Exception EntityManager est fermé

0
stephan.mada