skip to Main Content

I am trying to figure out how to achieve a specific URL structure in a Laravel 8 project and the necessary route to achieve this. What I want is:

// Example urls to listings in the business directory.
// These urls should be routed to the directory controller.
www.domain-name.com/example-business-name-d1.html
www.domain-name.com/example-business-name-d15.html
www.domain-name.com/example-business-name-d100.html
www.domain-name.com/example-business-name-d123.html
www.domain-name.com/example-business-name-d432.html

// Example urls to articles/posts in the blog.
// These urls should be routed to the posts controller.
www.domain-name.com/example-post-name-p5.html
www.domain-name.com/example-post-name-p11.html
www.domain-name.com/example-post-name-p120.html
www.domain-name.com/example-post-name-p290.html
www.domain-name.com/example-post-name-p747.html

// We want to avoid the more traditional:
www.domain-name.com/directory/example-business-name-1.html
www.domain-name.com/blog/example-post-name-5.html

This is because we don’t want the strings “directory” or “blog” contained in the url for every listing or blog post. Search engine results work better without it.

So far I am using a catch-all route {any} at the bottom of the web.php routes file to “catch all” routes that get that far. I then manipulate the string provided by the path to get the ID and single character token from the end of the urls. I then have these 2 variables but can figure out how to pass these onto the right controllers!

Or am I being really dumb and there is a much better way of achieving this?

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

    // Break up the url into seperate parts.
    $pieces = explode("-", $any);
    $pieces = array_reverse($pieces);
    $piece =  $pieces[0];

    // Remove the .html
    $piece = substr($piece, 0, -5);

    // Get the two parts of the identifier.
    $id = substr($piece, 1);
    $token = substr($piece, 0, 1);

    // Call correct controller based on the token.
    switch ($token) {
        case "d":
            // HERE I WANT TO PASS THE ID ON TO THE SHOW ACTION OF THE DIRECTORY CONTROLLER 
        break;
        case "p":
            // HERE I WANT TO PASS THE ID ON TO THE SHOW ACTION OF THE POSTS CONTROLLER 
        break;
        default:
            return abort(404);
        break;
    }

});

3

Answers


  1. I would split the path into 2 variables ($slug and $id) and directly pass it to the controller.

    Route::get('{slug}-d{id}.html', 'DirectoryController@show')
        ->where(['slug' => '([a-z-]+)', 'id' => '(d+)']);
    
    Route::get('{slug}-p{id}.html', 'PostController@show')
        ->where(['slug' => '([a-z-]+)', 'id' => '(d+)']);
    

    And in your controllers

    class DirectoryController
    {
        public function show(string $slug, int $id) {}
    }
    
    class PostController
    {
        public function show(string $slug, int $id) {}
    }
    
    Login or Signup to reply.
  2. I can see two ways of achieving this result:

    Create an intermediate controller

    Route::get('{path}', 'CheckPathController@redirect')
    

    Then in your CheckPathController you do all the checks and your call the proper controller action:

    public function redirect(Request $request, $path) {
      // Your checks on $path, extract $id and content type
    
      if($isPost) {
         $controller = resolve(PostController::class);
         return $controller->show($request, $id);
      }
    
      if($isBusiness) {
         $controller = resolve(BusinessController::class);
         return $controller->show($request, $id);
      }
    
      // No matches, error 404
      abort(404);
    }
    

    Complex regex

    see: https://laravel.com/docs/8.x/routing#parameters-regular-expression-constraints

    I’m not a regexp master, this should be a basic was to match any {word}-{word}-...-p{id}.html pattern but it will break in case of unexpected chars

    Route::get('{path}', 'PostController::show')
       ->where(['path' => '([w]*-)*p[0-9]+.html$']);
    
    Route::get('{path}', 'BusinessController::show')
       ->where(['path' => '([w]*-)*d[0-9]+.html$']);
    

    Note that in this case, you controller will receive the pull $path string, so you will need to extract the id there.

    Login or Signup to reply.
  3. You can match the slug using regex

    Route::get('/{any}', 'YourController@methodName')->where(['any' => '.*(-d(.*?).).*']);
    

    Repeated with p

    Then when you pickup your $site in your controller method you can use regex to grab the site.

    public function methodName($site)
    {
        preg_match('/.*(-(d(.*?)).).*/', $site, $parts); //or something similar, $parts[2] will have what you want
    }
    

    OR

    This will give your controller method d{number} or p{number}

    Route::get('/{site}', function($site) {
        $code = preg_match('/.*(-(d(.*?)|p(.*?)).).*/', $site, $parts) ? $parts[2] : null;
    
        $controllerName = 'ControllerA';
        if(isset($code) && !is_null($code) && Str::contains($code, 'p')) {
            $controllerName = 'ControllerB';
        }
    
        $controller = app()->make('AppHttpControllersApplication\' . $controllerName);
    
        return $controller->callAction('methodName', $params = ['code' => $code]);
    })->where(['site' => '.*(-(d|p)(.*?).).*']);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search