Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

robjbrain's avatar

Best Practice: Managing URLs

How do different people manage urls and within their application? It's one thing that always winds up a bit messy for me. I've listed the different approaches I can think of below, but really this a question not a guide, so i'd appreciate other peoples suggestions and input.

Our Goals Should Be:

1) Each URL to be defined in one place so it's easy to change if we ever change the structure of the website.

2) We want URL generation to be simple for use in the templating engine

3) We want to be flexible to handle different types of URLs and follow SEO best practises

In Models

You could do something like this:

$article = Article::find(1);
echo $article->link();

But then what about urls that are not about a specific model, such as the domain.com/articles section? How do you manage those URLs?

Static Classes

You could use static classes like this:

echo URL::articlesSection()
echo URL::article($article)
echo URL::articleCategory($category)

But this file is going to get big very quickly. Plus it becomes inflexible, generating a URL to a category is now dependent upon having a $category object. What if you only have the id and the title of the category? That is enough to generate the URL "domain.com/category/1/foo-bar" do you really want to do another query and create another object just to create a URL? (my actual response to this would be yes, since what if you change the structure and thus need something else, it's better to be using objects).

So avoid having one massive file you could namespace like so:

echo URL\Articles::section();
echo URL\Articles::category();
echo URL\Articles::article();

Arguably this is uglier when used within a Templating engine, but i'm not so sure that's really a problem.

Actions and Routes

Laravel has a built in way using actions and routes

echo action('ArticlesController@getCategory', $params);
echo route('articleCategory', $params);

The problem with this is, you don't necessarily know what $params you want to send and you can't send an object. So you would wind up writing something like this in your views:

echo action('ArticlesController@getCategory', array('id'=>$category->id,'title'=>$category->title));

That's not very nice at all and would still need to be changed everywhere if you changed your structure too much.

A mixture of both

Looking at the source code of Laravel.io: https://github.com/LaravelIO/laravel.io/blob/master/app/views/forum/threads/index.blade.php

It uses a mixture of both. In some places it will do things like this:

href="{{ action('ForumThreadsController@getCreateThread') }}"

In other places it will do this:

href="{{ $thread->url }}""

In some places it gets really messy like so:

href="{{ action('ForumThreadsController@getIndex', '') . $queryString }}"

This may need to be changed in multiple places throughout the app if the structure ever changed too much e.g. not using query strings.

How do you do it?

This post is genuinely meant as a question. I've tried to answer it and give a bit of detail because I find that's better than posting a one line question "How do you organise your URLs".

But I don't have a conclusion to offer, simply because I do not know what the best approach is. What novel ways has everyone else found of managing their URLs and the structure of their application?

0 likes
49 replies
pmall's avatar

That's the role of route definitions. Their parameters should be obvious.

Stick to $router->resource() conventions and everything will be ok.

A model should never know about its url. In fact its url is obvious if you stick to resource conventions. An article url ? articles/:id.

tios's avatar

Following Jeffrey's instructions I use restfull routing and everything is fine

{!! link_to( route('category.edit', array( 'category'=> $c->categories_id ) ) , 'edit', array('class' => '') )  !!}
pmall's avatar

You can even put the whole model as parameter :

route('models.show', [$model]) // => models/:id_of_the_model
robjbrain's avatar

Not sure about sticking to resource conventions? In most cases you're going to want seo optimised urls, which will involve something like:

domain.com/1-my-article-title

or domain.com/nice-category-title/1/nice-article-title

or even domain.com/mens-fashion/tops/blue-shirt

Which you won't get from Route::get('article/{article}'), no?

Just look at the url for this topic: laracasts.com/discuss/channels/general-discussion/best-practice-managing-urls

How would such a URL work within your suggestion?

*I may have misunderstood, when you said resourceful conventions, I am thinking directly of this: http://laravel.com/docs/4.2/controllers#restful-resource-controllers

mantasmo's avatar

Why not simply use whatever URL's you want (I often use slugs too) and use route names to "keep track of URL's"?

Route::get('/login', [
 'as' => 'getLogin',
 'uses' => 'AuthController@getLogin'
]);
<a href="{{ route('getLogin') }}">Login</a>

And so on... as long as you are consistent with route names you should be fine.

Did I miss the point?

danielwinter's avatar

Well that's exactly what you would get with route binding:

domain.com/nice-category-title/1/nice-article-title

it's like

domain.com/{category}/{page}/{article}

where you define your models like that:

$router->bind('categories', function($slug){
   return Category::whereSlug($slug)->first();
  });

  $router->bind('articles', function($slug){
   return Article::whereSlug($slug)->first();
  });

That's exactly how you get nice routes. Further Resource: http://laravel.com/docs/4.2/routing#route-model-binding

robjbrain's avatar

I'm struggling to Google this or find examples and don't have an application close to hand to test.

But i'll give it a go anyway.

How does that route binding work in your views?

<h1>
<!-- domain.com/articles -->
<a href="{{ route('articles') }}">Articles</a>
</h1>

<h2>
<!-- domain.com/category-name -->
<a href="{{ route('category', array('category'=>$category)) }}">{{ $category->title }}</a>
</h2>

@foreach($articles as $article)
    <!-- domain.com/category-name/article-id/article-slug -->
    <a href="{{ route('article', array('category'=>$category,'article'=>$article)) }}">{{ $article->title }}</a>
@endforeach
  $router->bind('category', function($slug){
   return Category::whereSlug($slug)->first();
  });

  $router->bind('article', function($id){
   return Article::find($id);
  });

   $router->get('articles', ['as' => 'articles', function()
  {
    return View::make('articles.index');
  }]);

  $router->get('{category}', ['as' => 'category', function()
  {
    return View::make('articles.category', array(
        'category' => $category
    ));
  }]);

  //I realise this is wrong, i'm not clear on what you're supposed to put for {slug}
  //I also believe (without testing) that {category} will use $category->id which is not achieving what we want
  $router->get('{category}/{article}/{slug}', ['as' => 'article', function()
  {
    return View::make('articles.show', array(
        'article' => $article,
        'category' => $category
    ));
  }]);

Obviously i've made some mistakes there, because i'm not 100% clear on the correct syntax off the top of my head, but assuming it's roughly along the right lines and relatively easy to correct, my main concern would that it would still be very messy in views. Why should a view know what to send to the routing method? Or rather, why should someone editing the HTML/CSS be in charge of what objects are sent to a routing method? Shouldn't this sort of logic be outside the view?

*I realise you might say you don't need id and slug at the same time for the article, that is probably true, historically all my applications have used id/slug. The slug is for SEO and the id is for speed using a int lookup rather than a string (like I say, historical and perhaps out of date)

pmall's avatar

Ok but I don't understand what do you want to manage or configure. The routes.php is the configuration.

robjbrain's avatar

pmall: Sorry I thought my first post was quite explicit. I'm talking about organising and generating URLs. This could be part of routing, but doesn't necessarily have to be.

All the responses so far have been about model binding, including your own, i'm trying to understand why that is the best approach since the responses haven't been explicit in answering that question.

To clarify incase it is unclear, I am asking the best practice for things like this:

<!-- Example One -->
<h1><a href="{{ URL::to('articles') }}">Articles</a></h1>
<h2><a href="{{ $category->url }}">{{ $category->title }}</a></h2>
@foreach($articles as $article)
    <a href="{{ $article->url }}">{{ $article->title }}</a>
@endforeach


<!-- Example Two -->
<h1><a href="{{ URL::articles() }}">Articles</a></h1>
<h2><a href="{{ URL::category($category) }}">{{ $category->title }}</a></h2>
@foreach($articles as $article)
    <a href="{{ URL::article($article) }}">{{ $article->title }}</a>
@endforeach


<!-- Example Three -->
<h1><a href="{{ route('articles') }}">Articles</a></h1>
<h2><a href="{{ route('category', array('category'=>$category)) }}">{{ $category->title }}</a></h2>
@foreach($articles as $article)
    <a href="{{ route('article', array('article'=>$article)) }}">{{ $article->title }}</a>
@endforeach

These are just three examples of different ways of doing it. The examples are not exhaustive. I was trying to ask how other people organise and create links and whether anyone had any bright solutions, since each example has drawbacks.

robjbrain's avatar

@keevitaja My question was not about routes explicitly, I gave one example about routes along with other examples in my original post. Everyone else is suggesting routes as the solution to the question without much explanation, which is why I tried to respond with examples and try to understand the suggestion of modal binding.

Of course I am aware of the URL helpers (again it was in my original post). But again i'm not sure what you're getting at? By suggesting the URL helpers what are you suggesting is the optimal approach? Named routes? Or writing each one explicitly using URL::to()? Sorry it's not clear.

Perhaps my first post was too detailed, I thought that was better than a one line question. But i'll try and simplify it as best I can:

"What is the best practice for creating links/URLs in views (and redirects etc), that allows for flexibility of easily changing those links (i.e. defined in one place and flexible to allow for different formats)"

bashy's avatar

Indeed linking via named routes. Long as they stay the same (or you can update them easily across your app).

robjbrain's avatar

@mantasmo what do you mean by overthinking it?

At the moment an application i'm involved with looks like this:

<h1><a href="{{ URL::to('articles') }}">Articles</a></h1>
<h2><a href="{{ URL::to($category->slug) }}">{{ $category->title }}</a></h2>
@foreach($articles as $article)
    <a href="{{ URL::to($category->slug.'/'.$article->id.'/'.$article->slug) }}">{{ $article->title }}</a>
@endforeach

Is it overthinking things to want to make things more organised and trying to figure out which would be the best approach?

I've just seen your other reply saying "named routes". Can you explain how that meets the flexibility criteria? So you would have this in your View:

<h1><a href="{{ route('articles') }}">Articles</a></h1>
<h2><a href="{{ route('category', array('category'=>$category)) }}">{{ $category->title }}</a></h2>
@foreach($articles as $article)
    <a href="{{ route('article', array('article'=>$article)) }}">{{ $article->title }}</a>
@endforeach

Now you're given an existing project and the URLs need to be kept intact, they prefix the article url with the category slug and contain both the article id and the article slug.

How do named routes help with this particular scenario (just an example, my main point is about being flexible to work with any url format or future changes)

robjbrain's avatar

@bashy thanks for the response. Same question as above, can you explain, with examples, how you would achieve that level of flexibility with named routes?

It feels like everyone is just shouting named routes and route binding at me, but without explanations or examples. I may very well just be naive and the answer is obvious, but it kind of feels like we're talking past each other.

mantasmo's avatar

@robjbrain what level of flexibility? Give me an example route + view and show me what you're changing that route/view to exactly.

Named routes allow you to change URL's without having to update any references to that route anywhere else (as long as you're not adding additional params to the route). You must be looking for something else..?

