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

BartHuis's avatar

Repeating part in router url path

Hi,

I would like to achieve something like this:

path: domain.com/search/key1/value1/key2/value2/key3/value3/key4/value4

where the key value part is an reapeating url path part. so i thought of something like this:

Route::group(['prefix' => '/search'], function() {
    Route::get('[{key}/{value}]', function($routerepeaterbetween[and])
    {
        forach($routerepeaterbetween[and]){
            $repeatingSearchArray[] = array($key=>$value);
        }
        $controller = new \App\Http\Controllers\SearchController();
        return $controller->searchByArray($repeatingSearchArray);
    });
 });

But i can't really find how i can do something like this? i came up with a where function appending the route with ".*" at:

http://stackoverflow.com/questions/14390631/laravel-4-routing-with-arbitrary-number-of-url-segments

But i don't realy understand whats happening there, its a solution for that specific problem, but no explenaition what .* actualy does, and i can't find that in the docs too. (and offcource i want it in laravel 5 ;) )

Bart

0 likes
33 replies
RoboRobok's avatar

In my app I have something like this:

$router->pattern('book', '[^/]+/[^/]+');

$router->bind('book', function ($bookString) {
    list($authorNameSlug, $titleSlug) = explode('/', $bookString);

    $author = Author::whereNameSlug($authorNameSlug)->firstOrFail();
    $book   = $author->books()->whereTitleSlug($titleSlug)->firstOrFail();

    return $book;
});

I am referring to books as author/title in my URLs from many places. Then, I can do something like this:

Route::get('{book}/reviews', 'BookReviews@index');
Route::get('{book}/buy', 'BuyBook@form');

And have URLs like:

  • book.dev/j-k-rowling/harry-potter-and-the-philosophers-stone/reviews
  • book.dev/antoine-de-saint-exupery/the-little-prince/buy

All I need to do is to put {book} in the route. This is Laravel 5 though, not sure if it works for Laravel 4.

BartHuis's avatar

but you have a fixed number of repeats, 2. in my case its a non fixed number of repeats. it can be domain.com/search/key1/value1 until domain.com/search/key1/value1/key2/value2/key3/value3/key4/value4/key5/value5 or even more

RoboRobok's avatar

The thing is to just change the pattern :)

Instead of:

$router->pattern('book', '[^/]+/[^/]+');

You would have:

$router->pattern('something', '.+');

But remember to put it as the very last rule. Also, keep in mind that you will need to handle 404 yourself, because this pattern will take all requests. Unless you want to make it more strict if it has ANY specific rules to it.

What are you going to do with those?

BartHuis's avatar

first of all, the position you mention, its within the group search, so its will only been done when your going to /search.

what i wanna do is something like this:

domain.com/search/size/12/color/red/searchterm/aterm/pricefrom/20/priceto/185/sort/priceup

so i can loop through all key's and value's and for each possible key, i make an seperate where or sort query calls. but the same must also work this way:

domain.com/search/sort/priceup/searchterm/aterm/color/red/pricefrom/20/priceto/185/size/12

and this way:

domain.com/search/searchterm/aterm

BartHuis's avatar

so in the actual search function i want to do something like this (quickly typed dummy code as example)

foreach($searcharray as $searchkey => $searchvalue){
    switch ($searchkey) {
        case searchterm:
            $query->where('title',$searchvalue);
            break;
        case size:
            $query->where('size',$searchvalue);
            break;
        case pricefrom:
            if($searcharray[priceto]){
                $query->wherebetween('price',$searchvalue,$searcharray[priceto]);
            }
            break;
        default:
           return error search term not excist
    }
}
RoboRobok's avatar

Yeah, I got you. The pattern I would use in this case would be this:

$router->pattern('query', 'search(?:/[^/]+/[^/])+');

Thanks to this, it will only catch requests with search/key/value pattern, where key/value can occur multiple times, like this:

  • /search/name/bart - is okay
  • /searchme/name/bart - nope, because it's not /search
  • /search/name/bart/coolness/much - it's fine
  • /search/name/bart/coolness - no, because we have odd number of chunks after /search (missing value)
  • /search - nope, because no key/value present. This can be easily changed to catch request without key/value pairs too though.

How about that?

BartHuis's avatar

that looks like what i want, but, how is it going to be passed through? as what variable can i pass it to my search controller?

RoboRobok's avatar

Maybe it's just not in the docs. Not every feature is present in the docs.

In your controller, you would just receive a string., like /name/bart/coolness/much. Converting that to your database query is just pure string manipulation:

$chunks = explode('/', ltrim($query, '/'));

And then making a key => $value from this array.

BartHuis's avatar

aah, your just not breaking it up in the router yet... i'll look at this, thanks!

RoboRobok's avatar

Yeah, exactly. The router just matches string to appropriate action. :)

Also, I forgot to mention that this code with route patterns would go to RouteServiceProvider (/app/Providers/RouteServiceProvider.php).

BartHuis's avatar

@RoboRobok

i never used the pattern / binding, but as i see your first example:

$router->pattern('book', '[^/]+/[^/]+');

$router->bind('book', function ($bookString) {
});

it's working like this?

$router->pattern('patternname', '[^/]+/[^/]+');

$router->bind('patternname', function ($variableContainingFullStringOfMatchedPattern) {
    return $variableContainingFullStringOfMatchedPattern // will return key1/value1/key2/value2
});

and offcourse, when i place that whole thing in my group search, it will only happen on the url search ;)

BartHuis's avatar

what do you mean by "this code with route patterns would go to RouteServiceProvider " ?

martinbean's avatar

@BartHuis Use a query string. That’s exactly what they’re for: an arbitrary number of key–value data pairs.

Given the URL: http://example.com/search?size=12&color=red&searchterm=aterm&pricefrom=20&priceto=185&sort=priceup

