使用preRemove / postRemove事件来获取哪些查询可以执行,哪些不能执行 [英] Using preRemove/postRemove events to get which queries can be executed and which can't

查看:694
本文介绍了使用preRemove / postRemove事件来获取哪些查询可以执行,哪些不能执行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我心中有这个问题一段时间,现在我需要一些关于preRemove / postRemove事件的建议,因为我将执行的查询基本上将是 DELETE 但这也应该也适用于prePersist / postPersist和preUpdate / postUpdate(不知道这些最新是否真的存在)。



我有两个可能的情况在几个实体中执行DELETE(见 foreach loop):

  //第一个方法
$ itemsRemoved = $ itemsNonRemoved = [];
foreach($ someVar as $ item){
$ item = $ em-> getRepository('someEntity') - > find($ item ['value']);
尝试{
$ em-> remove($ item);
$ em-> flush();
array_push($ itemsRemoved,$ item ['value']);
} catch(Exception $ e){
dump($ e-> getMessage());
array_push($ itemsNonRemoved,$ item ['value']);
}
}

//第二种方法
$ itemsRemoved = $ itemsNonRemoved = [];
foreach($ someVar as $ item){
$ item = $ em-> getRepository('someEntity') - > find($ item ['value']);
$ em-> remove($ item);
}

$ em-> flush();

不建议使用第一种方法,也可以将 @acontell 用户说这个答案执行 flush()是一个antipatern,并且也会影响应用程序性能,因为每次都需要执行多个查询,但是使用这种方法可以得到插入哪一个,哪一个



使用第二种方法,我将避免antipatern,并提高性能,但如何知道哪个项目被插入,哪些不是?此外,如果任何查询失败,默认情况下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());

返回新的JsonResponse($ response,$ status?:200);
}
}

$ response ['itemsRemoved'] = $ itemsRemoved;
}
}

如果我得到它,那么 LifeCycleCallbacks 应该在 AppBundle:FabricanteProductoSolicitud 中执行DELETE,是吗?



编辑:
此外,我也想知道使用多个实体的代码的最佳方法,因为我们在大多数实体中会有这种行为,那么应该是好的为此目的定义一个特征?应该被定义为任何其他特征?



这已被回答这里由我自己使用用户评论作为输入,希望它可以帮助他人



