-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Routing] Implement i18n routing #26143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
class Route | ||
{ | ||
private $path; | ||
private $locales = array(); | ||
private $name; | ||
private $requirements = array(); | ||
private $options = array(); | ||
|
@@ -38,11 +39,20 @@ class Route | |
*/ | ||
public function __construct(array $data) | ||
{ | ||
if (isset($data['locales'])) { | ||
throw new \BadMethodCallException(sprintf('Unknown property "locales" on annotation "%s".', get_class($this))); | ||
} | ||
|
||
if (isset($data['value'])) { | ||
$data['path'] = $data['value']; | ||
$data[is_array($data['value']) ? 'locales' : 'path'] = $data['value']; | ||
unset($data['value']); | ||
} | ||
|
||
if (isset($data['path']) && is_array($data['path'])) { | ||
$data['locales'] = $data['path']; | ||
unset($data['path']); | ||
} | ||
|
||
foreach ($data as $key => $value) { | ||
$method = 'set'.str_replace('_', '', $key); | ||
if (!method_exists($this, $method)) { | ||
|
@@ -62,6 +72,16 @@ public function getPath() | |
return $this->path; | ||
} | ||
|
||
public function setLocales(array $locales) | ||
{ | ||
$this->locales = $locales; | ||
} | ||
|
||
public function getLocales(): array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing phpdoc. it's not clear what this method returns at all. a better name would also be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. esp. if we want to add to possiblity to add localized hosts as well in the future There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated in 7101893 |
||
{ | ||
return $this->locales; | ||
} | ||
|
||
public function setHost($pattern) | ||
{ | ||
$this->host = $pattern; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,8 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt | |
|
||
protected $logger; | ||
|
||
private $defaultLocale; | ||
|
||
/** | ||
* This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. | ||
* | ||
|
@@ -65,11 +67,12 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt | |
'%7C' => '|', | ||
); | ||
|
||
public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) | ||
public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null) | ||
{ | ||
$this->routes = $routes; | ||
$this->context = $context; | ||
$this->logger = $logger; | ||
$this->defaultLocale = $defaultLocale; | ||
} | ||
|
||
/** | ||
|
@@ -109,7 +112,13 @@ public function isStrictRequirements() | |
*/ | ||
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) | ||
{ | ||
if (null === $route = $this->routes->get($name)) { | ||
$locale = $parameters['_locale'] | ||
?? $this->context->getParameter('_locale') | ||
?: $this->defaultLocale; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on what I see in the constructor, this->defaultLocale could be null, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, which results in a route not found. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it still produces an implicit route lookup, i.e. Instead why not normalize if (null !== $locale) {
$name .= '.'.$locale;
} that would do no? |
||
|
||
if (null !== $locale && null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) { | ||
unset($parameters['_locale']); | ||
} elseif (null === $route = $this->routes->get($name)) { | ||
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
|
||
use Doctrine\Common\Annotations\Reader; | ||
use Symfony\Component\Config\Resource\FileResource; | ||
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation; | ||
use Symfony\Component\Routing\Route; | ||
use Symfony\Component\Routing\RouteCollection; | ||
use Symfony\Component\Config\Loader\LoaderInterface; | ||
|
@@ -119,9 +120,11 @@ public function load($class, $type = null) | |
} | ||
} | ||
|
||
/** @var $annot RouteAnnotation */ | ||
if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { | ||
$globals['path'] = ''; | ||
$globals['path'] = null; | ||
$globals['name'] = ''; | ||
$globals['locales'] = array(); | ||
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); | ||
} | ||
|
||
|
@@ -137,11 +140,6 @@ protected function addRoute(RouteCollection $collection, $annot, $globals, \Refl | |
$name = $globals['name'].$name; | ||
|
||
$defaults = array_replace($globals['defaults'], $annot->getDefaults()); | ||
foreach ($method->getParameters() as $param) { | ||
if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { | ||
$defaults[$param->getName()] = $param->getDefaultValue(); | ||
} | ||
} | ||
$requirements = array_replace($globals['requirements'], $annot->getRequirements()); | ||
$options = array_replace($globals['options'], $annot->getOptions()); | ||
$schemes = array_merge($globals['schemes'], $annot->getSchemes()); | ||
|
@@ -157,11 +155,57 @@ protected function addRoute(RouteCollection $collection, $annot, $globals, \Refl | |
$condition = $globals['condition']; | ||
} | ||
|
||
$route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); | ||
$path = $annot->getLocales() ?: $annot->getPath(); | ||
$prefix = $globals['locales'] ?: $globals['path']; | ||
$paths = array(); | ||
|
||
$this->configureRoute($route, $class, $method, $annot); | ||
if (\is_array($path)) { | ||
if (!\is_array($prefix)) { | ||
foreach ($path as $locale => $localePath) { | ||
$paths[$locale] = $prefix.$localePath; | ||
} | ||
} elseif ($missing = array_diff_key($prefix, $path)) { | ||
throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name.'::'.$method->name, implode('", "', array_keys($missing)))); | ||
} else { | ||
foreach ($path as $locale => $localePath) { | ||
if (!isset($prefix[$locale])) { | ||
throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name)); | ||
} | ||
|
||
$paths[$locale] = $prefix[$locale].$localePath; | ||
} | ||
} | ||
} elseif (\is_array($prefix)) { | ||
foreach ($prefix as $locale => $localePrefix) { | ||
$paths[$locale] = $localePrefix.$path; | ||
} | ||
} else { | ||
$paths[] = $prefix.$path; | ||
} | ||
|
||
$collection->add($name, $route); | ||
foreach ($method->getParameters() as $param) { | ||
if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) { | ||
continue; | ||
} | ||
foreach ($paths as $locale => $path) { | ||
if (false !== strpos($path, sprintf('{%s}', $param->name))) { | ||
$defaults[$param->name] = $param->getDefaultValue(); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
foreach ($paths as $locale => $path) { | ||
$route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); | ||
$this->configureRoute($route, $class, $method, $annot); | ||
if (0 !== $locale) { | ||
$route->setDefault('_locale', $locale); | ||
$route->setDefault('_canonical_route', $name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
$collection->add($name.'.'.$locale, $route); | ||
} else { | ||
$collection->add($name, $route); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -208,7 +252,8 @@ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMetho | |
protected function getGlobals(\ReflectionClass $class) | ||
{ | ||
$globals = array( | ||
'path' => '', | ||
'path' => null, | ||
'locales' => array(), | ||
'requirements' => array(), | ||
'options' => array(), | ||
'defaults' => array(), | ||
|
@@ -228,6 +273,8 @@ protected function getGlobals(\ReflectionClass $class) | |
$globals['path'] = $annot->getPath(); | ||
} | ||
|
||
$globals['locales'] = $annot->getLocales(); | ||
|
||
if (null !== $annot->getRequirements()) { | ||
$globals['requirements'] = $annot->getRequirements(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to have this setter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how annotations hydrate itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nitpicking, it's how this particular annotation behaves. Sadly there's no real standard for it. Doctrine for example, uses public properties from what I can tell. Other annotations might use a k=>v storage.
For this annotation it's required to have a setter as @frankdejonge mentioned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in theory we could reuse
set/getPath()
supporting both string and array API. Not sure if that simplifies the code in any way..There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ro0NL could be, but we've handled it in the constructor now, which enables us to add type check for returns and make the "special" case more explicit. I think especially making the case explicit adds a lot of value because it's more clear.