preUpdate()兄弟姐妹管理到树:如何打破 - > persist()递归?

时间:2015-05-14 14:43:06

标签: symfony doctrine-orm

我们说我有这样的实体

public ActionResult ToggleProductPromoCodeIsActive(string promoCode, string productID, string countryCode)
    {
        var isActive = ToggleProductPromoCodeIsActive(promoCode, productID, countryCode);
        return Json(new { isActive = isActive }, JsonRequestBehavior.AllowGet);
    }

public ActionResult AddPromoCodeProperties(string promoCode, DateTime beginningDateTime, DateTime endDateTime, bool isActive, int? length, string countryCode, short? maximumRenewals)
    {
        int id = AddPromoCodeProperties(promoCode, beginningDateTime, endDateTime, isActive, length, countryCode, maximumRenewals);
        if (id != 0)
        {
            return Json(new { message = "Promo code properties have been succesfully added", id = id, promoCode = promoCode }, JsonRequestBehavior.AllowGet);
        }
        else
        {
            return Json(new { message = "Failed: The same promo code with the same country code has existed", promoCode = promoCode }, JsonRequestBehavior.AllowGet);
        }
    }


    // ----- Another line of code

    private int AddProductPromoCode(string promoCode, double discountPercentage, string productID, bool isActive, string countryCode, string paymentPageText, string finalProductID)
    {
        using (var provisioningContext = new ProvisioningEntities())
        {
            var productPromoCode = provisioningContext.ProductPromoCodes.SingleOrDefault(x => x.code == promoCode && x.productID == productID && x.countryCode == countryCode);
            if (productPromoCode == null)
            {
                try
                {
                    productPromoCode = new ProductPromoCode();
                    productPromoCode.code = promoCode;
                    productPromoCode.discountPercentage = discountPercentage;
                    productPromoCode.productID = productID;
                    productPromoCode.isActive = isActive;
                    productPromoCode.countryCode = countryCode;
                    productPromoCode.paymentPageText = paymentPageText;
                    productPromoCode.finalProductID = finalProductID;
                    provisioningContext.ProductPromoCodes.Add(productPromoCode);
                    provisioningContext.SaveChanges();
                    return productPromoCode.ID;
                }
                catch
                {
                    return 0;
                }
            }
            else
            {
                return 0;
            }
        }
    }

    private bool ToggleProductPromoCodeIsActive(string promoCode, string productID, string countryCode)
    {
        using (var provisioningContext = new ProvisioningEntities())
        {
            var productPromoCode = provisioningContext.ProductPromoCodes.SingleOrDefault(x => x.code == promoCode && x.productID == productID && x.countryCode == countryCode);
            productPromoCode.isActive = !productPromoCode.isActive;
            provisioningContext.Entry(productPromoCode).State = EntityState.Modified;
            provisioningContext.SaveChanges();
            return productPromoCode.isActive;
        }
    }

现在,我希望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 } delete更改此实体的级别和edit

所以我决定利用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之后obj传递给preUpdate()是相同的(所以设置一个"状态标志"很好)但是对象传递给{{ 1}} preRemove()之后不一样。

所以......

第一个问题

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

第二个问题

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

2 个答案:

答案 0 :(得分:3)

我已经建立了一个变通方法

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

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

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

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

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

//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的属性可以用作标志,你可以直接从LifecycleEventArgs preUpdate()事件中调用< / p>

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

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

答案 1 :(得分:1)

你在preUpdate中调用$ em-&gt; flush()做错了方法,我甚至可以说受到Doctrine操作的限制:http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-implementing-listeners

<强> 9.6.6。更新前的

  

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

     

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