假设我有一个这样的实体
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/