1
votes

I need to be able to define routes where the /{_locale} part is optional. This means I need these URLs to use the same controller:

  1. my-domain.com/ (the locale will be set by a request listener)
  2. my-domain.com/[any-locale]/

Same goes with:

  1. my-domain.com/my-page (the locale will be set by a request listener)
  2. my-domain.com/[any-locale]/my-page

The problem is I can't define two class @Route annotations as this isn't valid/accepted by Symfony; and I can't duplicate each and every route in my controller classes because there will be a large number and that would be really bad!

I've tried having only one class annotation but I couldn't get it to work. Here are my attempts:

First:

/**
 * @Route("/{_locale}", requirements={"_locale": "(\w{2,3})?"})
 */
class DefaultController extends Controller {

    /**
     * @Route("/")
     * @param Request $request
     * @return Response
     */
    public function indexAction(Request $request) {
        return new Response('index '.$request->getLocale());
    }

    /**
     * @Route("/my-page")
     * @param Request $request
     * @return Response
     */
    public function testAction(Request $request) {
        return new Response('test '.$request->getLocale());
    }

}

Conclusions:

  1. Works
  2. Works
  3. Fails (because only my-domain.com//my-page would work)
  4. Works

Second:

I thought the problem was the leading slash, so i tried this:

/**
 * @Route("{slash_and_locale}", requirements={"slash_and_locale": "\/?(\w{2,3})?"}, defaults={"slash_and_locale": ""})
 */
class DefaultController extends Controller { // ...

Conclusions:

  1. Works
  2. Works
  3. Fails (same reason)
  4. Works

I've seen this (old) question but no proper answer has been submitted yet :(

Anyone with a suggestion? Would be really helpful, thanks!

3
Not sure this bundle does that exactly but I will try to see if it is customizable the way I want.Quentin

3 Answers

1
votes

Here's what I eventually decided to do:

  1. despite what I wanted, I define two routes for each controller action (see example below);
  2. I extend Symfony's RoutingExtension to automatically pick the correct route when using path or url methods.

Here's what my double route configuration looks like (note the double route, the second having a +locale suffix):

<?php

// src\AppBundle\Controller\DefaultController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller {

    /**
     * @Route("/", name="front_index")
     * @Route("/{_locale}/", name="front_index+locale", requirements={"_locale": "\w{2,3}"})
     * @return Response
     */
    public function indexAction() {
        return $this->render('test/domain-translation.html.twig');
    }

}

And here's my RoutingExtension overload:

<?php

// src\AppBundle\Twig\LocaleRoutingExtension.php

namespace AppBundle\Twig;

use AppBundle\Document\Domain;
use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

class LocaleRoutingExtension extends RoutingExtension {

    /** @var ContainerInterface */
    protected $container;

    /** @var Domain */
    protected $domain;

    /** @var Request */
    protected $request;

    public function setContainer(ContainerInterface $container) {
        $this->container = $container;
        $this->domain = $this->container->get('app.domain_service')->getDomain();
        $this->request = $this->container->get('request_stack')->getMasterRequest();
    }

    protected function processNameAndParameters($name, $parameters) {

        $nameWithLocale = $name . '+locale';
        $routes = $this->container->get('router')->getRouteCollection();
        $route = $routes->get($nameWithLocale);

        if (array_key_exists('no-locale-filter', $parameters) && $parameters['no-locale-filter']) {
            unset($parameters['no-locale-filter']);
            return array('name' => $name, 'parameters' => $parameters);
        }

        if (isset($this->domain) && $this->domain->hasMultipleLocales() && isset($route)) {
            $name = $nameWithLocale;

            $locale = null;
            if (!array_key_exists('_locale', $parameters)) {
                if (isset($this->request)) {
                    $locale = $this->request->getLocale();
                } else {
                    $locale = $this->domain->getDefaultLocale();
                }
            }

            $parameters['_locale'] = $locale;
        }

        return array('name' => $name, 'parameters' => $parameters);
    }

    public function getPath($name, $parameters = array(), $relative = false) {
        $processed = $this->processNameAndParameters($name, $parameters);
        return parent::getPath($processed['name'], $processed['parameters'], $relative);
    }

    public function getUrl($name, $parameters = array(), $schemeRelative = false) {
        $processed = $this->processNameAndParameters($name, $parameters);
        return parent::getUrl($processed['name'], $processed['parameters'], $schemeRelative);
    }

}

I hope this helps!

0
votes

I do this in my project (yml)

language_selection:
    path: /{locale}/{path}
    defaults: { _controller: PurchaserBundle:Home:langSelection, path: "" }
    requirements:
        path:  .+
        locale: es|ca|en

And in the controller:

public function langSelectionAction($locale, $path)
{
    if (in_array($locale, array('es', 'en', 'ca'))) {
        return $this->redirect('/' . $path);
    }

    $this->create404NotFound();
}

It is maybe a rudimentary way to solve the problem, but works. All of your "default" routes works, and if you access to the site with any locale prefix, your listener change the language and this controller redirects to the correct route in the current language.

I hope it helps.

0
votes

I found a way to customize the routingComponent by just extending it. This way every url which matches no routes, will be retried prefixing url with default locale

<?php

namespace App\Component;

use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcher as BaseUrlMatcher;

class UrlMatcher extends BaseUrlMatcher
{
    public function match($pathinfo)
    {
        try {
            return parent::match($pathinfo);
        } catch (ResourceNotFoundException $exception) {
            $url = sprintf('/%s%s', 'en', $pathinfo);

            return parent::match($url);
        }
    }
}

Set new urlMatcher in parameters:

parameters:
        # UrlMatcher
        router.options.matcher.cache_class: ~
        router.options.matcher_class: App\Component\UrlMatcher