26

I will destroy my user with a HTML link, but it doesn't seem to generate the correct link, what am i doing wrong?

public function destroy($id)
{
    //Slet brugeren
    $e = new User($id);
    $e->destroy();

    //Log også brugeren ud
    Auth::logout();

    //redrect til forsiden
    Redirect::to("users/create");
}

In my view i call this {{URL::action('UserController@destroy', array($user->id))}}

helloworld
  • 475
  • 2
  • 6
  • 11

9 Answers9

33

Update 08/21/2017 for Laravel 5.x

The question asks about Laravel 4, but I include this in case people looking for Laravel 5.x answers end up here. The Form helper (and some others) aren't available as of 5.x. You still need to specify a method on a form if you are doing something besides GET or POST. This is the current way to accomplish that:

<form action="/foo/bar" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
    <!-- other inputs... -->
</form>

You can also use {{ method_field('PUT') }} instead of writing out the hidden _method input.

See https://laravel.com/docs/5.4/routing#form-method-spoofing

Original Answer for Laravel 4

I think when you click the link, it is probably sending a GET request to that end point. CRUD in Laravel works according to REST. This means it is expecting a DELETE request instead of GET.

Here's one possibility from a tutorial by Boris Strahija.

    {{ Form::open(array('route' => array('admin.pages.destroy', $page->id), 'method' => 'delete')) }}
        <button type="submit" class="btn btn-danger btn-mini">Delete</button>
    {{ Form::close() }}

This way, you send the request in a form with the DELETE method. The article explains why a traditional link won't work:

You may notice that the delete button is inside a form. The reason for this is that the destroy() method from our controller needs a DELETE request, and this can be done in this way. If the button was a simple link, the request would be sent via the GET method, and we wouldn’t call the destroy() method.

