Ecco come ho risolto la dottrina "EntityManager è chiuso". problema. Fondamentalmente ogni volta che c'è un'eccezione (cioè chiave duplicata) o non fornire dati per una colonna obbligatoria farà sì che Doctrine chiuda Entity Manager. Se vuoi comunque interagire con il database devi reimpostare l'Entity Manger chiamando il resetManager()
metodo menzionato da JGrinon .
Nella mia applicazione stavo eseguendo più consumatori RabbitMQ che stavano facendo tutti la stessa cosa: controllare se un'entità era presente nel database, in caso affermativo restituirla, in caso contrario crearla e quindi restituirla. Nei pochi millisecondi tra il controllo se quell'entità esisteva già e la sua creazione, un altro consumatore ha fatto lo stesso e ha creato l'entità mancante facendo incorrere l'altro consumatore in un'eccezione di chiave duplicata ( race condition ).
Ciò ha portato a un problema di progettazione del software. Fondamentalmente quello che stavo cercando di fare era creare tutte le entità in una transazione. Questo può sembrare naturale per la maggior parte, ma nel mio caso era decisamente sbagliato concettualmente. Considera il seguente problema: dovevo archiviare un'entità Partita di calcio che aveva queste dipendenze.
- un gruppo (es. gruppo A, gruppo B ...)
- un round (es. semifinali ...)
- un luogo (cioè lo stadio in cui si svolge la partita)
- uno stato della partita (ad esempio metà tempo, tempo pieno)
- le due squadre che giocano la partita
- la partita stessa
Ora, perché la creazione del luogo dovrebbe essere nella stessa transazione della partita? Potrebbe essere che ho appena ricevuto un nuovo locale che non è nel mio database, quindi devo prima crearlo. Ma potrebbe anche essere che quel luogo possa ospitare un'altra partita, quindi un altro consumatore probabilmente proverà a crearlo allo stesso tempo. Quindi quello che dovevo fare era creare prima tutte le dipendenze in transazioni separate assicurandomi di reimpostare il gestore entità in un'eccezione chiave duplicata. Direi che tutte le entità presenti accanto alla corrispondenza potrebbero essere definite "condivise" perché potrebbero potenzialmente far parte di altre transazioni in altri consumatori. Qualcosa che non è "condiviso" è la corrispondenza stessa che probabilmente non verrà creata da due consumatori contemporaneamente.
Tutto ciò ha portato anche a un altro problema. Se ripristini Entity Manager, tutti gli oggetti che hai recuperato prima del ripristino sono per Doctrine completamente nuovi. Quindi Doctrine non proverà a eseguire un AGGIORNAMENTO su di essi ma un INSERT ! Quindi assicurati di creare tutte le tue dipendenze in transazioni logicamente corrette e quindi recuperare tutti i tuoi oggetti dal database prima di impostarli sull'entità di destinazione. Considera il codice seguente come esempio:
$group = $this->createGroupIfDoesNotExist($groupData);
$match->setGroup($group);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
Quindi questo è come penso che dovrebbe essere fatto.
$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();
Spero possa essere d'aiuto :)