使用 preRemove/postRemove 事件获取哪些查询可以执行,哪些不能执行
我有一段时间在想这个问题,现在我需要一些关于 preRemove/postRemove 事件的建议,因为我将执行的查询基本上是 DELETE
但这应该也适用于 prePersist/postPersist 和 preUpdate/postUpdate(不知道那些最新的是否真的存在).
I'm having this question round in my mind for a while and now I need some kind of advice around preRemove/postRemove events since the queries I'll execute basically will be DELETE
but this should apply also to prePersist/postPersist and preUpdate/postUpdate (doesn't know if those latest really exists).
对于在多个实体中执行 DELETE,我有两种可能的情况(参见 foreach
循环):
I have this two possible situation for execute DELETE in several entities (see foreach
loop):
// 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 用户在 this 答案 execute flush()
是一个反模式,也会影响应用程序性能,因为需要多个查询每次都被执行,但使用这种方法我可以得到哪个被插入,哪个没有被插入.
The first approach is not recommended and also as @acontell user said on this answer execute flush()
is an antipatern and also will hit application performance since multiple queries will need to be executed each time but using that approach I can get which one was inserted and which one doesn't.
使用第二种方法,我将避免反模式并提高性能,但我怎么知道哪些项目被插入,哪些没有?此外,如果任何查询默认失败,Doctrine 将执行回滚,因此不会插入任何查询.
Using second approach I'll avoid antipatern and will improve performance but how do I know which item was inserted and which doesn't? Also if any query fails by default Doctrine will do a rollback so none will be inserted.
那么,我可以使用 preRemove/postRemove 事件来获取哪些查询可以执行,哪些不能表示插入或不插入哪些值?
So, can I use preRemove/postRemove events to get which queries can be executed and which can't meaning which values are inserted or not?
这个问题与 this 和 这个.
现实生活中的例子
既然 @acontell 给了我另一个很好的答案,我需要一些建议来看看我是否得到了整个事情,否则我仍然迷路了,所以这是一个真实的例子:
Since @acontell give me another excellent answer I'll need some advice to see if I get the whole thing or I'm still lost so here is a real life example:
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
应该放在 AppBundle:FabricanteProductoSolicitud
中执行 DELETE,对吗?
If I get it, then the LifeCycleCallbacks
should go in AppBundle:FabricanteProductoSolicitud
where DELETE are performed, is that right?
我也想知道在多个实体上使用代码的最佳方法,因为我在大多数实体中都有这种行为,那么为此目的定义一个 Trait 应该没问题?应该定义为任何其他特征吗?
这个问题已经由我自己在这里回答,使用用户评论作为输入,希望它可以帮助其他人
This has been answered here by myself using users comments as input, hope it can help others
通过@acontell 对代码进行一些测试
这是我的代码此时的样子:
This is how my code looks like at this moment:
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)
EntityProducto::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'] = EntityProducto::getDeletedEntities();
$response['itemsNonRemoved'] = EntityProducto::getNotDeletedEntities();
} else {
$response['error'] = $this->get('translator')->trans('mensajes.msgPeticionXMLHttpRequestInvalida');
}
return new JsonResponse($response, $status ?: 200);
}
问题 EntityProducto::getDeletedEntities()
返回一个没有删除值的空数组,为什么?
The problem EntityProducto::getDeletedEntities()
is returning an empty array without deleted values, why?
推荐答案
这是我的做法.我并不是说这是最好的方法,如果有人知道更简单或更好的东西,我会第一个有兴趣学习它.
Here's how I'd do it. I'm not saying this is the best approach, if anyone knows something easier or better, I'd be the first interested in learning it.
首先,这些是 你可以使用的教义事件.为简单起见,我将解释如何进行删除操作.同样为简单起见,我将使用一个静态数组(它可以通过其他方式完成,我喜欢这个)和 生命周期回调.在这种情况下,回调将是非常简单的方法(这就是为什么可以使用它们而不是实现 听众或订阅者).
First off, these are the Doctrine events that you can use. For simplicity's sake, I'm going to explain how I'd do it for deletions. Also for simplicity, I'm going to use an static array (it could be done some other ways, I like this one) and lifecycle callbacks. In this case the callbacks are going to be very simple methods (that's why it's ok to use them instead of implementing a listener or subscriber).
假设我们有这个实体:
AcmeMyBundleEntityCar:
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 事件触发.
As you can see, I've defined two callbacks that will be triggered with the preRemove event and the postRemove event.
preRemove - preRemove 事件发生在给定实体之前执行该实体的相应 EntityManager 删除操作.DQL DELETE 语句不会调用它.
preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. It is not called for a DQL DELETE statement.
postRemove - postRemove 事件发生在实体之后实体已被删除.数据库删除后会调用操作.DQL DELETE 语句不会调用它.
postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. It is not called for a DQL DELETE statement.
然后是实体的php代码:
Then the php code of the entity:
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
事件很重要,因为我们想知道是哪个实体导致事务失败.
Note the callbacks and the static arrays and methods. Everytime a remove is called over a Car
entity, the preRemove
callback will store the id of the entity in the array $preDeletedEntities
. When the entity is deleted, the postRemove
event will store the id in $entityDeleted
. The preRemove
event is important because we want to know which entity made the transaction fail.
现在,在控制器中我们可以这样做:
And now, in controller we can do this:
use AcmeMyBundleEntityCar;
$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,
));
}
希望有帮助.实施起来比第一种方法要麻烦一些,但在性能方面要好得多.
Hope it helps. It's a bit more cumbersome to implement than the first approach but much much better in terms of performance.
更新
我将尝试解释一下 catch
块中发生的事情:
I'm going to try to explain a little bit more what's hapenning inside the catch
block:
此时,交易已经失败.由于无法删除某些实体(例如由于 fk 约束),因此引发了异常.
At this point, the transaction has failed. An exception has been raised due to the fact that the deletion of some entity is not possible (due for example to a fk constraint).
事务已回滚,实际上没有实体从数据库中删除.
The transaction has been rolled back and no entites have been actually removed from the database.
$deletedCars
是一个变量,它包含那些本可以被删除(它们没有引发任何异常)但没有(因为回滚)的实体的 ID.
$deletedCars
is a variable that contains the ids of those entities that could've been deleted (they didn't raise any exception) but aren't (because of the roll back).
$failToDeleteCar
包含删除引发异常的实体的 ID.
$failToDeleteCar
contains the id of the entity whose deletion raised the exception.
$notDeletedCars
包含交易中的其余实体 ID,但我们不知道是否会成功.
$notDeletedCars
contains the rest of the entities ids that were in the transaction but that we don't know wheter would have succeeded or not.
此时,您可以重置 entitymanager(它已关闭),使用不会导致问题的 ID 启动另一个查询并删除它们(如果您愿意),然后发回一条消息让用户知道您删除了那些实体和 $failToDeleteCar
失败并且没有被删除,$notDeletedCars
也没有被删除.由您决定要做什么.
At this point, you can reset the entitymanager (it's closed), launch another query with the ids that didn't cause problem and delete them (if you like) and send back a message letting the user know that you deleted those entities and that $failToDeleteCar
failed and wasn't deleted and $notDeletedCars
weren't deleted either. It's up to you to decide what to do.
我无法重现您提到的关于 Entity::getDeletedEntities()
的问题,它在这里工作正常.
I can't reproduce the problem you mention about Entity::getDeletedEntities()
, it's working fine here.
您可以优化您的代码,这样您就不需要将此方法添加到您的实体(甚至生命周期回调)中.例如,您可以使用订阅者来捕获事件,并使用一个带有静态方法的特殊类来跟踪那些没有失败的实体、失败的实体和没有机会被删除的实体/更新/插入.我向您推荐我提供的文档.它比听起来要复杂一些,无法在几行代码中给您一个通用的答案,抱歉,您必须进一步调查.
You could refine your code so that you didn't need to add this methods to your entities (not even the lifecycle callbacks). You could, for example, make use of a subscriber to capture events and a special class with static methods to keep track of those entities that didn't fail, the one that failed and those that didn't have the opportunity to be deleted/updated/inserted. I refer you to the documentation I provided. It's a bit more complicated than it sounds, not able to give you a generic answer in a few lines of code, sorry, you'll have to investigate further.
我的建议是,您可以尝试使用我提供的带有虚假实体的代码并进行一些测试以完全了解它的工作原理.然后您可以尝试将其应用于您的实体.
My suggestion is that you try the code I provided with a fake entity and make some tests to fully understand how it works. Then you can try to apply it to your entities.
祝你好运!
相关文章