1
votes

Background

We have a (fairly typical?) arrangement for a multilingual Symfony CMF website, where resource paths are prefixed by the desired locale—for example:

  • http://www.example.com/en/path/to/english-resource.html; and
  • http://www.example.com/fr/voie/à/ressource-française.html.

We are using RoutingAutoBundle to store such routes in the content repository, and DynamicRouter to utilise them: simple and easy.

If a GET request arrives without a locale prefix, we would like to:

  1. determine the most appropriate locale for the user; and then
  2. redirect1 the user to the same path but with locale prefix added.

Current Approach

The first part is an obvious candidate for LuneticsLocaleBundle, with router higher in its guessing order than our desired fallback methods: again, simple and easy.

However, how best to implement the second part is a little less obvious. Currently we have configured Symfony's default/static router to have a lower priority in the routing chain than DynamicRouter, and have therein configured a controller as follows:

/**
 * @Route("/{path}", requirements={"path" = "^(?!(en|fr)(/.*)?$)"})
 * @Method({"GET"})
 */
public function localeNotInUriAction()
{
    $request = this->getRequest();

    $this->redirect(
        '/'
      . $request->getLocale()     // set by Lunetics
      . $request->getRequestUri()
    );
}

But this feels rather hacky and I'm on the search for something "cleaner".

A better way?

Initially I thought to modify LuneticsLocaleBundle so that it would fire an event whenever a guesser determines the locale, thinking that if it was not the RouterLocaleGuesser then we could infer that the requested URI did not contain a locale. However this clearly isn't the case, since the RouterLocaleGuesser will only determine the locale if there was a route in the first place—so I'd not have made any progress.

I'm now a bit stuck for any other ideas. Perhaps I'm already doing the right thing after all? If so, then all I need to do is find some way to inject the permitted locales (from the config) into the requirement regex…


  1. External redirection, i.e. via a response with HTTP 302 status.
1

1 Answers

1
votes

we use a custom 404 handler and lunetics:

exception_listener:
    class: AppBundle\EventListener\ExceptionListener
    arguments:
        container: "@service_container"
    tags:
        - { name:"kernel.event_listener", event:kernel.exception, handler:onKernelException }

and the php class

class ExceptionListener
{
/**
 * @var ContainerInterface
 */
protected $container;

public function __construct(ContainerInterface $container)
{
    $this->container = $container;
}

public function onKernelException(GetResponseForExceptionEvent $event)
{
    if ($this->container->getParameter('kernel.debug')) {
        // do not interfere with error handling while debugging
        return;
    }
    $exception = $event->getException();
    if ($exception instanceof NotFoundHttpException) {
        $this->handle404($event);
        return;
    }

    // ...
}

public function handle404(GetResponseForExceptionEvent $event)
{
    $request = $event->getRequest();

    if (preg_match('#^\/(de|fr|en)\/#', $request->getPathInfo())) {
        // a real 404, these are nicely handled by Twig
        return;
    }

    // i *think* that the locale is not set on the request, as lunetics comes after routing, and the routing will raise the 404
    $bestLang = $this->container->get('lunetics_locale.guesser_manager')->runLocaleGuessing($request);
    if (! $bestLang) {
        $bestLang = 'de';
    }

    $qs = $request->getQueryString();
    if (null !== $qs) {
        $qs = '?'.$qs;
    }
    $url = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . '/' . $bestLang . $request->getPathInfo() . $qs;

    $this->redirect($event, $url);
}

it would be nicer to also check if the target path actually exists - as is, we will redirect /foobar to /de/foobar and display a 404 for that one, which is not that elegant.