Así es como resolví la Doctrina "El EntityManager está cerrado". problema. Básicamente, cada vez que hay una excepción (es decir, una clave duplicada) o no proporcionar datos para una columna obligatoria, Doctrine cerrará Entity Manager. Si aún desea interactuar con la base de datos, debe restablecer el AdministradorresetManager()
de entidades llamando al método mencionado por JGrinon .
En mi aplicación, estaba ejecutando varios consumidores RabbitMQ que estaban haciendo lo mismo: verificar si una entidad estaba allí en la base de datos, si es así, devolverla, si no, crearla y luego devolverla. En los pocos milisegundos entre verificar si esa entidad ya existía y crearla, otro consumidor hizo lo mismo y creó la entidad faltante, lo que hizo que el otro consumidor incurriera en una excepción de clave duplicada ( condición de carrera ).
Esto llevó a un problema de diseño de software. Básicamente, lo que estaba tratando de hacer era crear todas las entidades en una transacción. Esto puede parecer natural para la mayoría, pero definitivamente fue conceptualmente incorrecto en mi caso. Considere el siguiente problema: tuve que almacenar una entidad de partido de fútbol que tenía estas dependencias.
- un grupo (por ejemplo, Grupo A, Grupo B ...)
- una ronda (por ejemplo, semifinales ...)
- un lugar (es decir, el estadio donde se lleva a cabo el partido)
- un estado de partido (por ejemplo, medio tiempo, tiempo completo)
- los dos equipos que juegan el partido
- el partido en sí
Ahora bien, ¿por qué la creación del lugar debe realizarse en la misma transacción que el partido? Podría ser que acabo de recibir un nuevo lugar que no está en mi base de datos, así que primero tengo que crearlo. Pero también podría ser que ese lugar pueda albergar otro partido, por lo que es probable que otro consumidor intente crearlo al mismo tiempo. Entonces, lo que tuve que hacer fue crear todas las dependencias primero en transacciones separadas asegurándome de restablecer el administrador de entidades en una excepción de clave duplicada. Yo diría que todas las entidades que se encuentran al lado de la coincidencia podrían definirse como "compartidas" porque podrían ser parte de otras transacciones en otros consumidores. Algo que no se "comparte" allí es la coincidencia en sí que probablemente no será creada por dos consumidores al mismo tiempo.
Todo esto también llevó a otro problema. Si restableces Entity Manager, todos los objetos que has recuperado antes de restablecer son para Doctrine totalmente nuevos. ¡Entonces Doctrine no intentará ejecutar una ACTUALIZACIÓN en ellos, sino un INSERT ! Así que asegúrese de crear todas sus dependencias en transacciones lógicamente correctas y luego recupere todos sus objetos de la base de datos antes de configurarlos en la entidad de destino. Considere el siguiente código como ejemplo:
$group = $this->createGroupIfDoesNotExist($groupData);
$match->setGroup($group);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
Así es como creo que debería hacerse.
$group = $this->createGroupIfDoesNotExist($groupData);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);
$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);
$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();
Espero que ayude :)