【问题标题】:Symfony 2 - Delete Forms and CSRF TokenSymfony 2 - 删除表单和 CSRF 令牌
【发布时间】:2013-04-12 22:27:36
【问题描述】:

我有一个来自数据库的条目列表。我想在每一行的末尾都有一个“删除按钮”,这样用户就不必先去编辑/显示页面来删除条目。

我尝试使用 csrf 令牌创建一个隐藏的输入字段,如下所示:

return $this->createFormBuilder()
   ->getForm()
;

这将输出:

<div id="form">
   <input type="hidden" id="form__token" name="form[_token]" value="6c98ebfa9df07.....">
</div>

我将表单的其余部分放在树枝模板中,以便每个表单根据条目的 id 都有自己的操作路径。

不幸的是,twig 模板中只有第一个

{{ form_widget(delete_form) }}

将被渲染。

如何更频繁地使用此隐藏字段? 或者有没有办法以不同的方式做这件事?

感谢您的帮助

public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();

        $deleteForm = $this->createDeleteForms();

        $entities = $em->getRepository('IntranetServicesBundle:Laender')->findAll();

        return $this->render('IntranetServicesBundle:Laender:index.html.twig', array(
            'entities' => $entities,
            'delete_form' => $deleteForm->createView(),
        ));
    }


private function createDeleteForms()
{
    return $this->createFormBuilder()
        ->add('id', 'hidden')
        ->getForm()
    ;
}

【问题讨论】:

  • 你不能只创建一个像object/delete/{id} 这样的路由并让它去你的控制器的删除操作吗?然后,您只需要在这条路线上添加一个&lt;a&gt; 链接就可以了。或者你想要更复杂的东西?
  • 是的,可以,但我确实想要 csrf 保护....

标签: ajax symfony twig csrf


【解决方案1】:

您可以使用以下方式渲染单个令牌:

{{ form_widget(form._token) }}

或专门针对您的情况:

{{ form_widget(delete_form._token) }}

但是,我认为您最好制作一组表单并完全渲染每个表单:

在您的控制器中:

public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();
        $rep= $em->getRepository('IntranetServicesBundle:Laender')
                ->createQueryBuilder('l');

        var_dump($rep->getQuery()->getDql());
        $entities=$rep->getQuery()->getResult();

        $delete_forms  = array_map(
            function($element){ 
                return $this->createDeleteForm($element->getId());}
            ,$entities->toArray()
            );

        return $this->render('IntranetServicesBundle:Laender:index.html.twig'
                           , array(
                                 'entities'        => $entities,
                                 'delete_forms'    => $delete_forms
                           ));
    }


private function createDeleteForms($id)
{
    return $this->createFormBuilder(array('id' => $id)))
        ->add('id', 'hidden')
        ->getForm()
    ;
}

public function deleteAction(Request $request, $id)
{
    $form = $this->createDeleteForm($id);
    $form->bind($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        $entity = $em->getRepository('IntranetServicesBundle:Laender')
                     ->find($id);
        // this line might need to be changed to point to the proper repository

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Laender entity.');
        }

        $em->remove($entity);
        $em->flush();
    }

    return $this->redirect($this->generateUrl('laender_index'));
    // this line might need to be changed to point to the proper 
    // post-delete route
}

在你的树枝上做一些类似的事情:

{% for form in delete_forms %}{{form_widget(form)}}{% endfor %}

