Laravel - Using (:any?) wildcard for ALL routes?
Asked Answered
P

10

81

I am having a bit of trouble with the routing.

I'm working on a CMS, and I need two primary routes. /admin and /(:any). The admin controller is used for the route /admin, and the view controller should be used for anything else than /admin. From the view controller, I will then parse the url and show the correct content.

This is what I have:

Route::get(array('admin', 'admin/dashboard'), array('as' => 'admin', 'uses' =>'admin.dashboard@index'));
Route::any('(:any)', 'view@index');

The first route works, but the second one doesn't. I played around with it a little bit, and it seems if I use (:any) without the question mark, it only works if I put something after /. If i do put the question mark there, it doesn't work at all.

I want all of the following routes to go to view@index:

/
/something
/something/something
/something/something/something
/something/something/something/something
...etc...

Is this possible without hardcoding a bunch of (:any?)/(:any?)/(:any?)/(:any?) (which I don't even know works)?

What's the best way to go about this?

Preeminence answered 8/11, 2012 at 20:29 Comment(0)
N
32

Edit: There has been some confusion since the release of Laravel 4 regarding this topic, this answer was targeting Laravel 3.

There are a few ways to approach this.

The first method is matching (:any)/(:all?):

Route::any('(:any)/(:all?)', function($first, $rest=''){
    $page = $rest ? "{$first}/{$rest}" : $first;
    dd($page);
});

Not the best solution because it gets broken into multiple parameters, and for some reason (:all) doesn't work by itself (bug?)

The second solution is to use a regular expression, this is a better way then above in my opinion.

Route::any( '(.*)', function( $page ){
    dd($page);
});

There is one more method, which would let you check if there are cms pages even when the route may have matched other patterns, provided those routes returned a 404. This method modifies the event listener defined in routes.php:

Event::listen('404', function() {
    $page = URI::current();
    // custom logic, else
    return Response::error('404');
});

However, my preferred method is #2. I hope this helps. Whatever you do, make sure you define all your other routes above these catch all routes, any routes defined after will never trigger.

Niigata answered 8/11, 2012 at 20:49 Comment(3)
Awesome, just what i needed. The second method will probably work just fine, although method one is also tempting to use, because i will end up breaking the route up in sections by / anyways. Eg. if i want to view a subpage, i would go to /parent/subpage, so i would need to parse that from the view controller and display the correct page. I'm not sure which method is most appropriate for this kind of use, but i guess in the end it doesn't matter?Preeminence
Can't get solution #2 to work :| I'm getting a Error: Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpExceptionInterpolate
If you find that #2 doesn't work you may be using Laravel 4. In which case, this answer may help. Which boils down to Route::any('{all}', function($page) { dd($page); })->where('all', '.*');Spin
F
136

Laravel 5

This solution works fine on Laravel 5:

Route::get('/admin', function () {

  // url /admin

});

Route::get('/{any}', function ($any) {

  // any other url, subfolders also

})->where('any', '.*');

Lumen 5

This is for Lumen instead:

$app->get('/admin', function () use ($app) {
  //
});

$app->get('/{any:.*}', function ($any) use ($app) {
  //
});
Falk answered 2/1, 2016 at 10:50 Comment(3)
Work for me! But what if I want to have more parameters after that. Example: $app->get('/admin/{select}/query/{query}', function () use ($app) {}->where('select', '.*')->->where('query', '.*');Asper
@Asper You can add your new rule between the rule '/admin' and the rule '/{any:.*}' or just replace the rule '/admin' with your new rule if you do not need the '/admin' url without parameters.Falk
Came here for the lumen. Verified worked with Lumen 5.5. Thanks!Shane
D
50

Hitting a 404 status seems a bit wrong to me. This can get you in all kind of problems when logging the 404's. I recently bumped into the same wildcard routing problem in Laravel 4 and solved it with the following snippet:

Route::any('{slug}', function($slug)
{
    //do whatever you want with the slug
})->where('slug', '([A-z\d-\/_.]+)?');

This should solve your problem in a controlled way. The regular expression can be simplified to:

'(.*)?'

But you should use this at your own risk.

Edit (addition):

As this overwrites a lot of routes, you should consider wrapping it in an "App::before" statement:

    App::before(function($request) {
            //put your routes here
    });

This way, it will not overwrite custom routes you define later on.

Derayne answered 23/3, 2013 at 11:29 Comment(2)
Also worked for me. I put it after my other routes inside a route group to catch all incorrect api endpoints.Agamemnon
Yeah this is the approach I use, and it works in L4.1, however I prefer a single line like this: Route::any('admin/{allsegments}', array('as'=>'admin', 'uses'=> 'AppBackend\Controllers\AdminController@index'))->where('allsegments','(.*)?');Leucoma
N
32

Edit: There has been some confusion since the release of Laravel 4 regarding this topic, this answer was targeting Laravel 3.

There are a few ways to approach this.

The first method is matching (:any)/(:all?):

Route::any('(:any)/(:all?)', function($first, $rest=''){
    $page = $rest ? "{$first}/{$rest}" : $first;
    dd($page);
});

Not the best solution because it gets broken into multiple parameters, and for some reason (:all) doesn't work by itself (bug?)

The second solution is to use a regular expression, this is a better way then above in my opinion.

Route::any( '(.*)', function( $page ){
    dd($page);
});

There is one more method, which would let you check if there are cms pages even when the route may have matched other patterns, provided those routes returned a 404. This method modifies the event listener defined in routes.php:

Event::listen('404', function() {
    $page = URI::current();
    // custom logic, else
    return Response::error('404');
});

However, my preferred method is #2. I hope this helps. Whatever you do, make sure you define all your other routes above these catch all routes, any routes defined after will never trigger.

Niigata answered 8/11, 2012 at 20:49 Comment(3)
Awesome, just what i needed. The second method will probably work just fine, although method one is also tempting to use, because i will end up breaking the route up in sections by / anyways. Eg. if i want to view a subpage, i would go to /parent/subpage, so i would need to parse that from the view controller and display the correct page. I'm not sure which method is most appropriate for this kind of use, but i guess in the end it doesn't matter?Preeminence
Can't get solution #2 to work :| I'm getting a Error: Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpExceptionInterpolate
If you find that #2 doesn't work you may be using Laravel 4. In which case, this answer may help. Which boils down to Route::any('{all}', function($page) { dd($page); })->where('all', '.*');Spin
D
14
Route::get("{path}", "SomeController@serve")->where('path', '.+');

The above code will capture the recursive sub urls you mentioned:

/
/something
/something/something
/something/something/something
/something/something/something/something

Any other special cases, such as admin/*, you can capture before this one.

Detective answered 2/1, 2016 at 18:54 Comment(2)
this is same as ->where('path', '.*');Rep
This is working but we should some change as given Route::get("/{path?}", "SomeController@serve")->where('path', '.+');Yarber
C
8

Just spelling-out my experience in case it helps someone piece something together.

I built a self-API-consuming React app on Laravel. It has a single view served by Laravel/Lumen. It uses the React router. Clicking links in the app always worked, but typing-in URLs needed the following consideration:

In Laravel I used the following in my web.php routes file:

Route::view('/{path?}', 'app')
    ->where('path', '.*')
    ->name('react');

And everything worked.

Then I switched the project to Lumen. Thanks to this post, I used the following in my web.php routes file:

$router->get('/{all:.*}', function() {
    return view('app');
});

This worked for first level URLS such as:

/
/something 

However,

/something/something etc.

did not.

I looked in the network tab in Google Developer tools and noticed that the URL for app.js was appending /something in front of app.js on second and higher tier URLS, such as:

myapp.com/something
app.js URL:  myapp.com/js/app.js  (as it should be)

myapp.com/something/something
app.js URL:  myapp.com/something/js/app.js  (not found)

All I had to do was add a leading slash to my app.js source in my single view page such as:

<script src="/js/app.js" defer></script>

Instead of:

<script src="js/app.js" defer></script>

so:

This worked in Laravel (It was a Blade file that may have automatically resolved the js/app.js URL)

<script src="{{ asset('js/app.js') }}" defer></script>

with

Route::view('/{path?}', 'app')
    ->where('path', '.*')
    ->name('react');

But, I had to do this in Lumen (Not a Blade file):

<script src="/js/app.js" defer></script>

with

$router->get('/{all:.*}', function() {
    return view('app');
});
Cleanup answered 27/4, 2019 at 12:58 Comment(0)
L
7

Laravel 8 / redirect only under subdirectory

I wanted to redirect not all non-existing URLs, but only the ones in a specific subdirectory (like https://example.com/subdir/*)

If I just had wanted to redirect all missing URLs I could have just used the fallback route at the end of the web.php file:

Route::fallback(function () {
    //
});

But as I wanted to redirect only urls in subdirectories, I used this code, which worked for me (just redirecting to /):

Route::get('subdirectory/{any}', function($page){return redirect()->to('/');})->where('any', '.*');

I found this in the FallbackRouteTest.php.

Leroy answered 20/6, 2021 at 4:42 Comment(3)
It only works for get requests though.Notional
@steve moretz, you can of course just use ::any for all methods or any specific method you want to redirect. But that is probably rarely useful.Leroy
No I mean the fallback method only works for get requests, but yeah my use cases are always stupidly rare, lolNotional
L
4

Add this in the end of routes file

App::missing(function($exception)
{
    return View::make('notfound');
});

from http://scotch.io/tutorials/simple-and-easy-laravel-routing

Lanettelaney answered 26/5, 2014 at 11:22 Comment(0)
M
2

Thanks for the solution William. However methods 1 & 2 aren't working anymore Laravel 4, and in order to use solution #3 in Laravel 4 you will have to fire the 404 event in your start/global.php file.

App::error(function(Exception $exception, $code)
{
    // i.o. -> this is our catchall!
    // https://mcmap.net/q/258055/-laravel-using-any-wildcard-for-all-routes
    Event::fire('404');

    return View::make('error')->with('exception', $exception)->with('code', $code);

    Log::error($exception);
});

Now we can handle this in our routes.php file:

Event::listen('404', function() {
    // url?
    $url = Request::path();

    // LOGIC HERE

    // else
    return View::make('error');
});
Moloch answered 13/3, 2013 at 23:43 Comment(1)
Actually, check this out four.laravel.com/docs/errors#handling-404-errors. You don't have to fire that 404 event yourself, you can just add a listener with App::missing()Sherrilsherrill
B
1

Having basic lumen scaffolding. In my case, I have 2 frontend apps and api routes

<?php // routes/web.php
/** @var \Laravel\Lumen\Routing\Router $router */

$router->group([
    'prefix' => '/api/v1',
    'namespace' => 'App\Http\Controllers'
], function () use ($router) {

    require 'routes-group1.php';
    require 'routes-group2.php';
    // ...
});

$router->get('/admin{any:.*}', function () {
    return file_get_contents('../public/a/index.html');
});

$router->get('{any:.*}', function () {
    return file_get_contents('../public/m/index.html');
});
Boater answered 17/1, 2018 at 3:29 Comment(0)
U
1

Fallback Routes Since Laravel 5.5

Using the Route::fallback method, you may define a route that will be executed when no other route matches the incoming request. Typically, unhandled requests will automatically render a "404" page via your application's exception handler. However, since you would typically define the fallback route within your routes/web.php file, all middleware in the web middleware group will apply to the route. You are free to add additional middleware to this route as needed:

Route::fallback(function () {
    //
});

Note: The fallback route should always be the last route registered by your application.

Uropod answered 11/12, 2020 at 16:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.