salsbury
  • 2,777
  • 1
  • 19
  • 22
  • 1
    Is it possible to create this button without using the Laravel's form builder? – lozadaOmr Sep 01 '14 at 11:39
  • @IozadaOmr Looks like the docs explain how Laravel accomplishes this: http://laravel.com/docs/html#opening-a-form. The pertinent part is this: "Note: Since HTML forms only support POST and GET, PUT and DELETE methods will be spoofed by automatically adding a _method hidden field to your form." So seems like on the server side, they must read the value of that input and use the method specified. I guess there's nothing stopping you from doing the same. – salsbury Sep 03 '14 at 16:09
  • For the Laravel 5 equivalent see [my answer](http://stackoverflow.com/questions/19643483/crud-laravel-4-how-to-link-to-destroy#28797820) below. – DutGRIFF Mar 01 '15 at 19:12
18

An cool ajax solution that works is this:

function deleteUser(id) {
    if (confirm('Delete this user?')) {
        $.ajax({
            type: "DELETE",
            url: 'users/' + id, //resource
            success: function(affectedRows) {
                //if something was deleted, we redirect the user to the users page, and automatically the user that he deleted will disappear
                if (affectedRows > 0) window.location = 'users';
            }
        });
    }
}

<a href="javascript:deleteUser('{{ $user->id }}');">Delete</a>

And in the UserController.php we have this method:

public function destroy($id)
{
    $affectedRows  = User::where('id', '=', $id)->delete();

    return $affectedRows;
}

 

Jocelyn
  • 1,297
  • 12
  • 20
paulalexandru
  • 9,218
  • 7
  • 66
  • 94
  • 1
    If you get a error about token mismatch exception (Laravel 5) you should add the $.ajaxSetup line to send the _token field. – paulalexandru Feb 21 '16 at 13:33
15

Another "clean" solution is to make it the Rails way as described here:

  1. Create a new .js file in public and write this function:

    $(function(){
       $('[data-method]').append(function(){
            return "\n"+
            "<form action='"+$(this).attr('href')+"' method='POST' style='display:none'>\n"+
            "   <input type='hidden' name='_method' value='"+$(this).attr('data-method')+"'>\n"+
            "</form>\n"
       })
       .removeAttr('href')
       .attr('style','cursor:pointer;')
       .attr('onclick','$(this).find("form").submit();');
    });
    
  2. Don't forget to include the .js file in your template after including jQuery.

  3. Use classic link_to() or link_to_method() functions to create links to delete records. Just remember to include the "data-method"="DELETE" parameter:

    {{ link_to_route('tasks.destroy', 'D', $task->id, ['data-method'=>'delete']) }}
    

What I like about this that it seems much cleaner than bloating your code with Form::open(); in blade templates.

  • Works really well. Thank you. It cleans the code much more, mostly when you need it 3-4 times on the same page. – Philippe Gilbert Sep 11 '14 at 15:24
  • @PhilippeGilbert What is the use of 'D' there? – wobsoriano Jun 09 '15 at 12:07
  • I prefer to replace `$(this).attr('data-method')` to `$(this).data('method')` – Scofield Nov 12 '15 at 17:11
  • it's also worth considering just pulling in Rails UJS - it's framework agnostic and it no longer depends on jQuery. It adds support for a whole help of helpful `data` attributes that make handling this stuff really easy. It's available on NPM as `rails-ujs`- https://www.npmjs.com/package/rails-ujs – Dwight Oct 15 '17 at 03:42
8

This is an old questions, but I was just looking for a quick answer and am not satisfied with any of these. What I would suggest to anyone with this same problem is create a new route. Worrying too much about crud compliance is silly, because there is no such thing over HTML; any solution is just shoe-horned to fit, whether it's a hidden form field or a get route.

So, in your routes, you likely have something like this:

Route::resource('users', 'UsersController'); The problem with this is that the only way to get to the "destroy" method is to sent a post request which has a hidden input named "_method" and a value of "DELETE".

Simply add under that line: Route::get('users/{id}/destroy',['as'=>'users.delete','uses'=>'UsersController@destroy']);

Now you have a route you can access from HTML::linkRoute, Route::url, or whatever method you please.

For example: {{ HTML::linkRoute( 'users.delete', 'Delete' , [ 'id' => $user->id ]) }}

EDIT

I want to clarify, though I have explained why it's somewhat silly to bend over backward to fit crud compliance, it is still true that your app will be more secure if changes are made only through POST requests.

  • Using GET for deletes is not very wise – Jarek Tkaczyk Dec 19 '14 at 20:35
  • That's the sort of point where you're following a procedure over actually examining the problem you're facing. There is no such thing as a delete method over HTTP anyway, so strict compliance is all just pretending anyway. – theaceofthespade Dec 20 '14 at 21:27
  • Plus, it actually achieves the goal of the question. – theaceofthespade Dec 20 '14 at 21:29
  • Actually, I should say, there is no Put or Delete in HTML; HTTP supports them just fine. – theaceofthespade Dec 20 '14 at 21:35
  • Well, HTTP defines them so obviously.. I didn't say it's against a procedure, standard or whatever, just it was not wise. Yes, it answers the question in a way, but if someone asks 'wrong' question, then it's more of help that you point it out. – Jarek Tkaczyk Dec 21 '14 at 14:51
  • Well do you want to articulate why it's not wise? The only reasons I've ever seen were in reference to compliance, and fear that it makes it easier to delete items by accidentally hitting url's, which is a non-problem if other things are done correctly. – theaceofthespade Dec 22 '14 at 00:09
  • Not wise because no CSRF protection with a GET request. Can't stress how unwise this is. – shxfee Aug 17 '15 at 21:03
  • Theoretically there's nothing that stops you from running CSRF protection on a GET (or any type) of request; it's just a routing filter. You just have to include the token in the link. Of course including those in GET requests is frowned upon. I should clarify, as it got lost in translation above, I DO support using POST for this if/whenever possible. – theaceofthespade Aug 19 '15 at 01:02
8

For those looking to create the delete button in Laravel 5:

{!! Form::open(['action' => ['UserController@destroy', $user->id], 'method' => 'delete']) !!}
  {!! Form::submit('Delete', ['class'=>'btn btn-danger btn-mini']) !!}
{!! Form::close() !!}

This is similar to Tayler's answer but we use the new blade escape tags ( {!! and !!} ), we use the Form facade to generate the button and we use a more elegant approach to link to the controller.

In Laravel < 5 the Forms & HTML package was pulled in automatically. In Laravel 5 we must add this package to composer.json:

...
"required": {
  ...
  "laravelcollective/html": "^5.1"
}
...

Now add the Service Provider and Alias in config/app.php:

...
'providers' => [
  ...
  Collective\Html\HtmlServiceProvider::class,
],

'aliases' => [
  ...
  'Form' => Collective\Html\FormFacade::class,
  'Html' => Collective\Html\HtmlFacade::class,
],

The above will output

<form method="POST" action="https://yourdomain.tld/users/1" accept-charset="UTF-8">
  <input name="_method" type="hidden" value="DELETE">
  <input name="_token" type="hidden" value="xxxCrAZyT0K3nsTr!NGxxx">
  <input class="btn btn-danger btn-mini" type="submit" value="Delete">
</form>

If you are using a different form builder just make sure it generates something similar to the above.

DutGRIFF
  • 5,103
  • 1
  • 33
  • 42
  • If you're using bootstrap, the class is `.btn-xs`, not `.btn-mini`. I'm glad that you mentioned that HTML package is no longer included by default in Laravel. – Ivanka Todorova Feb 03 '16 at 08:19
4

Want to send a DELETE request when outside of a form?

Well, Jeffrey Way created a nice javascript that creates a form for you and to use it you only need to add data-method="delete" to your delete links.

To use, import script, and create a link with the data-method="DELETE" attribute.

script :

(function() {

  var laravel = {
    initialize: function() {
      this.methodLinks = $('a[data-method]');

      this.registerEvents();
    },

    registerEvents: function() {
      this.methodLinks.on('click', this.handleMethod);
    },

    handleMethod: function(e) {
      var link = $(this);
      var httpMethod = link.data('method').toUpperCase();
      var form;

      // If the data-method attribute is not PUT or DELETE,
      // then we don't know what to do. Just ignore.
      if ( $.inArray(httpMethod, ['PUT', 'DELETE']) === - 1 ) {
        return;
      }

      // Allow user to optionally provide data-confirm="Are you sure?"
      if ( link.data('confirm') ) {
        if ( ! laravel.verifyConfirm(link) ) {
          return false;
        }
      }

      form = laravel.createForm(link);
      form.submit();

      e.preventDefault();
    },

    verifyConfirm: function(link) {
      return confirm(link.data('confirm'));
    },

    createForm: function(link) {
      var form = 
      $('<form>', {
        'method': 'POST',
        'action': link.attr('href')
      });

      var token = 
      $('<input>', {
        'type': 'hidden',
        'name': 'csrf_token',
          'value': '<?php echo csrf_token(); ?>' // hmmmm...
        });

      var hiddenInput =
      $('<input>', {
        'name': '_method',
        'type': 'hidden',
        'value': link.data('method')
      });

      return form.append(token, hiddenInput)
                 .appendTo('body');
    }
  };

  laravel.initialize();

})();
chebaby
  • 7,362
  • 50
  • 46
  • For me, this is the most clean solution for this. This technique is called UJS or Unobtrusive JavaScript – Armando Mar 02 '15 at 17:40
  • It's really help. You save the day. – akbarbin Jul 28 '15 at 14:49
  • While this method is unobtrusive, it will not degrade gracefully. If the user has js turned off he ll not be able to use the functionality. I guess we can say that this is a problem of the past though. – shxfee Aug 18 '15 at 13:33
  • For those using Laravel 5.x, the token is now called `_token` instead of `csrf_token`, so update that on the line: `'name': 'csrf_token'` – dKen Nov 30 '15 at 15:23
  • Also, you'll need to figure out a way of giving the token to the script. I added the token to the link (similar to how the method is set): `data-token="{{ csrf_token() }}"`, and switched the line: `'value': ''` for `'value': link.data('token')'` – dKen Nov 30 '15 at 15:26
2

For those looking to create delete button using anchor tag in laravel 5.

{!! Form::open(['action' => ['UserController@destroy', $user->id], 'method' => 'DELETE', 'name' => 'post_' . md5($user->id . $user->created_at)]) !!}
    <a href="javascript:void(0)" title="delete" onclick="if (confirm('Are you sure?')) { document.post_<?= md5($user->id . $user->created_at) ?>.submit(); } event.returnValue = false; return false;">
        <span class="icon-remove"></span>
    </a>
{!! Form::close() !!}
Konsole
  • 3,447
  • 3
  • 29
  • 39
  • Don't mix js with HTML - its only matter of time, the message is gonna change and search+replace is NOT the programmers best friend. – Srneczek Dec 26 '15 at 19:18
  • @Srneczek The idea here is to create delete button using anchor tag. I agree that message gonna change but it can be made dynamic in various ways. – Konsole Jan 13 '16 at 09:51
  • Yea I was little bit pedantic with that comment and I know it but hey, you gotta be pedantic as programmer especially about good practice else you will end up with unreadable code nobody can understand. And this site is usually visited by beginners and they should be at least aware of the good practice since the very beginning. Many ppl will just copy&paste your code and make some little changes. I don't like just "working" code. – Srneczek Jan 13 '16 at 15:10
1

I tried your code, used it like this and worked:

    <a href="{{URL::action('UserController@destroy',['id'=>$user->id]) }}" 
onclick=" return confirm('Are you sure you want to delete this?')" 
class="btn btn-default">
   DELETE     </a>

I think that the only problem is in your array:

['id'=>$user->id]
Laura Chesches
  • 2,493
  • 1
  • 19
  • 15
0
{{Form::open(['method'=>'delete','action'=>['ResourceController@destroy',$resource->id]])}}
 <button type="submit">Delete</button>                      
{{Form::close()}}   
Neo
  • 11,078
  • 2
  • 68
  • 79