12

I'm a relative beginner with Laravel (using version 5.2.3) and have been working through tutorials on Laracasts and then doing a bit of my own experimenting.

I successfully set up a route that fetches an item from a table by its ID, as shown below

Route::get('/wiseweasel/{id}', 'WiseweaselController@singleArticle');

For simplicity, the controller simply dd's the article

public function singleArticle($id)
{
  $article = ww_articles::find($id);
  dd($article);
}

This works absolutely fine - I visit eg /wiseweasel/2 and get the contents of the record with id2.

So, I then wanted to use the slug field from the record instead of the id. Since I know the ID method was working, I've tried just modifying this route and controller (also tried creating anew, neither worked) So I now have:

Route::get('/wiseweasel/{slug}', 'WiseweaselController@singleArticle');

and

public function singleArticle($slug)
{
  $article = ww_articles::find($slug);
  dd($article);
}

The slug for the second record is "secondarticle". So, visiting the url /wiseweasel/secondarticle, I would expect to see the same record as previously dd'd out. Instead, I end up with null.

Even more oddly, using the original id route (/wiseweasel/2) still returns the record... when I have removed all trace of this from the routes and controller, so I would expect this to fail...

This is making me wonder if this could be some odd caching issue? I've tried

php artisan route:clear

in case the route was being cached. I've also tried restarting both Apache and MySql (I'm using XAMMP for both).

Still no luck though... not sure if I've misunderstood how something works or what's going on... so if anyone has any suggestions as to what I might have done wrong, or anything to try, I would be very grateful! :)

Marcin Nabiałek
  • 109,655
  • 42
  • 258
  • 291
Roxy Walsh
  • 659
  • 1
  • 10
  • 16

4 Answers4

44

You also have the option of using Route Model Binding to take care of this and inject the resolved instance into your methods.

With the new implicit Route Model Binding you can tell the model what key it should use for route binding.

// routes
Route::get('/wiseweasel/{article}', 'WiseweaselController@singleArticle');


// Article model
public function getRouteKeyName()
{
    return 'slug';
}

// controller
public function singleArticle(Article $article)
{
    dd($article);
}

Laravel Docs - Route Model Binding

lagbox
  • 48,571
  • 8
  • 72
  • 83
18

Laravel won't automatically know that for slug it should search record in different way.

When you are using:

$article = ww_articles::find($slug);

you are telling Laravel - find record of www_articles by ID. (no matter you call this id $slug).

To achieve what you want change:

$article = ww_articles::find($slug);

into

$article = ww_articles::where('slug', $slug)->first();

This will do the trick (for slug put the name of column in table in database). Of course remember that in this case slug should be unique in all records or you won't be able to get all the slugs.

Marcin Nabiałek
  • 109,655
  • 42
  • 258
  • 291
  • Thanks Marcin, that worked perfectly! That makes sense, since I guess laravel has no way of knowing what $id or $slug or anything means in this case, since it's just a placeholder for whatever is fed in through the url. Thank you for your explanation too :) When you say that the slug must be unique, would this be the case if I firstly selected a category and then the slug - presumably in this case the slug would just need to be unique in the category? (though this would still be an issue if ever trying to access with no category I guess) – Roxy Walsh Dec 28 '15 at 13:48
  • I mean each category should have unique slug. If 2 categories will have same slug it will be a problem because you won't be able to determine which category you want to get getting just slug. So when filling slug for new category you need to make sure it's unique – Marcin Nabiałek Dec 28 '15 at 13:49
  • Ok, I understand, thanks :) They should always be unique anyway (the generic "first-article" slug was just for testing!) – Roxy Walsh Dec 28 '15 at 16:25
7

Maybe it's a bit late for the answer but there is another way to keep using find method and use slug as your table identifier. You have to set the protected $primaryKey property to 'slug' in your model.

class ww_articles extends Model
{
    protected $primaryKey = 'slug';
    ...
}

This will work because find method internally uses the getQualifiedKeyName method from Model class which uses the $primaryKey property.

Sergio Guillen Mantilla
  • 1,460
  • 1
  • 13
  • 20
  • Thanks Sergio, that's useful to know! In this case I think I'll likely keep the ID as the primary key, but in other cases it would be handy to change it to some other field :) (my problem was primarily a result of not fully understanding the find method so Marcin's answer explained it well for me, but this could well have been a good solution also! ) – Roxy Walsh Jan 07 '16 at 12:20
0

If you have both routes like this

Route::get('/wiseweasel/{id}', 'WiseweaselController@singleArticle');
Route::get('/wiseweasel/{slug}', 'WiseweaselController@singleArticle');

it will always use the first one. Obviously, there is no id 'secondarticle', so it returns null (although in this case it doesn't matter, they both point to the same method).

The reason is route will search through possible routes till it finds a matching, which is always the one with {id}. Why? You're not telling Route that {id} must match an integer!

You can make sure {id} is understood as an integer, however I suggest using urls like this is a better option

/wiseweasel/{id}/{slug?}

Another suggestion. Do not use names such as xx_articles for a model, but Article instead. This way you can use the new implicit route binding. So using implicit route binding your url would look like this (assuming your model is called Article)

Route::get('/wiseweasel/{article}', 'WiseweaselController@singleArticle');
Bojan Kogoj
  • 5,321
  • 3
  • 35
  • 57
  • Thanks Bojan - I had only the one route, as I'd simply modified the "id" route to create the "slug" route. But that is a good point to bear in mind as the two in your example are basically the same route, as far as laravel is concerned! I've used the ww_ prefix as I may have multiple different articles tables (containing articles for different areas of the site), so the model matches the table name. Or is it more to use the non-plural version for the model name? – Roxy Walsh Dec 28 '15 at 14:15
  • They shouldn't be plural, no. Also if you have different types you can deal with that differently, check out morphTo – Bojan Kogoj Dec 28 '15 at 16:39
  • Looks like it could come in useful... I'll probably come back to it once I've got to grips with the basics a little more, but thanks! :) – Roxy Walsh Dec 28 '15 at 20:48