【问题标题】:preUpdate() siblings manage into tree: how to break ->persist() recursion?preUpdate() 兄弟姐妹管理成树:如何打破 ->persist() 递归?
【发布时间】:2015-05-14 14:43:05
【问题描述】:

假设我有一个这样的实体

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
}

现在我想在deleteedit 上更改此实体的级别和sorting_index

所以我决定利用@​​987654325@ 并且我做了类似的事情

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() 的对象不是一样的。

所以...

第一个问题

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

第二个问题

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

【问题讨论】:

    标签: symfony doctrine-orm


    【解决方案1】:

    我已经找到了解决方法

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

    为了使这个答案完整,我将报告一些 sn-p 代码,以澄清 che 概念

    为您的捆绑包创建自定义事件

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

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

    为 foo 更新创建自定义事件

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

    创建事件并将其分派到控制器中

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

    替代解决方案

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

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

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

    【讨论】:

      【解决方案2】:

      您在 preUpdate 中调用 $em->flush() 是错误的做法,我什至可以说受到 Doctrine 操作的限制:http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-implementing-listeners

      9.6.6。更新前

      PreUpdate 是使用最严格的事件,因为它被称为 就在为内部实体调用更新语句之前 EntityManager#flush() 方法。

      永远不允许对更新实体的关联进行更改 这个事件,因为 Doctrine 不能保证正确处理 刷新操作时的引用完整性。

      【讨论】:

      • 这不是答案;应该是评论。顺便说一句,我找到了另一种解决方案(我会尽快发布),你给我一些关于该页面的指示:我将尝试继续使用 preUpdate() 来测试我的想法是否仍然有效。我还想强调一下,我没有更改实体之间的任何关联,因此即使这不是最佳方法,我几乎可以肯定它仍然是有效的。
      • 我相信它至少可以回答您的问题 >>有人可以为我指出正确的方向来管理这种情况吗?如果还不够,那么对不起
      • 是的,很好的解决方案,实际上我有一个类似的服务,我需要在控制器中调用它来重新排序实现 TreePosition 接口的实体。但这与调度程序的情况相同,我们需要额外启动排序过程,而不是通过 preUpdate 进行全自动调用,它将在任何控制器的每个更新操作中触发,并添加更优雅的解决方案。所以我无法在我的回答中为您提供完整的解决方案:) 刚刚通过了我之前已经面临的问题,快乐编码!
      猜你喜欢
      • 2019-05-06
      • 2019-08-29
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多