Ето как бих го направил. Не казвам, че това е най-добрият подход, ако някой знае нещо по-лесно или по-добро, аз първи ще се интересувам да го науча.
Първо, това са Събития в доктрината които можете да използвате. За по-голяма простота ще обясня как бих го направил за изтривания. Освен това за простота ще използвам статичен масив (може да се направи по други начини, харесвам този) и обратни извиквания на жизнения цикъл . В този случай обратните извиквания ще бъдат много прости методи (ето защо е добре да ги използвате, вместо да прилагате слушател или абонат ).
Да кажем, че имаме този обект:
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]
Както можете да видите, дефинирах две обратни извиквания, които ще бъдат задействани със събитието preRemove и събитието postRemove.
След това php кодът на обекта:
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();
}
}
Обърнете внимание на обратните извиквания и статичните масиви и методи. Всеки път, когато се извиква премахване през Car
обект, preRemove
обратното извикване ще съхрани идентификатора на обекта в масива $preDeletedEntities
. Когато обектът бъде изтрит, postRemove
събитие ще съхрани идентификатора в $entityDeleted
. preRemove
събитие е важно, защото искаме да знаем кой обект е направил транзакцията неуспешна.
И сега в контролера можем да направим това:
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,
));
}
Дано помогне. Той е малко по-тромав за прилагане от първия подход, но много по-добър по отношение на производителността.
АКТУАЛИЗАЦИЯ
Ще се опитам да обясня малко повече какво се случва вътре в catch
блокиране:
В този момент транзакцията е неуспешна. Беше повдигнато изключение поради факта, че изтриването на някакъв обект не е възможно (поради например ограничение fk).
Транзакцията е върната назад и нито един обект не е действително премахнат от базата данни.
$deletedCars
е променлива, която съдържа идентификаторите на онези обекти, които е можело да бъдат изтрити (те не са повдигнали изключение), но не са (поради връщането назад).
$failToDeleteCar
съдържа идентификатора на обекта, чието изтриване е предизвикало изключението.
$notDeletedCars
съдържа останалите идентификатори на обекти, които са били в транзакцията, но за които не знаем дали биха успели или не.
В този момент можете да нулирате мениджъра на обекти (той е затворен), да стартирате друга заявка с идентификаторите, които не са причинили проблем, и да ги изтриете (ако желаете) и да изпратите обратно съобщение, уведомяващо потребителя, че сте изтрили тези обекти и това $failToDeleteCar
не успя и не беше изтрит и $notDeletedCars
също не бяха изтрити. От вас зависи да решите какво да направите.
Не мога да възпроизведа проблема, който споменахте за Entity::getDeletedEntities()
, тук работи добре.
Можете да прецизирате кода си, така че да не е необходимо да добавяте тези методи към вашите обекти (дори и обратните извиквания на жизнения цикъл). Можете например да използвате абонат за улавяне на събития и специален клас със статични методи, за да следите тези обекти, които не са се провалили, тези, които са се провалили, и тези, които не са имали възможност да бъдат изтрити/ актуализиран/вмъкнат. Препращам ви към предоставената от мен документация. Малко по-сложно е, отколкото звучи, не може да ви даде общ отговор в няколко реда код, съжалявам, ще трябва да проучите допълнително.
Моето предложение е да опитате предоставения от мен код с фалшив обект и да направите някои тестове, за да разберете напълно как работи. След това можете да опитате да го приложите към вашите обекти.
Успех!