You can loop over them from $request->query(), match the key against a whitelist of column names, and filter there:

$query = Product::newQuery();

foreach ($request->query() as $key => $value) {
    if (in_array($key, $whitelist)) {
        $query = $query->where($key, '=', $value);
    }
}

$results = $query->paginate();

You could even have closures that you pass the value so you can do more than just simple “equals” conditions:

$strategies = [
    'size' => function ($query, $value) {
        return $query->where('size', '=', $value);
    },
    'pricefrom' => function ($query, $value) {
        return $query->where('price', '>', $value);
    },
    // And so on
];

And then call the corresponding strategy in your loop:

foreach ($request->query() as $key => $value) {
    if (array_key_exists($key, $strategies)) {
        $query = $strategies[$key]($query, $value);
    }
}

When you find something’s difficult to do (i.e. trying to match this route pattern in Laravel’s route) it usually means you’re doing something non-standard and that there’s a better way.

BartHuis's avatar

@martinbean : ah, you changed it i see, looks good, but what if i want the url's be like:

domain.com/search/size/12/color/red/searchterm/aterm/pricefrom/20/priceto/185/sort/priceup

instead of the parameters? ;)

RoboRobok's avatar

When you find something’s difficult to do (i.e. trying to match this route pattern in Laravel’s route) it usually means you’re doing something non-standard and that there’s a better way.

I don't agree with this one. In this case yes, most sites use query string. But there are many cases when framework just doesn't handle our idea well and it doesn't mean the idea is wrong.

what do you mean by "this code with route patterns would go to RouteServiceProvider " ?

I mean that the best place to put the code I showed you is RouteServiceProvider, the boot() method.

pmall's avatar

but what if i want the url's be like:

domain.com/search/size/12/color/red/searchterm/aterm/pricefrom/20/priceto/185/sort/priceup

instead of the parameters? ;)

As @martinbean explained there is absolutely no point having an url like this. Why do you ever want to?

You should use the tool made for this : the query string parameters.

RoboRobok's avatar

Yes, from routes file you don't have direct access to the Router. In routes file you would put your routes using this tag like {query}.

What @martinbean suggested is to don't use this structure of URL at all. You would have /search?name=Bart&coolness=much. It's completely different, it doesn't require handling anything in the router, but also the URL is not like you wanted. In this particular case I would base my decision on whether you are going to link to search results much or not.

BartHuis's avatar

@pmall becouse the search url's will be linked also, and then it's better for SEO

but i like the idea of the query string...

isn't it possible to pass the route like i want to the query string method?

RoboRobok's avatar

@pmall becouse the search url's will be linked also, and then it's better for SEO

Are SEO engines still so dumb these days?

martinbean's avatar

@BartHuis Query strings are for manipulating the presentation of a resource. “Search results” is a resource. You “manipulate” that resource by specifying criteria like size, colour, price, etc. Therefore, we have query strings and have done for many years now. Don’t fight conventions, use them ;)

Hard-baking parameters into your URI string has far more drawbacks than it does advantages. A major one is SEO. To search engines, the URLs…

…are going to get your site penalised for having duplicate content, because those URLs are going to yield the same results. Whereas /search and a query string, a search engine will go, “OK, this is a search page, and has that criteria applied”. It doesn’t matter what order you specify the criteria or how many you specify, because a search engine knows it’s data being used to manipulate a single URL.

BartHuis's avatar

hmm, i'll discuss that with my team here and think about the options. (it sounds logically) thanks for the multiple options for so far :D always nice to have diferent views on a topic ;)

RoboRobok's avatar

@martinbean I think his code would handle it with the same order, so SEO engines would never see those cases when you have them reversed.

I wouldn't also be so strict about resorces + parameters. There are cases like for example category trees with varying length, when you use the hierarchy in the URL, like: /catalog/clothing/shoes/dress/dior-blah-blah-new-model. It's also the same resource type (product), but the URL structure is different. There is nothing wrong with this and the URL these days serves aesthetic purpose too.

But again, in this case of searching, as you provide several unrelated parameters, I would strongly consider classic query string.

pmall's avatar

becouse the search url's will be linked also, and then it's better for SEO

This is a false assumption, google perfectly handle query string parameters for search page.

It's also the same resource type (product), but the URL structure is different

@RoboRobok This is different, it is not a matter of resource type, but a matter of content. Your page can have any url you want as long as it has only one.

In fact urls should look like paths of a well organized filesystem. It is just an abstraction of filesystem path after all.

RoboRobok's avatar

@pmall I agree. Still though, search results organized in URL chunks, if those chunks are going to be ordered some way (and they most likely will be), is not that far from filesystem abstraction. We could have them organized this way with going deeper and deeper on this tree. It is different, because those parameters are rather equal in the hierarchy, none of them is clear parent or child. But again, I just think it's not a terrible idea and if I had a site with, let's say, apartments for rent, I would consider this. I would link to the catalog filtered by different criteria all over and this segmented URL just feels more comfortable.

/apartments/rooms/2/location/nyc/price-max/1200

vs.

/apartments?rooms=2&location=nyc&price-max=1200

Clearly aesthetic purpose, nothing more. It just feels like a catalog and not low-level search engine.

BartHuis's avatar

actualy, to make it some more dificoult, the intension of this was not only to apply this to search, but also at /products/{category}/{subcategory}/key1/value1/key2/value2 to apply filters on the product views ;)

BartHuis's avatar

@RoboRobok it looks nices defenitely.

to both, the facts is that at this moment in the old website, the url's with the search querys ARE very good indexed... even though the order within the url can be diferent. so i guess google is understanding it really good at the moment ;) and we don't wanna loose that results ;)

Next

Please or to participate in this conversation.