Sean Fraser
Sean Fraser

Reputation: 352

Default value for route params in generated url

I have a site which is localized into several languages. Every public route is prefixed with the locale key (e.g. /{locale}/foo/bar), which gets caught and applied by middleware.

When generating URLs to point to other pages, I end up needing to feed the current locale into every url, like so:

<a href={{ route('foo.bar', ['locale' => app()->getLocale()]) }}">Foo Bar</a>

Otherwise the output url will contain %7Blocale%7D, which breaks it. This strikes me as needlessly verbose. Is there not a way to specify a default value for a given named parameter, such that if no value is explicitly provided for 'locale' it can be defaulted to whatever the current locale is?

I've inspected the UrlGenerator class, but I don't see anything to that effect.

The Route class has a defaults property, but that only appears to be used as part of binding the route to the current request.

Ultimately, not a huge issue, just wondering if anyone has any ideas for ways to save a bit of sanity.

Upvotes: 2

Views: 923

Answers (3)

Simon
Simon

Reputation: 11

You can use URL defaults as well at a middleware:

   use Illuminate\Support\Facades\URL;

     URL::defaults(
       [
            'locale' =>  $locale 
       ] 
    );

Upvotes: 1

Sean Fraser
Sean Fraser

Reputation: 352

There isn't any built in means of doing this, but I managed to achieve the desired result by extending the UrlGenerator

<?php

namespace App\Services;

use Illuminate\Routing\UrlGenerator as BaseGenerator;
use Illuminate\Support\Arr;

class UrlGenerator extends BaseGenerator
{
    protected $default_parameters = [];

    public function setDefaultParameter($key, $value){
        $this->default_parameters[$key] = $value;
    }

    public function removeDefaultParameter($key){
        unset($this->default_parameters[$key]);
    }

    public function getDefaultParameter($key){
        return isset($this->default_parameters[$key]) ? $this->default_parameters[$key] : null;
    }

    protected function replaceRouteParameters($path, array &$parameters)
    {
        if (count($parameters) || count($this->default_parameters)) {
            $path = preg_replace_sub(
                '/\{.*?\}/', $parameters, $this->replaceNamedParameters($path, $parameters)
            );
        }
        return trim(preg_replace('/\{.*?\?\}/', '', $path), '/');
    }

    protected function replaceNamedParameters($path, &$parameters)
    {
        return preg_replace_callback('/\{(.*?)\??\}/', function ($m) use (&$parameters) {
            return isset($parameters[$m[1]]) ? Arr::pull($parameters, $m[1]) : ($this->getDefaultParameter($m[1]) ?: $m[0]);
        }, $path);
    }
}

Then rebinding our subclass into the service container

class RouteServiceProvider extends ServiceProvider
{
    public function register(){
        parent::register();

        //bind our own UrlGenerator
        $this->app['url'] = $this->app->share(function ($app) {
            $routes = $app['router']->getRoutes();
            $url = new UrlGenerator(
                $routes, $app->rebinding(
                    'request', function ($app, $request) {
                        $app['url']->setRequest($request);
                    }
                )
            );
            $url->setSessionResolver(function () {
                return $this->app['session'];
            });
            $app->rebinding('routes', function ($app, $routes) {
                $app['url']->setRoutes($routes);
            });

            return $url;
        });
    }

    //...
}

Then all I needed to do was inject the default locale into the UrlGenerator from the Locale middleware

public function handle($request, Closure $next, $locale = null) {
    //...

    $this->app['url']->setDefaultParameter('locale', $locale);

    return $next($request);
}

Now route('foo.bar') will automatically bind the current locale to the route, unless another is explicitly provided.

Upvotes: 0

Patrick Stephan
Patrick Stephan

Reputation: 1829

When you define your routes, use optional variables with defaults:

Routes:

Route::get('{locale?}/foo/bar', 'Controller@fooBar');

Controller:

public function __construct()
{
    $this->locale = session()->has('locale') ? session('locale') : 'en';
}

public function fooBar($locale = null)
{
    $locale = $locale ?: $this->locale;
}

OR:

public function fooBar($locale = 'en')
{
    $locale = $locale ?: $this->locale;
}

Wherever you call your route:

<a href="{{ route('foo.bar', null) }}">Foo Bar</a>

You could optionally put the constructor in a BaseController class that all your other controllers extend.

There may be better ways to do this, but this would keep you from having to include the locale wherever you call a route.

Upvotes: 0

Related Questions