7

I want redirects (301) for some routes which contains the following -

page=1 (query string) or index.php or ?&(query string)

I have added the route as -

Route::get('/{any_url}', 'UsersController@processRedirect')->where('any_url', '(.*)index\.php(.*)|(.*)page=1(.*)|(.*)|?&(.*)');

When I am checing in https://regex101.com/ it is working but in my app iit iis not working.

What could be the issue?

I have done this via MiddleWare but I do not want to check for all urls.

Is there any other way to achieve this?

Sougata Bose
  • 31,517
  • 8
  • 49
  • 87

2 Answers2

4

So the trick is not to do this in the route but instead make a global catch all route that catches all routes and then you can do any handling of path or query string in your controller. This keeps your routes clean and easy to use and allows more power over processing of query strings and the path or base url. Be sure to remove the routes you want to handle with this catch all route.

routes/web.php

//make this last route to catch
Route::any('/{any}', "ProcessRequestController@handler")->where("any", ".*");

App/Http/Controllers/ProcessController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;


class ProcessRequestController extends Controller{

    public function handler(Request $request){
        //from here you can now access each section of the route a little more conviently.
        $request->path();
        if($request->has('page')){

        }
    }
}
jgetner
  • 691
  • 6
  • 14
  • This could solve the issue. I will try this and check. – Sougata Bose Apr 27 '20 at 09:54
  • 1
    make sure to add that the route needs to be added at the very bottom of the router file – Christophe Hubert Apr 27 '20 at 10:06
  • 1
    @ChristopheHubert Yes. I forgot that route order effects. Thanks. – Sougata Bose Apr 27 '20 at 10:15
  • We will not be able to implement this way as the routes will match previous routes. – Sougata Bose Apr 29 '20 at 13:00
  • 1
    You need to add it to the end after all other routes. This must be the last route as you are correct it will catch all routes so placement in your route order is key for this to work properly. If you can provide your routes we can further assist you in getting this setup properly. – jgetner Apr 29 '20 at 15:40
  • @jgetner `/companies.html` will catch `/companies.html?&page=1`. so if I define it after `/companies.html` then it will not work. – Sougata Bose May 04 '20 at 03:19
1

Middleware is probably the better solution.

Catch-all routes work, but there are unintended consequences. You will have to manually handle 404 errors. You may also notice decreased performance depending on how your routes are setup.

Unlike catch-all routes, middleware can run before any routes are matched. Middleware can also be attached to a group of routes or a specific route. There are other middleware classes that are running by default, such as Authentication and CORS.

Here are the latest docs for middleware. Middleware hasn't changed much since Laravel 5, but the latest docs just explain the concept better. https://laravel.com/docs/7.x/middleware


Here is how you could use middleware to solve your problem.

Create a middleware class.

php artisan make:middleware My301Filter

If you want the middleware to run on all requests, register the class in app/http/kernel.php

protected $middleware = [
    // ...
    \App\Http\Middleware\My301Filter::class
];

Then you can parse your route using any way you want. Use logic to determine if a 301 should be called or if the app should continue. The $next closure will go to the next middleware or route.

I chose to write something like this in my middleware class. However, this code is not perfect and you will probably have to change it to fit your needs.

<?php

namespace App\Http\Middleware;

use Closure;

class My301Filter
{

    // ...

    public function handle($request, Closure $next)
    {
        $needles = ["page=1", "index.php", "?&"];

        foreach ($needles as $needle) {
            if (strpos($request->getRequestUri(), $needle) !== false) {
                return redirect('/some/route', 301);
            }
        }

        return $next($request);
    }
}
wizardzeb
  • 1,546
  • 2
  • 14
  • 30
  • Currently using MiddleWare only, but **avoiding check for all route not possible with MiddleWare** – Sougata Bose Apr 28 '20 at 07:14
  • This is not a good solution for this issue. The problem becomes you ether have to apply this middleware to all routes or to specific routes. If you apply this to all routes this will run no matter the route. Where a global catch route will only run after all other routes have been checked. If you do not use global middle ware then you must attach it to the individual routes which seems not very practical giving the alternative solution. – jgetner Apr 28 '20 at 11:07
  • Attaching middleware to specific routes is practical. Route groups can be used to encompass many routes with the same middleware. If the group does not match the request, then the middleware will not run. A catch-all route relies on all other routes failing. So, if the query string contains `page=1`, and also matches another route, then the catch-all route will not run. Middleware on route groups is available in 5.x https://laravel.com/docs/5.0/routing#route-parameters – wizardzeb May 03 '20 at 02:02