Symfony 2 - удаление форм и токена CSRF

У меня есть список записей из базы данных. Я хотел бы иметь кнопку «Удалить» в конце каждой строки, чтобы пользователю не приходилось сначала переходить на страницу редактирования / отображения, чтобы удалить запись.

Я попытался создать скрытое поле ввода с токеном csrf следующим образом:

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

это выведет:

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

Остальную часть формы я поместил в шаблон веточки, чтобы каждая форма имела свой собственный путь действия в соответствии с идентификатором записи.

к сожалению в шаблоне веточки только первый

{{ 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()
    ;
}

person ldrocks    schedule 19.04.2013    source источник
comment
Разве вы не можете просто создать маршрут типа object/delete/{id} и заставить его перейти к действию удаления вашего контроллера? Затем вам просто нужно добавить <a> ссылку с этим маршрутом, и все будет хорошо. Или вы хотите чего-то более изощренного?   -  person cheesemacfly    schedule 19.04.2013
comment
да, это сработает, но мне нужна защита csrf ....   -  person ldrocks    schedule 19.04.2013


Ответы (4)


Вы можете отображать отдельные токены с помощью:

{{ 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 %}
person Lighthart    schedule 19.04.2013
comment
Хм. это вызывает исключение FatalErrorException: Error: Call to undefined method Doctrine \ ORM \ Query :: map () ... - person ldrocks; 21.04.2013
comment
это тоже не работает. $ entity - это объект, поэтому он не может работать с array_map :-( - person ldrocks; 22.04.2013
comment
что это за объект? Я немного удивлен, что это не сборник. Добавлена ​​правка в случае, если это коллекция (для вызова toArray ()) - person Lighthart; 22.04.2013
comment
FatalErrorException: Ошибка: вызов неопределенного метода Doctrine \ ORM \ Query :: toArray () в Может быть, я просто сделаю это без токена csrf и обычной ссылки .... :-( - person ldrocks; 22.04.2013
comment
Я не знал, что вызов репозитория вернул запрос. Хм. Отредактировано выше. - person Lighthart; 22.04.2013
comment
FatalErrorException: Ошибка: вызов функции-члена toArray () для не-объекта в - person ldrocks; 22.04.2013
comment
Дох. Введите $ entity = $ rep- ›getQuery-› getResult (); должен получить getQuery (). отредактированный - person Lighthart; 22.04.2013
comment
попробуйте var_dumping dql и посмотрите, правильный ли запрос. Отредактировано выше - person Lighthart; 22.04.2013
comment
строка (54) ВЫБРАТЬ l ИЗ Интранета \ ServicesBundle \ Entity \ Laender l - person ldrocks; 22.04.2013
comment
Это объект в вашей базе данных? Все свидетельства указывают на то, что у вас нет этой сущности. - person Lighthart; 22.04.2013
comment
Я не уверен, что понимаю, что вы имеете в виду: / - person ldrocks; 22.04.2013
comment
Запрос не должен завершиться ошибкой. В вашем объяснении чего-то не хватает или что-то настроено неправильно. - person Lighthart; 22.04.2013

Ответ @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 %}

Вот и все.

person Phidelux    schedule 02.12.2015

Я столкнулся с аналогичной ситуацией, когда я хотел удалить продукт при использовании защиты csrf. Я также хотел использовать ajax для выполнения запроса DELETE.

Протестировано для Symfony 3.x

Итак, вот как выглядело мое представление, index.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') }} - это то, что на самом деле дает вам токен 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()
    ;
}

И это должно удалить требуемую строку, как и ожидалось!

person Niket Pathak    schedule 25.03.2017

Ответ @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') }}

Это решило метод не разрешенного сообщения при удалении

person DNAklik    schedule 03.06.2021