Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices - Delete links with Symfony 2

Tags:

rest

php

symfony

In Symfony 2, which is the best way to create a link to delete a record?

I can define a route to /entity/delete accepting only a DELETE method, but I don't know how to create a DELETE link from a template. The same goes for creating PUT links.

So, what do you do? Accept GET petition to delete a record? Is there any way to create a DELETE link?

like image 916
mHouses Avatar asked Feb 13 '14 07:02

mHouses


Video Answer


2 Answers

Symfony2 _method functionality description can be found in How to Define Route Requirements. Below is the solution I use.

Link

<a
    href="{{ path('my_delete_route_name', {'id': some_entity.id}) }}"
    class="as-form"
    data-method="delete"
    data-csrf="_token:{{ csrf }}"
>{{ 'delete'|trans({}, 'button') }}</a>

Re-Format the above link to inline after copy-paste

Attach onClick event listener:

JS

$('.as-form').on('click',function(){
    var $form = $('<form/>').hide();

    //form options
    $form.attr({
        'action' : $(this).attr('href'),
        'method':'post'
    })

    //adding the _method hidden field
    $form.append($('<input/>',{
        type:'hidden',
        name:'_method'
    }).val($(this).data('method')));

    //adding a CSRF if needs
    if ($(this).data('csrf'))
    {
        var csrf = $(this).data('csrf').split(':');
        $form.append($('<input/>',{
            type:'hidden',
            name:csrf[0]
        }).val(csrf[1]));
    }

    //add form to parent node
    $(this).parent().append($form);

    $form.submit();

    return false;
});

Controller

class MyCustomController extends Controller
{
    /**
    * @Route("/delete/{id}",name="my_delete_route_name")
    * @Method("DELETE")
    * @ParamConverter("entity", class="MyEntityClass")
    * @CsrfProtector(intention="my_csrf_intention", name="_token")
    */
    public function deleteAction(Request $request, $entity)
    {
        // do whatever you need
    }
}

Note

@CsrfProtector is my custom annotation that is used to validate the CSRF token passed in request before running the controller method.

like image 163
Vera Avatar answered Oct 13 '22 00:10

Vera


Anchors can fire only GET requests. And since GET requests can be cached and in some future (when you implement serious caching) might not reach your app code at all, it's a bad practice to use GET for changing anything that modifies state of your App. Here is what I do in my projects. With these two, you will be able to just do the following in your templates:

<a href="{{ path('my_entity_destroy') }}" {{ delete_link(myEntity) }}>Delete</a>

How it works

Assuming your entity delete path is: /my_entity, methods: [DELETE]

Principle is really simple. delete_link extension method will create data-attribute on anchor, so compiled anchor will look like this:

<a href="/my_entity" data-delete-link="3964">Delete</a>

Then, when somebody clicks on that link, javascript will catch that click, create a form and fire a DELETE request with id provided in data-delete-link attribute.

What makes it work

Here is what makes it possible, a LinkHelper Twig extension:

<?php

namespace Me\MyBundle\Twig;

class LinkHelperExtension extends \Twig_Extension
{
    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('delete_link', [$this, 'fnDeleteLink'], ['is_safe' => ['all']]),
        ];
    }

    public function fnDeleteLink($target)
    {
        if (is_object($target)) {
            $target = $target->getId();
        }
        return "data-delete-link='$target'";
    }

    public function getName()
    {
        return 'link_helper';
    }
} 

And I a JavaScript in my base template:

$(function () {
    var createForm = function (action, data) {
        var $form = $('<form action="' + action + '" method="POST"></form>');
        for (input in data) {
            if (data.hasOwnProperty(input)) {
                $form.append('<input name="' + input + '" value="' + data[input] + '">');
            }
        }

        return $form;
    };

    $(document).on('click', 'a[data-delete-link]', function (e) {
        e.preventDefault();
        var $this = $(this);

        var $form = createForm($this.attr('href'), {
            id: $this.attr('data-delete-link'),
            _method: 'DELETE'
        }).hide();

        $('body').append($form); // Firefox requires form to be on the page to allow submission
        $form.submit();
    });
});

Limitations

It only works for entities that have primary key named id. However, you can pretty easy modify this to suit your needs and support composite primary keys etc.

like image 31
Igor Pantović Avatar answered Oct 13 '22 01:10

Igor Pantović