尝试保存其他实体时,Doctrine事件侦听器中的无限循环

时间:2015-06-01 14:33:31

标签: php symfony orm doctrine-orm doctrine

我希望每次保存新的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表单,除了输入地名外,我还可以添加/删除/编辑到其他Place的距离集合。

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

1 个答案:

答案 0 :(得分:2)

你不应该在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()方法。   在此>事件中永远不允许对更新实体的关联进行更改,因为在刷新操作的此时,Doctrine无法保证正确处理引用完整性。

此处描述了类似的sitaution preUpdate() siblings manage into tree: how to break ->persist() recursion?

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