bashy's avatar

As above - In the documentation it states that you can generate links or redirects via named routes that you set via your routes (using route resource auto adds this).

Even if the URI changes or you change the structure of it (/auth/login to /login) it will still work if you use {{ URL::route('login.index') }} or similar.

robjbrain's avatar

Crikey I thought i'd given a few examples already!

Okay, how would you have this using named routes:

$link = 'http://domain.com/'.$category->slug.'/'.$article->id.'/'.$article->slug;

Changing it after that seems simple enough, unless you're changing parameters like you said.

Although that brings me back to the other point I was trying to discuss which was "why would you leave it up to the design guy doing his HTML to ensure that the right parameters are set and not put that logic in a class/object somewhere instead". But asking that is perhaps over complicating things at this point.

keevitaja's avatar

@robjbrain if i sounded bit rude, that was not my intention at all. sorry about that!

I use only named routes myself, so route() and link_to_route() are my tools of choice. That said, i must name all the routes i create. Using a named route works well for me because i do not have to worry about any problems when i change the the route path somewhere.

Edit:

Also i suggest you consider not using the id and a slug both in the same URI if the slug is unique. It will just cost you extra time...

mantasmo's avatar

@robjbrain

That's a poorly designed route - legacy code right... You declare the route:

Route::get('{category}/{article-id}/{article-slug}', [
 'as' => 'article.show',
 'uses' => 'ArticleController@show'
]);

