Недавно столкнулся с интересной задачей и, думаю, что решение может оказаться кому-то полезным.
Наш сайт использует локализацию + региональность и соответственно ссылки имеют вид:
1) В описании роута в requirements в _locale добавить пустое значение: _locale: ru|by -> _locale: |ru|by
Но в этом подходе url принимает вид: //minsk/aaa, а 2 слеша нам не нужны. Да и нужно убирать локаль из router->context, чтобы сгенерировать url.
2) В аннотациях к контроллеру прописать 2 разных роута.
Но здесь тоже встает вопрос: «Как генерировать url и какой роут выбирать?». Да и для всех контроллеров придется такие аннотации писать.
Решение состоит из 2х этапов: обработка входящих параметров путем подмены контроллера и генерация ссылок на странице.
Настраивать будем все ресурсы относящиеся к префиксу "/{_locale}/{region}" и которые хранятся в одной папке и описаны в app_web_common:
1. Обработка входящих параметров
Все url вида "/by/minsk/aaa" будут обрабатываться по обычной схеме через «app_web_common». А урлы, не содержащие локаль, через отдельный роут:
Но вызов контроллера производиться не будет(кроме 404), тк мы перенаправим вызов к другому контроллеру в KernelEvents::CONTROLLER. Поэтому создаем EventListener со своим обработчиком:
Если кратко, то:
— берем текущий путь
— добавляем в начало строки префикс локали
— ищем совпадения для нового урла
— заменяем данные в реквесте
— заменяем контроллер
Еще нужно запретить переход по ссылке содержащей дефолтную локаль. Для этого в этом же классе добавим еще один обработчик, но уже KernelEvents::REQUEST:
Но все это действительно если есть префикс ввиде {region} как в статье. Но как быть если нет этого префикса?
1) можно точно также все перенаправить через app_web_*_no_locale, но уже без региона.
2) Конкретно в нашем приложении есть еще и урлы без локали и региона (/aaa), поэтому мы сначала обрабатываем их, а затем если не находим, то применяем логику из «matchingWebController()»
2. Генерация ссылок
Для этого нужно переопределить класс UrlGenerator.
В параметры добавляем:
И содержимое классов:
Если кратко, то если в полученном урле есть дефолтная локаль, то ее удаляем.
Вот и всё.
Наш сайт использует локализацию + региональность и соответственно ссылки имеют вид:
/ru/minsk/aaaЗадача убрать дефолтную локаль(ru) так чтобы все url приняли вид:
/by/minsk/aaa
/minsk/aaaStackoverflow дает два варианта:
/by/minsk/aaa
1) В описании роута в requirements в _locale добавить пустое значение: _locale: ru|by -> _locale: |ru|by
Но в этом подходе url принимает вид: //minsk/aaa, а 2 слеша нам не нужны. Да и нужно убирать локаль из router->context, чтобы сгенерировать url.
2) В аннотациях к контроллеру прописать 2 разных роута.
* @Route("/{region}/{other}", name="route", ...)
* @Route("/{_locale}/{region}/{other}", name="route-with-locale", ...)
Но здесь тоже встает вопрос: «Как генерировать url и какой роут выбирать?». Да и для всех контроллеров придется такие аннотации писать.
Решение состоит из 2х этапов: обработка входящих параметров путем подмены контроллера и генерация ссылок на странице.
Настраивать будем все ресурсы относящиеся к префиксу "/{_locale}/{region}" и которые хранятся в одной папке и описаны в app_web_common:
app_web_common:
resource: "@AppBundle/Controller/Web/"
type: annotation
prefix: /{_locale}/{region}
requirements:
_locale: ru|by
region: minsk|brest
1. Обработка входящих параметров
Все url вида "/by/minsk/aaa" будут обрабатываться по обычной схеме через «app_web_common». А урлы, не содержащие локаль, через отдельный роут:
app_web_region_no_locale:
path: /{region}/{catchAll}
defaults:
_locale: ru|by
_controller: AppBundle:NoLocale:region
requirements:
region: minsk|brest
catchAll: ".+"
Но вызов контроллера производиться не будет(кроме 404), тк мы перенаправим вызов к другому контроллеру в KernelEvents::CONTROLLER. Поэтому создаем EventListener со своим обработчиком:
public function matchingWebController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$route = $request->get('_route');
$pathInfo = $request->getPathInfo();
if ($route == 'app_web_region_no_locale') {
$this->setupLocale($this->defaultLocale, $request);
$path = "/{$this->defaultLocale}{$pathInfo}";
$data = $this->router->match($path);
$request->attributes->replace($data);
unset($data['_controller']);
$request->attributes->set('_route_params', $data);
$controller = $this->resolver->getController($request);
$event->setController($controller);
}
}
Если кратко, то:
— берем текущий путь
— добавляем в начало строки префикс локали
— ищем совпадения для нового урла
— заменяем данные в реквесте
— заменяем контроллер
Еще нужно запретить переход по ссылке содержащей дефолтную локаль. Для этого в этом же классе добавим еще один обработчик, но уже KernelEvents::REQUEST:
public function redirectFromDefaultLocale(GetResponseEvent $event)
{
$request = $event->getRequest();
$queryString = $request->getQueryString();
$pathInfo = $request->getPathInfo();
$pathInfoArr = explode('/', $pathInfo);
$queryString = $queryString ? "?" . $queryString : '';
// if route contains default locale
if (isset($pathInfoArr[1]) && $pathInfoArr[1] == $this->defaultLocale) {
unset($pathInfoArr[1]);
$this->setupLocale($this->defaultLocale, $request);
$response = new RedirectResponse($pathInfo . $queryString);
$event->setResponse($response);
}
}
Но все это действительно если есть префикс ввиде {region} как в статье. Но как быть если нет этого префикса?
/ru/aaaа нужно:
/by/aaa
/aaaЗдесь все зависит от приложения:
/by/aaa
1) можно точно также все перенаправить через app_web_*_no_locale, но уже без региона.
2) Конкретно в нашем приложении есть еще и урлы без локали и региона (/aaa), поэтому мы сначала обрабатываем их, а затем если не находим, то применяем логику из «matchingWebController()»
2. Генерация ссылок
Для этого нужно переопределить класс UrlGenerator.
В параметры добавляем:
router.class: AppBundle\Routing\Router\Router
router.options.generator_base_class: AppBundle\Routing\Generator\UrlGenerator\UrlGenerator
И содержимое классов:
class Router extends BaseRouter implements ContainerAwareInterface
{
private $container;
public function __construct(ContainerInterface $container, $resource, array $options = array(), RequestContext $context = null)
{
parent::__construct($container, $resource, $options, $context);
$this->setContainer($container);
}
public function getGenerator()
{
$generator = parent::getGenerator();
$generator->setContainer($this->container);
return $generator;
}
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
class UrlGenerator extends BaseUrlGenerator implements ContainerAwareInterface
{
/**
* @var ContainerInterface
*/
private $container;
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array())
{
$defaultLocale = $this->container->getParameter('default_locale');
// здесь все содержимое родительского метода parent::doGenerate
// ....
// заменяем одну строчку $url = $token[1].$mergedParams[$token[3]].$url; на
if (!($token[3] == '_locale' && $mergedParams[$token[3]] == $defaultLocale)) {
$url = $token[1].$mergedParams[$token[3]].$url;
}
// ....
}
/**
* @param ContainerInterface|null $container
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
Если кратко, то если в полученном урле есть дефолтная локаль, то ее удаляем.
Вот и всё.