Last active
December 23, 2022 03:59
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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)), | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
language_routing: | |
resource: "@YourBundle/Resources/config/multilanguage-routes.yml" | |
prefix: /{_locale} | |
requirements: | |
_locale: de|en | |
defaults: { _locale: de} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
parameters: | |
locale: de | |
languages: [de, en] | |
services: | |
app.locale.listener: | |
class: YourBundle\EventListener\LocaleListener | |
arguments: ["@router","%languages%","%locale%"] | |
tags: | |
- { name: kernel.event_subscriber } |
@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
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.