DEV Community

Cover image for Laravel multiPatternRoute
marouane moutih
marouane moutih

Posted on

Laravel multiPatternRoute

when we develop a multilingual web application, sometimes we face a case where the pattern of the route is different between two languages.

how we can solve such a problem?

  • First, we need to create our standard for route names, in our case, we opt for this scheme: "{name}.{locale}" when {name} is the route name and {locale} is a language or a group of languages.
Route::get('/', App\Http\Controllers\Front\HomeController::class)->name("home.fr");
Route::get('/{locale}', App\Http\Controllers\Front\HomeController::class)->where(['locale' => '[a-z]{2,3}'])
    ->name("home.locale");
Enter fullscreen mode Exit fullscreen mode
  • Second, we need our own Helper to generate URLs. we will need a class that will parse the list of routes and get the routes linked to the name we provide, After that the class will take the list of parameters that we pass and return just the parameters needed for the current pattern, also it will inject constant parameters if they exist.
class MultiPatternRoute{

    protected string $locale;
    protected $routes;

    public function __construct()
    {
        $this->routes =  Route::getRoutes()->getRoutesByName();
        $this->locale = app()->getLocale();
    }

    public function getParameters (string $routeName,array $parameters) : array
    {
        if(!isset($this->routes[$routeName.".fr"]) || !isset($this->routes[$routeName.".locale"]))
            throw new \Exception("unknown route : ". $routeName.".fr");

        $route_fr = $this->getCompiledRoute($this->routes[$routeName.".fr"]);
        $route_locale = $this->getCompiledRoute($this->routes[$routeName.".locale"]);
        if($this->locale === "fr"){
            $route = $routeName.".fr";
            return [$route,$this->getStaticParameters($route ,$this->cleanParameters($parameters, $route_fr->getVariables(), $route_locale->getVariables()))];
        }
        $route = $routeName.".locale";
        $parameters = $this->getStaticParameters($route, $this->cleanParameters($parameters, $route_locale->getVariables(), $route_fr->getVariables()));
        $parameters["locale"] = app()->getLocale();
        return [$route,$parameters];

    }

    private function getCompiledRoute(RoutingRoute $route): CompiledRoute
    {
        return $route->getCompiled()!==null?$route->getCompiled():$route->toSymfonyRoute()->compile();
    }

    private  function cleanParameters(array $parameters, array $neededParametersA, array $neededParametersB) : array
    {
        $neededParameters = array_intersect(array_keys($parameters), $neededParametersA);
        $queryStringParameters = array_diff(array_keys($parameters), $neededParametersB);
        $parameters_keys = array_unique (array_merge ($neededParameters,$queryStringParameters));
        return array_filter($parameters, fn($k)=>in_array($k,$parameters_keys),ARRAY_FILTER_USE_KEY);
    }

    private function getStaticParameters(string $routeName, array $parameters) : array
    {

        $controller = $this->routes[$routeName]->getController();
        if(!method_exists($controller,'getConstantRouteParameters'))
            return $parameters;
        return array_merge($this->routes[$routeName]->getController()::getConstantRouteParameters($this->locale),$parameters);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Third, we provide this class as Singleton via the container in our AppServiceProvider.
$this->app->singleton(
            'MultiPatternRoute',
           function(){
              return new  MultiPatternRoute();
           }
        );
Enter fullscreen mode Exit fullscreen mode
  • Fourth, we create a helper function that will consume our class and generate our URL.
if ( !function_exists('multiPatternRoute') )
{
    function multiPatternRoute(string $routeName, array $parameters =[], bool $isAbsolute = true){
        list($route, $parameters) = app("MultiPatternRoute")->getParameters($routeName, $parameters);
        return route($route, $parameters, $isAbsolute);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Fifth, we prepare our controllers to communicate with our class by providing constant parameters if they exist. we do that thanks to a trait as a result.
trait MultiPatternRoutable
{

    public static function getConstantRouteParameters($locale): array
    {
        return  isset(static::$constantRouteParameters)?static::$constantRouteParameters[$locale]:[];
    }

    public function loadConstantRouteParameters($locale): array
    {
        return array_keys(static::getConstantRouteParameters($locale));
    }

}
Enter fullscreen mode Exit fullscreen mode

then in the controller

class DestinationController extends Controller
{
    use MultiPatternRoutable;

    protected static array $constantRouteParameters =[
        "fr" => [],
        "en" => ["slugSeo"=>"destinations"],
        "de" => ["slugSeo"=>"unsere-reiseziele"],
        "it" => ["slugSeo"=>"destinazioni"],
    ];

    public function __invoke(Request $request, string $slug){
        return view("front.pages.destination", compact("slug"));
    }

}
Enter fullscreen mode Exit fullscreen mode

finally, we add a middleware to clean the request object from the constant or unnecessary parameters for the current route.

class Locale
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $locale = isset($request->route()->parameters['locale'])?$request->route()->parameters['locale']:null;
        if($locale){
            app()->setLocale($locale);
            $request->route()->forgetParameter('locale');
        }
        $otherParameterToForgot =$request->route()->getController()->loadConstantRouteParameters(app()->getLocale());
        foreach($otherParameterToForgot as $parameter){
            $request->route()->forgetParameter($parameter);
        }
        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

then in our route file


Route::group(["middleware" => ["locale"]], function () {

    Route::get('/', App\Http\Controllers\Front\HomeController::class)->name("home.fr");
    Route::get('/{locale}', App\Http\Controllers\Front\HomeController::class)->where(['locale' => '[a-z]{2,3}'])
    ->name("home.locale");

    Route::get("/{locale}/{slugSeo}", App\Http\Controllers\Front\DestinationsController::class)
        ->where(['locale' => '[a-z]{2,3}', 'slugSeo' => 'tutte-destinazioni|unsere-reiseziele|destinations-all'])
        ->name("destinations.locale");
    Route::get("/destinations/notre-list-destination", App\Http\Controllers\Front\DestinationsController::class)
        ->name("destinations.fr");

    Route::get("/{locale}/{slugSeo}/{slug}", App\Http\Controllers\Front\DestinationController::class)
        ->where(['locale' => '[a-z]{2,3}', 'slugSeo' => 'destinazioni|reiseziele|destinations'])
        ->name("destination.locale");
    Route::get("/destinations-{slug}-produit-{slugAlt}.html", App\Http\Controllers\Front\DestinationController::class)
        ->name("destination.fr");
});
Enter fullscreen mode Exit fullscreen mode

then in blade file

 <a href="{{ multiPatternRoute('destination',["slug"=>"france","slugAlt"=>"france"]) }}">France</a>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)