我们说我有这样的实体
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()
之后不一样。
所以......
有人可以指出我正确的方向来管理这种情况吗?
如果引发两个相似的事件,为什么教条需要生成不同的对象?
答案 0 :(得分:3)
我已经建立了一个变通方法
解决此问题的最佳方法似乎是创建一个自定义EventSubscriber
,并将自定义Event
以编程方式分派到控制器更新操作中。
这样我就可以打破"循环并有一个工作代码。
为了使这个答案完整,我将报告一些代码片段,以澄清概念
//src/path/to/your/bundle/YourBundleNameEvents.php
final class YourBundleNameEvents
{
const FOO_EVENT_UPDATE = 'bundle_name.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无法保证正确处理 刷新操作此时的参照完整性。