使用preRemove / postRemove事件来获取可以执行哪些查询以及哪些查询不能执行

时间:2015-01-22 20:10:27

标签: php mysql symfony doctrine-orm

我脑子里已经有了这个问题一段时间了,现在我需要关于preRemove / postRemove事件的一些建议,因为我执行的查询基本上都是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 用户在this上说回答执行flush()是一个反对,并且还会达到应用程序性能,因为需要多次查询每次执行但使用这种方法我可以得到哪一个插入,哪一个没有。

使用第二种方法我会避免使用反方案并且会提高性能,但我如何知道插入哪个项目以及哪个项目没有?此外,如果任何查询失败,默认情况下Doctrine将执行回滚,因此不会插入任何查询。

那么,我可以使用preRemove / postRemove事件来获取哪些查询可以执行,哪些不能意味着插入了哪些值?

这个问题与thisthis关系密切相关。

现实生活中的例子

由于 @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应该没问题呢?应该被定义为任何其他特质吗?

我已经使用用户评论作为输入回答here,希望它可以帮助其他人

通过@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()返回一个没有删除值的空数组,为什么?

1 个答案:

答案 0 :(得分:1)

以下是我的表现。我并不是说这是最好的方法,如果有人知道更容易或更好的事情,我会第一个有兴趣学习它。

首先,这些是您可以使用的Doctrine events。为了简单起见,我将解释我是如何进行删除的。同样为了简单起见,我将使用静态数组(可以通过其他方式完成,我喜欢这个)和lifecycle callbacks。在这种情况下,回调将是非常简单的方法(这就是为什么可以使用它们而不是实现listener or subscriber)。

我们说我们有这个实体:

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

请注意回调以及静态数组和方法。每次通过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()的问题,它在这里工作得很好。

您可以优化代码,这样您就不需要将此方法添加到您的实体(甚至不包括生命周期回调)。例如,您可以利用订阅者捕获事件和使用静态方法的特殊类来跟踪那些没有失败的实体,失败的实体和没有机会的实​​体被删除/更新/插入。我向您推荐我提供的文档。它听起来有点复杂,不能用几行代码给你一个通用的答案,对不起,你不得不进一步调查。

我的建议是,您尝试使用虚假实体提供的代码并进行一些测试以完全了解其工作原理。然后,您可以尝试将其应用于您的实体。

祝你好运!