...or something very similar...

At this point it really depends on how badly designed the app is. The double binding for "article" isn't needed so you can model-bind one and ignore the other (passing it as a a string that doesn't get used). Model bind the category as well if needed.

Let me try this locally.

bashy's avatar

Also i suggest you consider not using the id and a slug both in the same URI if the slug is unique. It will just cost you extra time...

It's fine for anything small but if you edit title or slug in the future you can redirect it to the new version if required (with the ID). Mainly SEO things to think about.

keevitaja's avatar

Bit off topic again, but

has any of you tested the impact the URI formatting has on rankings? I have done this several times, and i am 99,9% sure, there is no difference in using /this-is-a-slug vs /?id=5

Just a thought... If changing slugs are something to worry about, then why use them at all?

keevitaja's avatar

How old was this site and its content you experimented on?

mantasmo's avatar

@keevitaja multiple sites of all sizes/ages - I made a very comfortable living building/selling medium sized sites for a few years. Sold my last big site about 15 months ago so things could have changed since then. Though I doubt it...

keevitaja's avatar

Yeah, only google knows the answer to this.

Anyway i have experimented on many sites with different loads and i have seen no change in rankings. The same goes for using or not using h1 tags and many other myths we learn from different blog posts.

And for me it has been like that for more than 15 month. Can't belive that in 15 month it will change. It's google!