通过@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 ''',$ qb-> expr() - > in('q.id',':ids'))
- > setParameter('ids',$ ids)
- > getQuery()
- > getResult();

//初始化数组(有用于重置它们)
Entity\Producto :: prepareArrays();

foreach($ entNorma as $ norma){
//这里实体被保留,因为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');
}

返回新的JsonResponse($ response,$ status?:200);
}

问题 Entity\Producto :: getDeletedEntities( )返回一个没有删除值的空数组,为什么?

解决方案

它。我不是说这是最好的方法,如果有人知道更容易或更好的东西,我会是第一个有兴趣学习的人。



首先,这些是教义事件你可以使用为了简单起见,我要解释一下我如何去做删除。为了简单起见,我将使用一个静态数组(可以通过其他方式,我喜欢这个)和生命周期回调。在这种情况下,回调将是非常简单的方法(这就是为什么使用它们而不是实现听众或订阅者)。



假设我们有这个实体:

  Acme\MyBundle\Entity\Car:
类型:实体
表:cars
id:
id:
类型:整数
id:true
生成器:
策略:AUTO
字段:
名称:
类型:字符串
长度: 25'
unique:true
color:
type:string
length:'64'
lifecycleCallbacks:
preRemove:[entityDueToDeletion]
postRemove:[entityDeleted]

正如你所看到的,我已经定义了两个将被触发的回调使用preRemove事件和postRemove事件。


preRemove - 在
各自的EntityManager之前,给定实体发生preRemove事件执行该实体的删除操作。
它不会调用DQL DELETE语句。



postRemove -
实体被删除后,实体发生postRemove事件。在数据库删除
操作后将被调用。它不被称为DQL DELETE语句。


然后实体的PHP代码:

  class Car {

// Getters& setter等等,不要在这里简单地复制它们

private static $ preDeletedEntities; //将包含实体删除的静态数组。
private static $ deletedEntities; //将包含被删除的实体的静态数组(至少抛出SQL)。

public function entityDueToDeletion(){//这个回调将在preRemove事件上调用
self :: $ preDeletedEntities [] = $ this-> getId(); //此实体将被删除,但尚未删除。
}

public function entityDeleted(){//这个回调将在postRemove事件中调用
self :: $ deletedEntities [] = $ this-> getId() ; //删除实体的SQL已经发出。可能会失败并触发回滚,在这种情况下,该ID不会存储在数组中。
}

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)){
返回NULL; //一切都OK了
}
return self :: $ preDeletedEntities [count(self :: $ deletedEntities)]; //我们返回失败的实体的id。
}

public static function prepareArrays(){
self :: $ preDeletedEntities = array();
self :: $ deletedEntities = array();
}
}

注意回调和静态数组和方法。每次通过 Car 实体调用删除时, preRemove 回调将将实体的ID存储在数组 $ preDeletedEntities 。当实体被删除时, postRemove 事件将在 $ entityDeleted 中存储该ID。 preRemove 事件很重要,因为我们想知道哪个实体使事务失败。



现在,在控制器我们可以这样做:

 使用Acme\MyBundle\Entity\Car; 

$ qb = $ em-> createQueryBuilder();
$ ret = $ qb
- > select(c)
- > from('AcmeMyBundle:Car','c')
- > add ''$'$'$'$'$'$'$'$'$'$'$'$'$' > getQuery()
- > getResult();

Car :: prepareArrays(); //初始化数组(有用于重置它们)
foreach($ ret as $ car){//第二种方法
$ em- >清除($车);
}

try {
$ em-> flush();
} catch(\Exception $ e){
$ canBeDeleted = Car :: getDeletedEntities();
$ entityThatFailed = Car :: getFailedToDeleteEntity();
$ notDeletedCars = Car :: getNotDeletedEntities();

//做你想要的,你可以删除那些没有失败的实体,但你必须重置entitymanager(现在由于异常关闭)。

return $ this-> render('AcmeMyBundle:Car:errors.html.twig',array(//我将用可能已经成功的ids来响应,id
'deletedCars'=> $ canBeDeleted,
'failToDeleteCar'=> $ entityThatFailed,
'我们不知道他们是否可以成功notDeletedCars'=> $ notDeletedCars,
));
}

希望有帮助。实施起来比第一种方法要麻烦得多,但在性能方面要好得多。



更新



我将尝试解释一下,在 catch 块中的内容:



此时,交易失败。由于某些实体的删除是不可能的(由于例如fk约束),因此已经提出了例外。



事务已经回滚,没有真正的数据库已被删除。



$ deletedCars 是一个包含这些实体的id的变量可能已被删除(他们没有提出任何例外),但不是(因为回滚)。



$ failToDeleteCar 包含删除引发异常的实体的ID。



$ notDeletedCars contains在交易中的其他实体ID,但是我们不知道的事情将会成功。



此时,您可以重置entitymanager(它关闭),启动另一个查询与i​​ds不会导致问题并删除它们(如果你喜欢),并发回一个消息,让用户知道你删除了这些实体和th在 $ failToDeleteCar 失败,没有被删除,并且 $ notDeletedCars 也没有被删除。



我无法重现您提到的有关 Entity :: getDeletedEntities(),它在这里工作正常。



您可以细化代码,以便您不需要将此方法添加到实体(甚至生命周期回调) )。例如,您可以使用订阅者捕获事件和使用静态方法的特殊类来跟踪那些未失败的实体,失败的那些实体以及没有机会被删除/更新/插入。我转介你提供的文件。这听起来有点复杂,不能在几行代码中给出一般的答案,对不起,你必须进一步调查。



我的建议您尝试使用假实体提供的代码,并进行一些测试,以充分了解它是如何工作的。那么你可以尝试将它应用到你的实体。



祝你好运!


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).

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();

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.

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.

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 question has closed relation to this and this ones.

Real life example

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;
        }
    }

If I get it, then the LifeCycleCallbacks should go in AppBundle:FabricanteProductoSolicitud where DELETE are performed, is that right?

EDIT: Also I like to know the best approach to use the code on several entities since I'll have this behavior in most of them, then should be fine to define a Trait for this purpose? Should be defined as any other Trait?

This has been answered here by myself using users comments as input, hope it can help others

Performing some test on the code by @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)
        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);
}

The problem Entity\Producto::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).

Let's say we have this entity:

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]

As you can see, I've defined two callbacks that will be triggered with the preRemove event and the postRemove event.

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 - 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.

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();
    }
}

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 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,
    ));
}

Hope it helps. It's a bit more cumbersome to implement than the first approach but much much better in terms of performance.

UPDATE

I'm going to try to explain a little bit more what's hapenning inside the catch block:

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 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 contains the id of the entity whose deletion raised the exception.

$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.

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.

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.

Good luck!

这篇关于使用preRemove / postRemove事件来获取哪些查询可以执行,哪些不能执行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