我在脑海中盘旋了一段时间这个问题,现在我需要一些关于preRemove/postMove事件的建议,因为我基本上要执行的查询将是DELETE
但这也适用于prePersist/postPersist和preUpdate/postUpdate(不知道那些最新的是否真的存在)。
我在几个实体中执行 DELETE 有两种可能的情况(请参阅foreach
循环):
// First approach
$itemsRemoved = $itemsNonRemoved = [];
foreach($someVar as $item) {
$item = $em->getRepository('someEntity')->find($item['value']);
try {
$em->remove($item);
$em->flush();
array_push($itemsRemoved, $item['value']);
} catch (Exception $e) {
dump($e->getMessage());
array_push($itemsNonRemoved, $item['value']);
}
}
// Second approach
$itemsRemoved = $itemsNonRemoved = [];
foreach($someVar as $item) {
$item = $em->getRepository('someEntity')->find($item['value']);
$em->remove($item);
}
$em->flush();
不建议使用第一种方法@acontell并且正如用户在此答案上所说的那样,执行flush()
是一个反父,并且也会影响应用程序性能,因为每次都需要执行多个查询,但使用这种方法我可以得到哪个入,哪个没有。
使用第二种方法,我将避免反父,并将提高性能,但我如何知道哪个项目入,哪些没有?此外,如果默认情况下任何查询失败,Doctrine 将执行回滚,因此不会插入任何查询。
那么,我是否可以使用 preRemove/postRemove 事件来获取哪些查询可以执行,哪些查询不能表示插入或不插入哪些值?
这个问题与这个问题和这个问题已经关闭了关系。
现实生活中的例子
由于@acontell给了我另一个很好的答案,我需要一些建议,看看我是否得到了整个东西,或者我仍然迷路了,所以这里有一个现实生活中的例子:
foreach ($request->request->get( 'items' ) as $item) {
$relacion = $this->get( 'database_connection' )->fetchColumn(
'SELECT COUNT(fabricante_producto_solicitud_id) AS cnt FROM negocio.fabricante_modelo_marca_producto WHERE fabricante_producto_solicitud_id = ?',
array( $item['value'] )
);
if ($relacion === 0) {
$entFabricanteProductoSolicitud = $em->getRepository(
"AppBundle:FabricanteProductoSolicitud"
)->find( $item['value'] );
try {
$em->remove( $entFabricanteProductoSolicitud );
$em->flush();
array_push( $itemsRemoved, $item['value'] );
$response['success'] = true;
$status = 200;
} catch ( 'Exception $e ) {
$status = 400;
dump( $e->getMessage() );
return new JsonResponse( $response, $status ?: 200 );
}
}
$response['itemsRemoved'] = $itemsRemoved;
}
}
如果我得到它,那么LifeCycleCallbacks
应该进入执行 DELETE 的AppBundle:FabricanteProductoSolicitud
,对吗?
编辑:我也想知道在多个实体上使用代码的最佳方法,因为我在大多数实体中都有这种行为,那么为此目的定义一个 Trait 应该没问题吗?应该被定义为任何其他特征吗?
这是我自己在这里用用户评论作为输入来回答的,希望可以帮助其他人
通过@acontell对代码执行一些测试
这是我的代码此刻的样子:
public function eliminarNormasAction(Request $request)
{
if ($request->isXmlHttpRequest()) {
$em = $this->getDoctrine()->getManager();
$response['success'] = false;
$entProducto = $em->getRepository('AppBundle:Producto')->find($request->request->get('producto'));
$response['success'] = false;
$status = null;
$ids = [];
foreach($request->request->get( 'items' ) as $item) {
array_push( $ids, $item['value'] );
}
$qb = $em->createQueryBuilder();
$entNorma = $qb
->select("q")
->from('AppBundle:Norma', 'q')
->add('where', $qb->expr()->in('q.id', ':ids'))
->setParameter('ids', $ids)
->getQuery()
->getResult();
// Initialize arrays (useful to reset them also)
Entity'Producto::prepareArrays();
foreach($entNorma as $norma) {
// here entities are persisted since rows there is not more at DB
$entProducto->removeProductoNorma( $norma );
}
try {
$em->flush();
$response['success'] = true;
} catch ('Exception $e) {
$status = 400;
}
$response['itemsRemoved'] = Entity'Producto::getDeletedEntities();
$response['itemsNonRemoved'] = Entity'Producto::getNotDeletedEntities();
} else {
$response['error'] = $this->get('translator')->trans('mensajes.msgPeticionXMLHttpRequestInvalida');
}
return new JsonResponse($response, $status ?: 200);
}
Entity'Producto::getDeletedEntities()
问题是返回一个没有删除值的空数组,为什么?
这是我的做法。我并不是说这是最好的方法,如果有人知道更容易或更好的东西,我会是第一个有兴趣学习它的人。
首先,这些是您可以使用的教义事件。为了简单起见,我将解释如何删除。同样为了简单起见,我将使用静态数组(可以通过其他方式完成,我喜欢这个)和生命周期回调。在这种情况下,回调将是非常简单的方法(这就是为什么可以使用它们而不是实现侦听器或订阅者)。
假设我们有这个实体:
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 - 在 执行该实体的相应实体管理器删除操作。 DQL DELETE 语句不需要它。
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();
}
}
请注意回调以及静态数组和方法。每次通过Car
实体调用 remove 时,preRemove
回调都会将实体的 id 存储在数组$preDeletedEntities
中。删除实体时,postRemove
事件会将 id 存储在 $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
是一个变量,其中包含那些可以删除(它们没有引发任何异常)但未删除(由于回滚)的实体的 ID。
$failToDeleteCar
包含删除引发异常的实体的 ID。
$notDeletedCars
包含事务中的其余实体 ID,但我们不知道这些实体 ID 是否会成功。
此时,您可以重置实体管理器(它已关闭),使用未导致问题的 id 启动另一个查询并删除它们(如果您愿意),然后发回一条消息,让用户知道您删除了这些实体,并且$failToDeleteCar
失败了,未被删除,$notDeletedCars
也没有被删除。由您决定该怎么做。
我无法重现您提到的关于Entity::getDeletedEntities()
的问题,它在这里工作正常。
您可以优化代码,以便无需将此方法添加到实体(甚至不需要生命周期回调)。例如,您可以使用订阅者来捕获事件,并使用具有静态方法的特殊类来跟踪那些未失败的实体、失败的实体以及那些没有机会被删除/更新/插入的实体。我请您参考我提供的文档。它比听起来要复杂一些,无法在几行代码中为您提供通用答案,抱歉,您必须进一步调查。
我的建议是,您尝试使用假实体提供的代码并进行一些测试以完全了解其工作原理。然后,您可以尝试将其应用于实体。
祝你好运!