Foi assim que resolvi a Doutrina "O EntityManager está fechado." questão. Basicamente, toda vez que houver uma exceção (ou seja, chave duplicada) ou o não fornecimento de dados para uma coluna obrigatória fará com que o Doctrine feche o Entity Manager. Se você ainda deseja interagir com o banco de dados, você deve redefinir o Entity Manager chamando o resetManager()
método mencionado por JGrinon .
No meu aplicativo eu estava executando vários consumidores RabbitMQ que estavam todos fazendo a mesma coisa: verificar se havia uma entidade no banco de dados, se sim devolvia, senão criava e depois devolvia. Nos poucos milissegundos entre verificar se essa entidade já existia e criá-la, outro consumidor fez o mesmo e criou a entidade ausente, fazendo com que o outro consumidor incorresse em uma exceção de chave duplicada ( condição de corrida ).
Isso levou a um problema de design de software. Basicamente, o que eu estava tentando fazer era criar todas as entidades em uma transação. Isso pode parecer natural para a maioria, mas definitivamente era conceitualmente errado no meu caso. Considere o seguinte problema: eu tive que armazenar uma entidade de jogo de futebol que tinha essas dependências.
- um grupo (por exemplo, Grupo A, Grupo B ...)
- uma rodada (por exemplo, semifinais ...)
- um local (ou seja, estádio onde a partida está acontecendo)
- um status de jogo (por exemplo, intervalo, tempo integral)
- as duas equipes jogando a partida
- o jogo em si
Agora, por que a criação do local deve estar na mesma transação da partida? Pode ser que acabei de receber um novo local que não está no meu banco de dados, então tenho que criá-lo primeiro. Mas também pode ser que aquele local hospede outro jogo, de modo que outro consumidor provavelmente tentará criá-lo ao mesmo tempo. Então, o que eu tive que fazer foi criar todas as dependências primeiro em transações separadas, certificando-me de que estava redefinindo o gerenciador de entidade em uma exceção de chave duplicada. Eu diria que todas as entidades ali ao lado da correspondência podem ser definidas como "compartilhadas" porque podem potencialmente fazer parte de outras transações em outros consumidores. Algo que não é "compartilhado" ali é a própria correspondência que provavelmente não será criada por dois consumidores ao mesmo tempo.
Tudo isso também levou a outro problema. Se você resetar o Entity Manager, todos os objetos que você recuperou antes de resetar são para o Doctrine totalmente novos. Portanto, o Doctrine não tentará executar um UPDATE neles, mas sim um INSERT ! Portanto, certifique-se de criar todas as suas dependências em transações logicamente corretas e, em seguida, recuperar todos os seus objetos do banco de dados antes de defini-los para a entidade de destino. Considere o seguinte código como exemplo:
$group = $this->createGroupIfDoesNotExist($groupData);
$match->setGroup($group);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
Então é assim que eu acho que deveria ser feito.
$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 ajude :)