mantasmo's avatar

You can gain massively (in rankings which == visitors) by buying big but poorly optimized sites and slowly switching out links to use slugs instead of random gibberish (ids) + many other small tweaks. Agree on h1's and such - makes no difference. URL structure is a whole different beast though you have to be very, veeery careful with established sites. I'm talking going from 500-800 daily uniques to 2000-20000/day in under a year.

This is getting too off-topic haha. :) Sorry OP!

keevitaja's avatar

Did these sites have sitemap before? Without a sitemap it makes lot of sense... otherwise i am still skeptical but i am not saying it cannot be as you say. Google works in the weird way, which normal person can never get 100%. Sometimes all you need is a different type of site and everything will be different.

robjbrain's avatar

Yep it's also user friendly, not just seo friendly. You're looking for that article on dog food, type dog food into the address bar and it might pop up from your history. Clicking a link that someone has copy and pasted and it says domain.com/what-to-feed-your-dog you know what it's about rather than domain.com/?p=5. So it's not just about SEO although that is a big part of it still.

Just tested this. From what I can tell it doesn't look like it likes the hyphens, unless they're there for something specific that i'm not cottoning onto.

Route::get('{category}/{article_id}/{article_slug}', array('as' => 'article.show', function($category, $article)
{
    var_dump($category);
    var_dump($article);
}));

Route::model('category', 'Category');
Route::model('article_id', 'Article');

This route works when visiting domain.com/foo/1/bar

But i'm still unclear when trying to create the url. From what people have been saying about it binding it seemed like this should work:

    $category = Category::find(1);
    $article = Article::find(1);

    echo route('article.show', array(
       'category' => $category,
        'article_id' => $article,
        'article_slug' => $article->slug
    ));

But apparently not. Presumably it should be something like this:

    $category = Category::find(1);
    $article = Article::find(1);

    echo route('article.show', array(
       'category' => $category->slug,
        'article_id' => $article->id,
        'article_slug' => $article->slug
    ));

But that's going to be very messy to put into views.

<a href="{{ route('article.show', array('category' => $category->slug,'article_id' => $article->id,'article_slug' => $article->slug)); }}">{{ $article->title }}</a>

The guy writing HTML shouldn't have to worry about all that, compared to writing something like:

<a href="{{ $article->link() }}">{{ $article->title }}</a>

Which was an example I gave in my original post, but has its own drawbacks.

So I just wanted to clarify is that 4th snippet of code what everyone has been recommending? If it is that is where i'm getting confused since it doesn't seem to meet the needs of 1) being flexible (the parameters need to be declared in various blade files) and 2) being easy to write within HTML.

Next

Please or to participate in this conversation.