Aqui está como eu faria isso. Não estou dizendo que esta é a melhor abordagem, se alguém souber algo mais fácil ou melhor, eu seria o primeiro interessado em aprender.
Primeiramente, estes são os Eventos de doutrina que você pode usar. Para simplificar, vou explicar como eu faria isso para exclusões. Também para simplificar, vou usar um array estático (pode ser feito de outras maneiras, gosto deste) e retornos de chamada do ciclo de vida . Nesse caso, os retornos de chamada serão métodos muito simples (é por isso que não há problema em usá-los em vez de implementar um ouvinte ou assinante ).
Digamos que temos esta entidade:
Acme\MyBundle\Entity\Car:
type: entity
table: cars
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: '25'
unique: true
color:
type: string
length: '64'
lifecycleCallbacks:
preRemove: [entityDueToDeletion]
postRemove: [entityDeleted]
Como você pode ver, defini dois callbacks que serão acionados com o evento preRemove e o evento postRemove.
Em seguida, o código php da entidade:
class Car {
// Getters & setters and so on, not going to copy them here for simplicity
private static $preDeletedEntities;// static array that will contain entities due to deletion.
private static $deletedEntities;// static array that will contain entities that were deleted (well, at least the SQL was thrown).
public function entityDueToDeletion() {// This callback will be called on the preRemove event
self::$preDeletedEntities[] = $this->getId();// This entity is due to be deleted though not deleted yet.
}
public function entityDeleted() {// This callback will be called in the postRemove event
self::$deletedEntities[] = $this->getId();// The SQL to delete the entity has been issued. Could fail and trigger the rollback in which case the id doesn't get stored in the array.
}
public static function getDeletedEntities() {
return array_slice(self::$preDeletedEntities, 0, count(self::$deletedEntities));
}
public static function getNotDeletedEntities() {
return array_slice(self::$preDeletedEntities, count(self::$deletedEntities)+1, count(self::$preDeletedEntities));
}
public static function getFailedToDeleteEntity() {
if(count(self::$preDeletedEntities) == count(self::$deletedEntities)) {
return NULL; // Everything went ok
}
return self::$preDeletedEntities[count(self::$deletedEntities)]; // We return the id of the entity that failed.
}
public static function prepareArrays() {
self::$preDeletedEntities = array();
self::$deletedEntities = array();
}
}
Observe os retornos de chamada e as matrizes e métodos estáticos. Toda vez que uma remoção é chamada em um
Car
entidade, o preRemove
callback irá armazenar o id da entidade no array $preDeletedEntities
. Quando a entidade é excluída, o postRemove
O evento armazenará o id em $entityDeleted
. O preRemove
O evento é importante porque queremos saber qual entidade fez a transação falhar. E agora, no controlador, podemos fazer isso:
use Acme\MyBundle\Entity\Car;
$qb = $em->createQueryBuilder();
$ret = $qb
->select("c")
->from('AcmeMyBundle:Car', 'c')
->add('where', $qb->expr()->in('c.id', ':ids'))
->setParameter('ids', $arrayOfIds)
->getQuery()
->getResult();
Car::prepareArrays();// Initialize arrays (useful to reset them also)
foreach ($ret as $car) {// Second approach
$em->remove($car);
}
try {
$em->flush();
} catch (\Exception $e) {
$couldBeDeleted = Car::getDeletedEntities();
$entityThatFailed = Car::getFailedToDeleteEntity();
$notDeletedCars = Car::getNotDeletedEntities();
// Do what you please, you can delete those entities that didn't fail though you'll have to reset the entitymanager (it'll be closed by now due to the exception).
return $this->render('AcmeMyBundle:Car:errors.html.twig', array(// I'm going to respond with the ids that could've succeded, the id that failed and those entities that we don't know whether they could've succeeded or not.
'deletedCars' => $couldBeDeleted,
'failToDeleteCar' => $entityThatFailed,
'notDeletedCars' => $notDeletedCars,
));
}
Espero que ajude. É um pouco mais complicado de implementar do que a primeira abordagem, mas muito melhor em termos de desempenho.
ATUALIZAÇÃO
Vou tentar explicar um pouco mais o que está acontecendo dentro do
catch
quadra:Neste ponto, a transação falhou. Uma exceção foi levantada devido ao fato de que a exclusão de alguma entidade não é possível (devido, por exemplo, a uma restrição fk).
A transação foi revertida e nenhuma entidade foi realmente removida do banco de dados.
$deletedCars
é uma variável que contém os ids dessas entidades que poderiam ter sido excluídas (elas não geraram nenhuma exceção), mas não são (por causa da reversão). $failToDeleteCar
contém o id da entidade cuja exclusão gerou a exceção. $notDeletedCars
contém o restante dos IDs de entidades que estavam na transação, mas que não sabemos se teriam sucesso ou não. Neste ponto, você pode redefinir o entitymanager (está fechado), iniciar outra consulta com os ids que não causaram problema e excluí-los (se desejar) e enviar de volta uma mensagem informando ao usuário que você excluiu essas entidades e que
$failToDeleteCar
falhou e não foi excluído e $notDeletedCars
também não foram deletados. Cabe a você decidir o que fazer. Não consigo reproduzir o problema que você mencionou sobre
Entity::getDeletedEntities()
, está funcionando bem aqui. Você pode refinar seu código para não precisar adicionar esses métodos às suas entidades (nem mesmo os retornos de chamada do ciclo de vida). Você poderia, por exemplo, fazer uso de um assinante para capturar eventos e uma classe especial com métodos estáticos para acompanhar as entidades que não falharam, as que falharam e as que não tiveram a oportunidade de serem excluídas/ atualizado/inserido. Indico a documentação que forneci. É um pouco mais complicado do que parece, não é capaz de lhe dar uma resposta genérica em algumas linhas de código, desculpe, você terá que investigar mais.
Minha sugestão é que você experimente o código que forneci com uma entidade falsa e faça alguns testes para entender completamente como funciona. Então você pode tentar aplicá-lo às suas entidades.
Boa sorte!