【讨论】:

  • 嗯。这会引发“FatalErrorException:错误:调用未定义的方法 Doctrine\ORM\Query::map()...”
  • 它也不起作用。 $entities 是一个对象,所以它不能与 array_map 一起使用:-(
  • 它是什么样的对象?我有点惊讶它不是一个集合。添加了一个编辑,以防它是一个集合(toArray() 调用)
  • FatalErrorException: Error: Call to undefined method Doctrine\ORM\Query::toArray() in 也许我会在没有 csrf 令牌和常规链接的情况下这样做...... :-(
  • 我不知道存储库调用返回了查询。唔。以上编辑。
【解决方案2】:

@Lighthart 的回答让我找到了正确的答案:

在您的控制器中生成一个表单视图数组并将其交给视图:

public function indexAction()
{
    $em = $this->getDoctrine()->getManager();

    $entities = $em->getRepository('AppBundle:Entity')->findAll();

    $delete_forms = array_map(
        function ($element) {
            return $this->createDeleteForm($element->getId())->createView();
        }
        , $entities
    );

    return $this->render('AppBundle:Entity:index.html.twig', array(
        'entities' => $entities,
        'delete_forms' => $delete_forms
    ));
}

现在您必须在您的视图中访问它。因此,您可以使用表单函数和特殊的循环变量:

{% extends '::base.html.twig' %}

{% block body %}
    {% for entity in entities %}
        {# Access the delete form for this entity using the loop index ... #}
        {% set delete_form = delete_forms[loop.index0] %}

        {# ... and display the form using the form tags. #}
        {{ form_start(delete_form) }}
            <input type="submit" value="Delete" />
        {{ form_end(delete_form) }}
    {% endfor %}
{% endblock %}

就是这样。

【讨论】:

    【解决方案3】:

    我遇到了类似的情况,我想在使用 csrf 保护时删除产品。我还想使用 ajax 来发出 DELETE 请求。

    针对 Symfony 3.x 测试

    所以,要做到这一点,这就是我的 viewindex.html 的样子:

    // html for displaying a products table with delete btn for each row
    // ...
    // Retrieve csrf token and store it somewhere in DOM (preferably outside table), 
    // We do this so that we can send the token via ajax later
    <span id="csrf_token" data-token="{{ csrf_token('form') }}"></span> 
    <script>
    // Make an ajax request on the on-click handler of our delete btn
    $.ajax({
        url: localhost/admin/product/4545,   // generated dynamically, the last segment being the ID of the item to be deleted.
        type: 'POST',
        data: {
            "_method": "DELETE",
            "form[_token]": $("#csrf_token").data("token")   // passed csrf token here
        },
        success: function(result) {
            // Do something with the result
       }
    });
    </script>
    

    如上所示,{{ csrf_token('form') }} 实际上是在 twig 中为您提供 csrf 令牌。

    我的控制器

    /**
     * Deletes a product entity.
     * @Route("/{id}", name="admin_product_delete")
     * @Method("DELETE")
     */
    public function deleteAction(Request $request, product $product)
        {
            $form = $this->createDeleteForm($product);
            $form->handleRequest($request);
            if ($form->isSubmitted() && $form->isValid()) {
                $em = $this->getDoctrine()->getManager();
                $em->remove($product);
                $em->flush($product);
                // you can return a json response here to your ajax callback if you'd like.
                return new JsonResponse(array('status' => 'deleted'));
            }
        // return new JsonResponse(array('status' => 'failed'));
    }
    
    /**
     * Creates a form to delete a product entity.
     * @param product $product The product entity
     * @return \Symfony\Component\Form\Form The form
     */
    private function createDeleteForm(product $product)
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl('admin/product/{id}', array('id' => $product->getId())))
            ->setMethod('DELETE')
            ->getForm()
        ;
    }
    

    这应该会按预期删除所需的行!

    【讨论】:

      【解决方案4】:

      @Phidelux 的回答让我想到了以下解决方案:

      在树枝中我创建了删除表单:

      <form method="post" action="{{ path('page_delete', {'id': page.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
          <input type="hidden" name="_method" value="DELETE">
          <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ page.id) }}">
          <button type="submit" class="btn btn-icon">
              <i class="far fa-trash-alt"></i>
          </button>
      </form>
      

      创建列表视图的代码是:

      /**
           * @Route("/", name="page-list", methods={"GET"})
           */
          public function index(PageRepository $pageRepository): Response
          {
              $pages = $pageRepository->findAll();
              $delete_forms = array_map(
                  function ($element) {
                      return $this->render('admin/page/_delete_form.html.twig', [
                          'page' => $element,
                      ]);
      
                  }
                  , $pages
              );
              return $this->render('admin/page/index.html.twig', [
                  'pages' => $pages,
                  'delete_forms' => $delete_forms
              ]);
          }
      

      然后在我的列表视图中我添加了删除按钮:

      {% for key, page in pages %}
      ...
          {{ delete_forms[key].content | raw }}
      {% endfor %}
      

      在我的编辑页面表单中我可以使用:

      {{ include('admin/sjabloon/_delete_form.html.twig') }}
      

      这解决了删除时不允许消息的方法

      【讨论】:

        猜你喜欢
        • 2013-12-02
        • 2014-10-06
        • 2018-05-10
        • 1970-01-01
        • 2013-04-14
        • 1970-01-01
        • 2018-03-01
        • 2020-03-30
        • 2020-05-19
        相关资源
        最近更新 更多