【问题标题】:Infinite loop in Doctrine event listener when trying to save additional entity尝试保存其他实体时,Doctrine 事件侦听器中的无限循环
【发布时间】:2015-08-15 01:47:37
【问题描述】:

我希望每次保存一个新的Distance 实体(从 Place_A 到 Place_B)时,反向距离(从 Place_B 到 Place_A)gets inserted too into the DB

我的问题是以下监听器无限循环(因此是计数器):

class Listener
{
    public $count;

    public function prePersist(LifecycleEventArgs $eventArgs)
    {
        if ($this->count > 5) {
            die();
        }

        $entity = $eventArgs->getEntity();

        if ($entity instanceof Distance) {
            // $this->created = microtime(true) in Distance's constructor
            echo 'Entity created at ' . $entity->created;

            if ($entity->isReverse) {
                echo " is reverse\n";
            } else {
                echo " is not reverse\n";
                $this->count++;

                $reverse = new Distance();
                $reverse->setOrigin($entity->getDestination());
                $reverse->setDestination($entity->getOrigin());
                $reverse->set($entity->getMiles());
                $reverse->isReverse = true;

                $em = $eventArgs->getEntityManager();
                $em->persist($reverse);
                $em->flush();
            }
        }
    }
}

输出:

Entity created at 1433168310.8787 is not reverse
Entity created at 1433168310.9073 is reverse
Entity created at 1433168310.8787 is not reverse
Entity created at 1433168310.9078 is reverse
Entity created at 1433168310.8787 is not reverse
Entity created at 1433168310.908 is reverse
Entity created at 1433168310.8787 is not reverse
Entity created at 1433168310.9084 is reverse
Entity created at 1433168310.8787 is not reverse
Entity created at 1433168310.9087 is reverse
Entity created at 1433168310.8787 is not reverse

就像原始实体(创建时间以 8787 结尾)被无限次保留。

以防万一,如果我删除对$em->flush 的调用,我会正确得到以下输出:

Entity created at 1433167824.2552 is not reverse
Entity created at 1433167824.2947 is reverse

但随后出现异常,表示没有参数绑定到插入查询。 Symfony 的分析器证实了这一点:

INSERT INTO Distance (
    miles, origin_id, destination_id
) 
VALUES 
(?, ?, ?)
Parameters: { }

我想了解为什么我的监听器不能按我的预期工作,以及如何解决它。


根据要求,这里还有一些代码。一切都来自Place 表单,除了输入地名之外,我还可以添加/删除/编辑与其他Places 的距离集合。

// PlaceController::updateAction
public function updateAction(Request $request, $id)
{
    $em = $this->getDoctrine()->getManager();

    $entity = $em->getRepository('MyBundle:Place')->find($id);
    if (! $entity) {
        throw $this->createNotFoundException('Unable to find Place entity.');
    }

    $deleteForm = $this->createDeleteForm($id);
    $editForm = $this->createForm(new PlaceType(), $entity, array(
        'action' => $this->generateUrl('update_place', array('id' => $entity->getId())),
        'method' => 'PUT'
    ));
    $editForm->add('submit', 'submit', array('label' => 'panel.button.save'));

    $editForm->handleRequest($request);

    if ($editForm->isValid()) {
        $em->flush();

    return array(
        'entity' => $entity,
        'form' => $editForm->createView(),
        'delete_form' => $deleteForm->createView(),
    );
}

// PlaceType::buildForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $Place = $builder->getData();

    $builder
        ->add(
            'name',
            'text',
            [
                'label' => 'object.place.name'
            ]
        )
        ->add(
            'distancesTo',
            'collection',
            [
                'label' => 'object.place.distance.plural',
                'type' => new DistanceType(),
                'by_reference' => false,
                'allow_add' => true,
                'allow_delete' => true,
                'options' => [
                    'required' => false,
                    'origin' => $Place->getId() ? $Place : null
                ]
            ]
        );
}

【问题讨论】:

  • 首先你不应该在那里调用$em->flush(),因为在 prePersist 块中刷新是不好的形式,因为实体管理器可能会在原始位置被刷新坚持。
  • 你能在原始实体被持久化的地方添加代码吗?
  • @Decave 保留原始距离实体的代码不是我的。正如我在编辑中解释的那样,它在持久保存父 Place 实体(关联上的 cascade:{"persist"})时会自动发生。

标签: php symfony orm doctrine-orm doctrine


【解决方案1】:

你不应该在 prePersist 中使用 $em->flush(),它受到 Doctrine 的限制:http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-implementing-listeners

有关于preUpdate的信息,但同样的情况(循环)适用于prePesist调用

9.6.6。更新前

PreUpdate 是使用最严格的事件,因为它被称为 就在为内部实体调用更新语句之前 EntityManager#flush() 方法。 在这个 >event 中永远不允许对更新实体的关联进行更改,因为 Doctrine 不能保证在刷新操作的此时正确处理引用的完整性。

这里描述了类似的情况preUpdate() siblings manage into tree: how to break ->persist() recursion?

因此您也可以执行类似的方式:创建自定义事件,创建自定义事件订阅者,您将在其中创建反向实体并在控制器操作中调度该事件订阅者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-11
    相关资源
    最近更新 更多