我就是这样做的。我并不是说这是最好的方法,如果有人知道更简单或更好的东西,我将是第一个有兴趣学习它的人。
首先,这些是教义事件 http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events您可以使用。为了简单起见,我将解释如何进行删除。另外为了简单起见,我将使用静态数组(可以通过其他方式完成,我喜欢这个)并且生命周期回调 http://symfony.com/doc/current/book/doctrine.html#lifecycle-callbacks。在这种情况下,回调将是非常简单的方法(这就是为什么可以使用它们而不是实现听众或订阅者 http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html).
假设我们有这个实体:
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 事件触发的回调。
preRemove - 给定实体的 preRemove 事件发生在
执行该实体的相应 EntityManager 删除操作。
DQL DELETE 语句不会调用它。
postRemove - postRemove 事件发生在实体
实体已被删除。数据库删除后会调用
运营。 DQL DELETE 语句不会调用它。
然后实体的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();
}
}
请注意回调以及静态数组和方法。每次通过 a 调用删除Car
实体,即preRemove
回调会将实体的 id 存储在数组中$preDeletedEntities
。当实体被删除时,postRemove
事件会将 id 存储在$entityDeleted
. The 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,
));
}
希望能帮助到你。它的实现比第一种方法要麻烦一些,但在性能方面要好得多。
UPDATE
我将尝试更多地解释一下内部发生的事情catch
block:
至此,交易失败。由于无法删除某些实体(例如由于 fk 约束),因此引发了异常。
事务已回滚,并且实际上没有从数据库中删除任何实体。
$deletedCars
是一个变量,其中包含那些本来可以被删除(它们没有引发任何异常)但没有(因为回滚)的实体的 id。
$failToDeleteCar
包含其删除引发异常的实体的 ID。
$notDeletedCars
包含事务中的其余实体 ID,但我们不知道是否会成功。
此时,您可以重置实体管理器(它已关闭),使用不会导致问题的 id 启动另一个查询并删除它们(如果您愿意),然后发回一条消息,让用户知道您删除了这些实体,并且$failToDeleteCar
失败且未被删除$notDeletedCars
也没有被删除。由您决定要做什么。
我无法重现你提到的问题Entity::getDeletedEntities()
,这里工作正常。
您可以优化代码,以便不需要将此方法添加到实体(甚至不需要生命周期回调)。例如,您可以利用订阅者来捕获事件,并使用具有静态方法的特殊类来跟踪那些未失败的实体、失败的实体以及那些没有机会被删除的实体/更新/插入。我建议您参考我提供的文档。这比听起来要复杂一些,无法在几行代码中为您提供通用答案,抱歉,您必须进一步调查。
我的建议是您尝试使用我提供的假实体代码并进行一些测试以完全理解它是如何工作的。然后您可以尝试将其应用到您的实体中。
祝你好运!