symfony - preUpdate() sibling 管理到树 : how to break ->persist() recursion?

标签 symfony doctrine-orm

假设我有一个这样的实体

class FooEntity
{
  $id;
  
  //foreign key with FooEntity itself
  $parent_id;

  //if no parent level =1, if have a parent without parent itself = 2 and so on...
  $level;

  //sorting index is relative to level
  $sorting_index
}

现在我想在删除编辑上更改该实体的级别和sorting_index

所以我决定利用 Doctrine2 EntityListeners 并且我做了类似的事情

class FooListener
{
  public function preUpdate(Foo $entity, LifecycleEventArgs $args)
    {
        $em = $args->getEntityManager();
        $this->handleEntityOrdering($entity, $em);
    }
    
    public function preRemove(Foo $entity, LifecycleEventArgs $args)
    {
        $level = $entity->getLevel();
        $cur_sorting_index = $entity->getSortingIndex();
        $em = $args->getEntityManager();
        $this->handleSiblingOrdering($level, $cur_sorting_index, $em);
    }

    private function handleEntityOrdering($entity, $em)
    {
        error_log('entity to_update_category stop flag: '.$entity->getStopEventPropagationStatus());
        error_log('entity splobj: '.spl_object_hash($entity));
        //code to calculate new sorting_index and level for this entity (omitted)
        $this->handleSiblingOrdering($old_level, $old_sorting_index, $em);
        }
    }
    
    private function handleSiblingOrdering($level, $cur_sorting_index, $em)
    {   
        $to_update_foos = //retrieve from db all siblings that needs an update
        //some code to update sibling ordering (omitted)
        foreach ($to_update_foos as $to_update_foo)
        {
            $em->persist($to_update_foo);
        }
        $em->flush();
    }
}

这里的问题非常清楚:如果我保留 Foo 实体,则会引发 preUpdate() (进入 handleSiblingOrdering 函数)触发器并这会导致无限循环。

我的第一个想法是在我的实体中插入一个特殊变量以防止此循环:当我开始同级更新时,会设置该变量,并在执行更新代码之前进行检查。这对于 preRemove() 来说就像一个魅力,但对于 preUpdate() 则不然。
如果您注意到我正在记录 spl_obj_hash 来了解此行为。令我惊讶的是,我可以看到在 preRemove() 之后传递给 preUpdate() 的 obj 是相同的(因此设置“状态标志”就可以了),但是preUpdate() 之后传递给 preUpdate() 的对象不相同。

所以...

第一个问题

有人可以为我指明处理这种情况的正确方向吗?

第二个问题

如果引发两个相似事件,为什么学说需要生成不同的对象?

最佳答案

我找到了一个解决方法

解决此问题的最佳方法似乎是创建一个自定义 EventSubscriber,其中自定义 Event 以编程方式分派(dispatch)到 Controller 更新操作中。
这样我就可以“打破”循环并获得有效的代码。

为了使这个答案完整,我将报告一些代码片段以澄清概念

为您的 bundle 创建自定义事件

//src/path/to/your/bundle/YourBundleNameEvents.php 
final class YourBundleNameEvents
{
    const FOO_EVENT_UPDATE = 'bundle_name.foo.update';
}

这是一个特殊的类,除了为我们的包提供一些自定义事件之外不会执行任何操作

为 foo update 创建自定义事件

//src/path/to/your/bundle/Event/FooUpdateEvent
class FooUpdateEvent
{
  //this is the class that will be dispatched so add properties useful for your own logic. In my example two properties could be $level and $sorting_index. This values are setted BEFORE dispatch the event
}

创建自定义事件订阅者

//src/path/to/your/bundle/EventListener/FooSubscriber
class FooSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(YourBundleNameEvents::FooUpdate => 'handleSiblingsOrdering');
    }

    public function handleSiblingsOrdering(FooUpdateEvent $event)
    {
        //I can retrieve there, from $event, all data I setted into event itself. Now I can run all my own logic code to re-order siblings
    }
}

将您的订阅者注册为服务

//app/config/config.yml

services:
your_bundlename.foo_listener:
        class: Your\Bundle\Name\EventListener\FooListener
        tags:
            - { name: kernel.event_subscriber }

创建事件并将其分派(dispatch)到 Controller

//src/path/to/your/bundle/Controller/FooController
class FooController extends Controller
{
    public function updateAction()
    {
        //some code here
        $dispatcher = $this->get('event_dispatcher');
        $foo_event = new FooEvent();
        $foo_event->setLevel($level); //just an example
        $foo_event->setOrderingIndex($ordering_index); //just an examle
        
        $dispatcher->dispatch(YourBundleNameEvents::FooUpdate, $foo_event);
    }
}

替代解决方案

当然,上面的解决方案是最好的解决方案,但是,如果您有一个映射到数据库的属性可以用作标志,您可以直接从 preUpdate() 的 LifecycleEventArgs 访问它 通过调用事件

$event->getNewValue('flag_name'); //$event is an object of LifecycleEventArgs type

通过使用该标志,我们可以检查更改并停止传播

关于symfony - preUpdate() sibling 管理到树 : how to break ->persist() recursion?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30240230/

相关文章:

php - doctrine2 getRepository->findBy() 中的逻辑或

symfony - 在 Silex 上监听 kernel.request 事件?

php - 使用 PHP 将 XSL 文件渲染为 HTML

symfony - JMS序列化器如何手动处理序列化?

php - 如何在cloudControl上使用Symfony2和MongoDB?

mysql - 在 symfony2 Twig 模板中找不到实体

mysql - Doctrine - 自动插入行关联表

php - 尝试清除我的数据库时出现语法错误

symfony - 无法使用 MAMP 在 Symfony2 中为 Doctrine2 创建数据库连接(连接被拒绝)

Symfony2主义FindOneOrCreate