Voici comment je procéderais. Je ne dis pas que c'est la meilleure approche, si quelqu'un sait quelque chose de plus facile ou de mieux, je serais le premier intéressé à l'apprendre.
Tout d'abord, ce sont les Événements doctrinaux que vous pouvez utiliser. Par souci de simplicité, je vais vous expliquer comment je le ferais pour les suppressions. Aussi pour plus de simplicité, je vais utiliser un tableau statique (cela pourrait être fait d'une autre manière, j'aime celui-ci) et rappels de cycle de vie . Dans ce cas, les rappels vont être des méthodes très simples (c'est pourquoi il est possible de les utiliser au lieu d'implémenter un auditeur ou abonné ).
Disons que nous avons cette entité :
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]
Comme vous pouvez le voir, j'ai défini deux rappels qui seront déclenchés avec l'événement preRemove et l'événement postRemove.
Puis le code php de l'entité :
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();
}
}
Notez les rappels et les tableaux et méthodes statiques. Chaque fois qu'un retrait est appelé sur une Car
entité, le preRemove
le rappel stockera l'identifiant de l'entité dans le tableau $preDeletedEntities
. Lorsque l'entité est supprimée, le postRemove
l'événement stockera l'identifiant dans $entityDeleted
. Le preRemove
L'événement est important car nous voulons savoir quelle entité a fait échouer la transaction.
Et maintenant, dans le contrôleur, nous pouvons faire ceci :
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,
));
}
J'espère que cela aide. C'est un peu plus lourd à mettre en œuvre que la première approche mais beaucoup mieux en termes de performances.
MISE À JOUR
Je vais essayer d'expliquer un peu plus ce qui se passe à l'intérieur du catch
bloquer :
À ce stade, la transaction a échoué. Une exception a été levée car la suppression de certaines entités n'est pas possible (à cause par exemple d'une contrainte fk).
La transaction a été annulée et aucune entité n'a été réellement supprimée de la base de données.
$deletedCars
est une variable qui contient les identifiants des entités qui auraient pu être supprimées (elles n'ont déclenché aucune exception) mais qui ne le sont pas (à cause de la restauration).
$failToDeleteCar
contient l'identifiant de l'entité dont la suppression a déclenché l'exception.
$notDeletedCars
contient le reste des identifiants d'entités qui étaient dans la transaction mais dont nous ne savons pas si cela aurait réussi ou non.
À ce stade, vous pouvez réinitialiser le gestionnaire d'entités (il est fermé), lancer une autre requête avec les identifiants qui n'ont pas causé de problème et les supprimer (si vous le souhaitez) et renvoyer un message informant l'utilisateur que vous avez supprimé ces entités et que $failToDeleteCar
a échoué et n'a pas été supprimé et $notDeletedCars
n'ont pas été supprimés non plus. C'est à vous de décider quoi faire.
Je ne peux pas reproduire le problème que vous mentionnez à propos de Entity::getDeletedEntities()
, ça marche bien ici.
Vous pouvez affiner votre code afin de ne pas avoir besoin d'ajouter ces méthodes à vos entités (pas même les rappels de cycle de vie). Vous pouvez, par exemple, utiliser un abonné pour capturer des événements et une classe spéciale avec des méthodes statiques pour suivre les entités qui n'ont pas échoué, celle qui a échoué et celles qui n'ont pas eu la possibilité d'être supprimées/ mis à jour/inséré. Je vous renvoie à la documentation que j'ai fournie. C'est un peu plus compliqué qu'il n'y paraît, pas en mesure de vous donner une réponse générique en quelques lignes de code, désolé, vous devrez enquêter plus avant.
Ma suggestion est que vous essayiez le code que j'ai fourni avec une fausse entité et que vous fassiez quelques tests pour bien comprendre comment cela fonctionne. Ensuite, vous pouvez essayer de l'appliquer à vos entités.
Bonne chance !