Skip to content

Instantly share code, notes, and snippets.

@kunicmarko20
Last active December 23, 2022 03:59
Show Gist options
  • Save kunicmarko20/02a42c76f638322d58b1def7d2e770d7 to your computer and use it in GitHub Desktop.
Save kunicmarko20/02a42c76f638322d58b1def7d2e770d7 to your computer and use it in GitHub Desktop.
Symfony Locale Listener that redirects to default language route if no language has been added instead of 404
<?php
namespace YourBundle\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
class LocaleListener implements EventSubscriberInterface
{
/**
* @var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* @var urlMatcher \Symfony\Component\Routing\Matcher\UrlMatcher;
*/
private $urlMatcher;
private $oldUrl;
private $newUrl;
private $languages;
private $defaultLanguage;
public function __construct(RouterInterface $router, $languages, $defaultLanguage = 'de')
{
$this->routeCollection = $router->getRouteCollection();
$this->languages = $languages;
$this->defaultLanguage = $defaultLanguage;
$context = new RequestContext("/");
}
public function onKernelRequest(GetResponseEvent $event)
{
//GOAL:
// Redirect all incoming requests to their /locale/route equivalent when exists.
// Do nothing if it already has /locale/ in the route to prevent redirect loops
// Do nothing if the route requested has no locale param
$request = $event->getRequest();
$this->newUrl = $request->getPathInfo();
$this->oldUrl = $request->headers->get('referer');
$locale = $this->checkLanguage();
if($locale === null) return;
$request->setLocale($locale);
$pathLocale = "/".$locale.$this->newUrl;
//We have to catch the ResourceNotFoundException
try {
//Try to match the path with the local prefix
$this->urlMatcher->match($pathLocale);
$event->setResponse(new RedirectResponse($pathLocale));
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
} catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
}
}
private function checkLanguage(){
foreach($this->languages as $language){
if(preg_match_all("/\/$language\//", $this->newUrl))
return null;
if(preg_match_all("/\/$language\//", $this->oldUrl))
return $language;
}
return $this->defaultLanguage;
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
language_routing:
resource: "@YourBundle/Resources/config/multilanguage-routes.yml"
prefix: /{_locale}
requirements:
_locale: de|en
defaults: { _locale: de}
parameters:
locale: de
languages: [de, en]
services:
app.locale.listener:
class: YourBundle\EventListener\LocaleListener
arguments: ["@router","%languages%","%locale%"]
tags:
- { name: kernel.event_subscriber }
@kunicmarko20
Copy link
Author

Fixed, didn't use this code in a long time, it can be written a lot better. If I find the time I will refactor all gists.

@wiejakp
Copy link

wiejakp commented Dec 23, 2022

@kunicmarko20, instead of redirecting, you can have optional locale prefix in your URL.

For example you can have:

http://example.com/
http://example.com/en/
http://example.com/pl/

or

http://example.com/text/
http://example.com/en/test/
http://example.com/pl/test/

The trick is to configure controllers to have prefix and set one of prefixes as a forward slash (/):

### config/routes/annotations.yaml

controllers:
    resource: ../../src/Controller/
    type: annotation
    prefix:
        /: '/'
        en: '/en'
        pl: '/pl'
    locale: ''
    defaults:
        _locale: ''
    requirements:
        _locale: 'en|pl'
    trailing_slash_on_root: true
kernel:
    resource: ../../src/Kernel.php
    type: annotation

Notice that request attribute parameter _locale will be "/" when you enter path without giving locale.

Then I handle my locale through subscriber like so:

<?php
declare(strict_types=1);

namespace App\EventSubscriber;

use App\Entity\Config\ConfigApp;
use App\Entity\User;
use App\Http\Requests\Request;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;

/**
 * @final
 */
class LocaleSubscriber implements EventSubscriberInterface
{
    /**
     * @var ConfigApp
     */
    private $configApp;

    /**
     * @var Security
     */
    private $security;

    /**
     * @param ConfigApp $configApp
     * @param Security $security
     */
    public function __construct(ConfigApp $configApp, Security $security)
    {
        $this->configApp = $configApp;
        $this->security = $security;
    }

    /**
     * {@inheritDoc}
     */
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => [['onKernelRequest', 21]],
        ];
    }

    /**
     * @param RequestEvent $event
     */
    public function onKernelRequest(RequestEvent $event)
    {
        /** @var Request $request */
        $request = $event->getRequest();

        /** @var Session $session */
        $session = $request->getSession();

        /** @var string $defaultLocale */
        $defaultLocale = $this->configApp->getLocale();

        /** @var User|null $user */
        $user = $this->security->getUser();

        if ($user && false === empty($user->getLocale())) {
            $defaultLocale = $user->getLocale();
        }

        try {
            $locale = $request->attributes->get('_locale');

            if ('/' === $locale) {
                $locale = $session->get('_locale', $defaultLocale);
            } else {

            }
            
            $session->set('_locale', $locale);
            $request->attributes->set('_locale', $locale);
            $request->setLocale($locale);
        } catch (\Error $e) {

        } catch (\Exception $e) {

        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment