package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -15,18 +15,15 @@ use Symfony\Component\Routing\Exception\InvalidArgumentException;
class Alias
{
private $id;
private $deprecation = [];
private string $id;
private array $deprecation = [];
public function __construct(string $id)
{
$this->id = $id;
}
/**
* @return static
*/
public function withId(string $id): self
public function withId(string $id): static
{
$new = clone $this;
@@ -56,7 +53,7 @@ class Alias
*
* @throws InvalidArgumentException when the message template is invalid
*/
public function setDeprecated(string $package, string $version, string $message): self
public function setDeprecated(string $package, string $version, string $message): static
{
if ('' !== $message) {
if (preg_match('#[\r\n]|\*/#', $message)) {

View File

@@ -24,129 +24,55 @@ namespace Symfony\Component\Routing\Annotation;
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Route
{
private $path;
private $localizedPaths = [];
private $name;
private $requirements = [];
private $options = [];
private $defaults = [];
private $host;
private $methods = [];
private $schemes = [];
private $condition;
private $priority;
private $env;
private ?string $path = null;
private array $localizedPaths = [];
private array $methods;
private array $schemes;
/**
* @param array|string $data data array managed by the Doctrine Annotations library or the path
* @param array|string|null $path
* @param string[] $requirements
* @param string[]|string $methods
* @param string[]|string $schemes
*
* @throws \BadMethodCallException
* @param array<string|\Stringable> $requirements
* @param string[]|string $methods
* @param string[]|string $schemes
*/
public function __construct(
$data = [],
$path = null,
string $name = null,
array $requirements = [],
array $options = [],
array $defaults = [],
string $host = null,
$methods = [],
$schemes = [],
string $condition = null,
int $priority = null,
string|array $path = null,
private ?string $name = null,
private array $requirements = [],
private array $options = [],
private array $defaults = [],
private ?string $host = null,
array|string $methods = [],
array|string $schemes = [],
private ?string $condition = null,
private ?int $priority = null,
string $locale = null,
string $format = null,
bool $utf8 = null,
bool $stateless = null,
string $env = null
private ?string $env = null
) {
if (\is_string($data)) {
$data = ['path' => $data];
} elseif (!\is_array($data)) {
throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data)));
} elseif ([] !== $data) {
$deprecation = false;
foreach ($data as $key => $val) {
if (\in_array($key, ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env', 'value'])) {
$deprecation = true;
}
}
if ($deprecation) {
trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__);
} else {
$localizedPaths = $data;
$data = ['path' => $localizedPaths];
}
if (\is_array($path)) {
$this->localizedPaths = $path;
} else {
$this->path = $path;
}
if (null !== $path && !\is_string($path) && !\is_array($path)) {
throw new \TypeError(sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, get_debug_type($path)));
$this->setMethods($methods);
$this->setSchemes($schemes);
if (null !== $locale) {
$this->defaults['_locale'] = $locale;
}
$data['path'] = $data['path'] ?? $path;
$data['name'] = $data['name'] ?? $name;
$data['requirements'] = $data['requirements'] ?? $requirements;
$data['options'] = $data['options'] ?? $options;
$data['defaults'] = $data['defaults'] ?? $defaults;
$data['host'] = $data['host'] ?? $host;
$data['methods'] = $data['methods'] ?? $methods;
$data['schemes'] = $data['schemes'] ?? $schemes;
$data['condition'] = $data['condition'] ?? $condition;
$data['priority'] = $data['priority'] ?? $priority;
$data['locale'] = $data['locale'] ?? $locale;
$data['format'] = $data['format'] ?? $format;
$data['utf8'] = $data['utf8'] ?? $utf8;
$data['stateless'] = $data['stateless'] ?? $stateless;
$data['env'] = $data['env'] ?? $env;
$data = array_filter($data, static function ($value): bool {
return null !== $value;
});
if (isset($data['localized_paths'])) {
throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', static::class));
if (null !== $format) {
$this->defaults['_format'] = $format;
}
if (isset($data['value'])) {
$data[\is_array($data['value']) ? 'localized_paths' : 'path'] = $data['value'];
unset($data['value']);
if (null !== $utf8) {
$this->options['utf8'] = $utf8;
}
if (isset($data['path']) && \is_array($data['path'])) {
$data['localized_paths'] = $data['path'];
unset($data['path']);
}
if (isset($data['locale'])) {
$data['defaults']['_locale'] = $data['locale'];
unset($data['locale']);
}
if (isset($data['format'])) {
$data['defaults']['_format'] = $data['format'];
unset($data['format']);
}
if (isset($data['utf8'])) {
$data['options']['utf8'] = filter_var($data['utf8'], \FILTER_VALIDATE_BOOLEAN) ?: false;
unset($data['utf8']);
}
if (isset($data['stateless'])) {
$data['defaults']['_stateless'] = filter_var($data['stateless'], \FILTER_VALIDATE_BOOLEAN) ?: false;
unset($data['stateless']);
}
foreach ($data as $key => $value) {
$method = 'set'.str_replace('_', '', $key);
if (!method_exists($this, $method)) {
throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, static::class));
}
$this->$method($value);
if (null !== $stateless) {
$this->defaults['_stateless'] = $stateless;
}
}
@@ -220,9 +146,9 @@ class Route
return $this->defaults;
}
public function setSchemes($schemes)
public function setSchemes(array|string $schemes)
{
$this->schemes = \is_array($schemes) ? $schemes : [$schemes];
$this->schemes = (array) $schemes;
}
public function getSchemes()
@@ -230,9 +156,9 @@ class Route
return $this->schemes;
}
public function setMethods($methods)
public function setMethods(array|string $methods)
{
$this->methods = \is_array($methods) ? $methods : [$methods];
$this->methods = (array) $methods;
}
public function getMethods()

View File

@@ -1,6 +1,23 @@
CHANGELOG
=========
6.2
---
* Add `Requirement::POSITIVE_INT` for common ids and pagination
6.1
---
* Add `getMissingParameters` and `getRouteName` methods on `MissingMandatoryParametersException`
* Allow using UTF-8 parameter names
* Support the `attribute` type (alias of `annotation`) in annotation loaders
* Already encoded slashes are not decoded nor double-encoded anymore when generating URLs (query parameters)
* Add `EnumRequirement` to help generate route requirements from a `\BackedEnum`
* Add `Requirement`, a collection of universal regular-expression constants to use as route parameter requirements
* Add `params` variable to condition expression
* Deprecate not passing route parameters as the fourth argument to `UrlMatcher::handleRouteRequirements()`
5.3
---

View File

@@ -18,14 +18,14 @@ namespace Symfony\Component\Routing;
*/
class CompiledRoute implements \Serializable
{
private $variables;
private $tokens;
private $staticPrefix;
private $regex;
private $pathVariables;
private $hostVariables;
private $hostRegex;
private $hostTokens;
private array $variables;
private array $tokens;
private string $staticPrefix;
private string $regex;
private array $pathVariables;
private array $hostVariables;
private ?string $hostRegex;
private array $hostTokens;
/**
* @param string $staticPrefix The static prefix of the compiled route
@@ -68,7 +68,7 @@ class CompiledRoute implements \Serializable
*/
final public function serialize(): string
{
return serialize($this->__serialize());
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __unserialize(array $data): void
@@ -86,87 +86,71 @@ class CompiledRoute implements \Serializable
/**
* @internal
*/
final public function unserialize($serialized)
final public function unserialize(string $serialized)
{
$this->__unserialize(unserialize($serialized, ['allowed_classes' => false]));
}
/**
* Returns the static prefix.
*
* @return string
*/
public function getStaticPrefix()
public function getStaticPrefix(): string
{
return $this->staticPrefix;
}
/**
* Returns the regex.
*
* @return string
*/
public function getRegex()
public function getRegex(): string
{
return $this->regex;
}
/**
* Returns the host regex.
*
* @return string|null
*/
public function getHostRegex()
public function getHostRegex(): ?string
{
return $this->hostRegex;
}
/**
* Returns the tokens.
*
* @return array
*/
public function getTokens()
public function getTokens(): array
{
return $this->tokens;
}
/**
* Returns the host tokens.
*
* @return array
*/
public function getHostTokens()
public function getHostTokens(): array
{
return $this->hostTokens;
}
/**
* Returns the variables.
*
* @return array
*/
public function getVariables()
public function getVariables(): array
{
return $this->variables;
}
/**
* Returns the path variables.
*
* @return array
*/
public function getPathVariables()
public function getPathVariables(): array
{
return $this->pathVariables;
}
/**
* Returns the host variables.
*
* @return array
*/
public function getHostVariables()
public function getHostVariables(): array
{
return $this->hostVariables;
}

View File

@@ -25,28 +25,15 @@ class RoutingResolverPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
private $resolverServiceId;
private $loaderTag;
public function __construct(string $resolverServiceId = 'routing.resolver', string $loaderTag = 'routing.loader')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/routing', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->resolverServiceId = $resolverServiceId;
$this->loaderTag = $loaderTag;
}
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition($this->resolverServiceId)) {
if (false === $container->hasDefinition('routing.resolver')) {
return;
}
$definition = $container->getDefinition($this->resolverServiceId);
$definition = $container->getDefinition('routing.resolver');
foreach ($this->findAndSortTaggedServices($this->loaderTag, $container) as $id) {
foreach ($this->findAndSortTaggedServices('routing.loader', $container) as $id) {
$definition->addMethodCall('addLoader', [new Reference($id)]);
}
}

View File

@@ -25,14 +25,8 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn
/**
* @param string[] $allowedMethods
*/
public function __construct(array $allowedMethods, ?string $message = '', int $code = 0, \Throwable $previous = null)
public function __construct(array $allowedMethods, string $message = '', int $code = 0, \Throwable $previous = null)
{
if (null === $message) {
trigger_deprecation('symfony/routing', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__);
$message = '';
}
$this->allowedMethods = array_map('strtoupper', $allowedMethods);
parent::__construct($message, $code, $previous);
@@ -43,7 +37,7 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn
*
* @return string[]
*/
public function getAllowedMethods()
public function getAllowedMethods(): array
{
return $this->allowedMethods;
}

View File

@@ -19,4 +19,39 @@ namespace Symfony\Component\Routing\Exception;
*/
class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface
{
private string $routeName = '';
private array $missingParameters = [];
/**
* @param string[] $missingParameters
* @param int $code
*/
public function __construct(string $routeName = '', $missingParameters = null, $code = 0, \Throwable $previous = null)
{
if (\is_array($missingParameters)) {
$this->routeName = $routeName;
$this->missingParameters = $missingParameters;
$message = sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', $missingParameters), $routeName);
} else {
trigger_deprecation('symfony/routing', '6.1', 'Construction of "%s" with an exception message is deprecated, provide the route name and an array of missing parameters instead.', __CLASS__);
$message = $routeName;
$previous = $code instanceof \Throwable ? $code : null;
$code = (int) $missingParameters;
}
parent::__construct($message, $code, $previous);
}
/**
* @return string[]
*/
public function getMissingParameters(): array
{
return $this->missingParameters;
}
public function getRouteName(): string
{
return $this->routeName;
}
}

View File

@@ -20,8 +20,8 @@ use Symfony\Component\Routing\RequestContext;
*/
class CompiledUrlGenerator extends UrlGenerator
{
private $compiledRoutes = [];
private $defaultLocale;
private array $compiledRoutes = [];
private ?string $defaultLocale;
public function __construct(array $compiledRoutes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null)
{
@@ -31,7 +31,7 @@ class CompiledUrlGenerator extends UrlGenerator
$this->defaultLocale = $defaultLocale;
}
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH)
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
{
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')

View File

@@ -46,8 +46,6 @@ interface ConfigurableRequirementsInterface
/**
* Returns whether to throw an exception on incorrect parameters.
* Null means the requirements check is deactivated completely.
*
* @return bool|null
*/
public function isStrictRequirements();
public function isStrictRequirements(): ?bool;
}

View File

@@ -88,10 +88,7 @@ class CompiledUrlGeneratorDumper extends GeneratorDumper
return $compiledAliases;
}
/**
* {@inheritdoc}
*/
public function dump(array $options = [])
public function dump(array $options = []): string
{
return <<<EOF
<?php

View File

@@ -20,17 +20,14 @@ use Symfony\Component\Routing\RouteCollection;
*/
abstract class GeneratorDumper implements GeneratorDumperInterface
{
private $routes;
private RouteCollection $routes;
public function __construct(RouteCollection $routes)
{
$this->routes = $routes;
}
/**
* {@inheritdoc}
*/
public function getRoutes()
public function getRoutes(): RouteCollection
{
return $this->routes;
}

View File

@@ -23,15 +23,11 @@ interface GeneratorDumperInterface
/**
* Dumps a set of routes to a string representation of executable code
* that can then be used to generate a URL of such a route.
*
* @return string
*/
public function dump(array $options = []);
public function dump(array $options = []): string;
/**
* Gets the routes to dump.
*
* @return RouteCollection
*/
public function getRoutes();
public function getRoutes(): RouteCollection;
}

View File

@@ -30,6 +30,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
private const QUERY_FRAGMENT_DECODED = [
// RFC 3986 explicitly allows those in the query/fragment to reference other URIs unencoded
'%2F' => '/',
'%252F' => '%2F',
'%3F' => '?',
// reserved chars that have no special meaning for HTTP URIs in a query or fragment
// this excludes esp. "&", "=" and also "+" because PHP would treat it as a space (form-encoded)
@@ -51,7 +52,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
protected $logger;
private $defaultLocale;
private ?string $defaultLocale;
/**
* This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL.
@@ -90,47 +91,30 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$this->defaultLocale = $defaultLocale;
}
/**
* {@inheritdoc}
*/
public function setContext(RequestContext $context)
{
$this->context = $context;
}
/**
* {@inheritdoc}
*/
public function getContext()
public function getContext(): RequestContext
{
return $this->context;
}
/**
* {@inheritdoc}
*/
public function setStrictRequirements(?bool $enabled)
{
$this->strictRequirements = $enabled;
}
/**
* {@inheritdoc}
*/
public function isStrictRequirements()
public function isStrictRequirements(): ?bool
{
return $this->strictRequirements;
}
/**
* {@inheritdoc}
*/
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH)
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
{
$route = null;
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')
?: $this->defaultLocale;
$locale = $parameters['_locale'] ?? $this->context->getParameter('_locale') ?: $this->defaultLocale;
if (null !== $locale) {
do {
@@ -140,7 +124,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
} while (false !== $locale = strstr($locale, '_', true));
}
if (null === $route = $route ?? $this->routes->get($name)) {
if (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));
}
@@ -165,17 +149,15 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
* @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
* @throws InvalidParameterException When a parameter value for a placeholder is not correct because
* it does not match the requirement
*
* @return string
*/
protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = [])
protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = []): string
{
$variables = array_flip($variables);
$mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
// all params must be given
if ($diff = array_diff_key($variables, $mergedParams)) {
throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name));
throw new MissingMandatoryParametersException($name, array_keys($diff));
}
$url = '';
@@ -194,9 +176,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
throw new InvalidParameterException(strtr($message, ['{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]]));
}
if ($this->logger) {
$this->logger->error($message, ['parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName]]);
}
$this->logger?->error($message, ['parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName]]);
return '';
}
@@ -249,9 +229,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
throw new InvalidParameterException(strtr($message, ['{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]]));
}
if ($this->logger) {
$this->logger->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]);
}
$this->logger?->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]);
return '';
}
@@ -342,10 +320,8 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
*
* @param string $basePath The base path
* @param string $targetPath The target path
*
* @return string
*/
public static function getRelativePath(string $basePath, string $targetPath)
public static function getRelativePath(string $basePath, string $targetPath): string
{
if ($basePath === $targetPath) {
return '';

View File

@@ -71,12 +71,10 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface
*
* The special parameter _fragment will be used as the document fragment suffixed to the final URL.
*
* @return string
*
* @throws RouteNotFoundException If the named route doesn't exist
* @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
* @throws InvalidParameterException When a parameter value for a placeholder is not correct because
* it does not match the requirement
*/
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH);
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string;
}

View File

@@ -102,13 +102,9 @@ abstract class AnnotationClassLoader implements LoaderInterface
/**
* Loads from annotations from a class.
*
* @param string $class A class name
*
* @return RouteCollection
*
* @throws \InvalidArgumentException When route can't be parsed
*/
public function load($class, string $type = null)
public function load(mixed $class, string $type = null): RouteCollection
{
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
@@ -154,10 +150,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
return;
}
$name = $annot->getName();
if (null === $name) {
$name = $this->getDefaultRouteName($class, $method);
}
$name = $annot->getName() ?? $this->getDefaultRouteName($class, $method);
$name = $globals['name'].$name;
$requirements = $annot->getRequirements();
@@ -174,11 +167,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
$schemes = array_merge($globals['schemes'], $annot->getSchemes());
$methods = array_merge($globals['methods'], $annot->getMethods());
$host = $annot->getHost();
if (null === $host) {
$host = $globals['host'];
}
$host = $annot->getHost() ?? $globals['host'];
$condition = $annot->getCondition() ?? $globals['condition'];
$priority = $annot->getPriority() ?? $globals['priority'];
@@ -236,25 +225,16 @@ abstract class AnnotationClassLoader implements LoaderInterface
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type);
return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || \in_array($type, ['annotation', 'attribute'], true));
}
/**
* {@inheritdoc}
*/
public function setResolver(LoaderResolverInterface $resolver)
{
}
/**
* {@inheritdoc}
*/
public function getResolver()
public function getResolver(): LoaderResolverInterface
{
}
@@ -280,7 +260,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
$globals = $this->resetGlobals();
$annot = null;
if (\PHP_VERSION_ID >= 80000 && ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)) {
if ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
$annot = $attribute->newInstance();
}
if (!$annot && $this->reader) {
@@ -371,21 +351,19 @@ abstract class AnnotationClassLoader implements LoaderInterface
*/
private function getAnnotations(object $reflection): iterable
{
if (\PHP_VERSION_ID >= 80000) {
foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
yield $attribute->newInstance();
}
foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
yield $attribute->newInstance();
}
if (!$this->reader) {
return;
}
$anntotations = $reflection instanceof \ReflectionClass
$annotations = $reflection instanceof \ReflectionClass
? $this->reader->getClassAnnotations($reflection)
: $this->reader->getMethodAnnotations($reflection);
foreach ($anntotations as $annotation) {
foreach ($annotations as $annotation) {
if ($annotation instanceof $this->routeAnnotationClass) {
yield $annotation;
}

View File

@@ -23,16 +23,9 @@ use Symfony\Component\Routing\RouteCollection;
class AnnotationDirectoryLoader extends AnnotationFileLoader
{
/**
* Loads from annotations from a directory.
*
* @param string $path A directory path
* @param string|null $type The resource type
*
* @return RouteCollection
*
* @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed
*/
public function load($path, string $type = null)
public function load(mixed $path, string $type = null): ?RouteCollection
{
if (!is_dir($dir = $this->locator->locate($path))) {
return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection();
@@ -44,7 +37,7 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader
new \RecursiveCallbackFilterIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
function (\SplFileInfo $current) {
return '.' !== substr($current->getBasename(), 0, 1);
return !str_starts_with($current->getBasename(), '.');
}
),
\RecursiveIteratorIterator::LEAVES_ONLY
@@ -71,22 +64,23 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
if ('annotation' === $type) {
if (!\is_string($resource)) {
return false;
}
if (\in_array($type, ['annotation', 'attribute'], true)) {
return true;
}
if ($type || !\is_string($resource)) {
if ($type) {
return false;
}
try {
return is_dir($this->locator->locate($resource));
} catch (\Exception $e) {
} catch (\Exception) {
return false;
}
}

View File

@@ -40,14 +40,9 @@ class AnnotationFileLoader extends FileLoader
/**
* Loads from annotations from a file.
*
* @param string $file A PHP file path
* @param string|null $type The resource type
*
* @return RouteCollection|null
*
* @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed
*/
public function load($file, string $type = null)
public function load(mixed $file, string $type = null): ?RouteCollection
{
$path = $this->locator->locate($file);
@@ -67,20 +62,15 @@ class AnnotationFileLoader extends FileLoader
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'annotation' === $type);
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || \in_array($type, ['annotation', 'attribute'], true));
}
/**
* Returns the full class name for the first class in the file.
*
* @return string|false
*/
protected function findClass(string $file)
protected function findClass(string $file): string|false
{
$class = false;
$namespace = false;

View File

@@ -25,21 +25,13 @@ class ClosureLoader extends Loader
{
/**
* Loads a Closure.
*
* @param \Closure $closure A Closure
* @param string|null $type The resource type
*
* @return RouteCollection
*/
public function load($closure, string $type = null)
public function load(mixed $closure, string $type = null): RouteCollection
{
return $closure($this->env);
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return $resource instanceof \Closure && (!$type || 'closure' === $type);
}

View File

@@ -16,7 +16,7 @@ use Symfony\Component\Routing\Alias;
class AliasConfigurator
{
private $alias;
private Alias $alias;
public function __construct(Alias $alias)
{
@@ -34,7 +34,7 @@ class AliasConfigurator
*
* @throws InvalidArgumentException when the message template is invalid
*/
public function deprecate(string $package, string $version, string $message): self
public function deprecate(string $package, string $version, string $message): static
{
$this->alias->setDeprecated($package, $version, $message);

View File

@@ -23,10 +23,10 @@ class CollectionConfigurator
use Traits\HostTrait;
use Traits\RouteTrait;
private $parent;
private $parentConfigurator;
private $parentPrefixes;
private $host;
private RouteCollection $parent;
private ?CollectionConfigurator $parentConfigurator;
private ?array $parentPrefixes;
private string|array|null $host = null;
public function __construct(RouteCollection $parent, string $name, self $parentConfigurator = null, array $parentPrefixes = null)
{
@@ -38,10 +38,7 @@ class CollectionConfigurator
$this->parentPrefixes = $parentPrefixes;
}
/**
* @return array
*/
public function __sleep()
public function __sleep(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
@@ -78,7 +75,7 @@ class CollectionConfigurator
*
* @return $this
*/
final public function prefix($prefix): self
final public function prefix(string|array $prefix): static
{
if (\is_array($prefix)) {
if (null === $this->parentPrefixes) {
@@ -111,7 +108,7 @@ class CollectionConfigurator
*
* @return $this
*/
final public function host($host): self
final public function host(string|array $host): static
{
$this->host = $host;

View File

@@ -22,7 +22,7 @@ class ImportConfigurator
use Traits\PrefixTrait;
use Traits\RouteTrait;
private $parent;
private RouteCollection $parent;
public function __construct(RouteCollection $parent, RouteCollection $route)
{
@@ -30,10 +30,7 @@ class ImportConfigurator
$this->route = $route;
}
/**
* @return array
*/
public function __sleep()
public function __sleep(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
@@ -55,7 +52,7 @@ class ImportConfigurator
*
* @return $this
*/
final public function prefix($prefix, bool $trailingSlashOnRoot = true): self
final public function prefix(string|array $prefix, bool $trailingSlashOnRoot = true): static
{
$this->addPrefix($this->route, $prefix, $trailingSlashOnRoot);
@@ -67,7 +64,7 @@ class ImportConfigurator
*
* @return $this
*/
final public function namePrefix(string $namePrefix): self
final public function namePrefix(string $namePrefix): static
{
$this->route->addNamePrefix($namePrefix);
@@ -81,7 +78,7 @@ class ImportConfigurator
*
* @return $this
*/
final public function host($host): self
final public function host(string|array $host): static
{
$this->addHost($this->route, $host);

View File

@@ -40,7 +40,7 @@ class RouteConfigurator
*
* @return $this
*/
final public function host($host): self
final public function host(string|array $host): static
{
$this->addHost($this->route, $host);

View File

@@ -21,10 +21,10 @@ class RoutingConfigurator
{
use Traits\AddTrait;
private $loader;
private $path;
private $file;
private $env;
private PhpFileLoader $loader;
private string $path;
private string $file;
private ?string $env;
public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, string $env = null)
{
@@ -38,7 +38,7 @@ class RoutingConfigurator
/**
* @param string|string[]|null $exclude Glob patterns to exclude from the import
*/
final public function import($resource, string $type = null, bool $ignoreErrors = false, $exclude = null): ImportConfigurator
final public function import(string|array $resource, string $type = null, bool $ignoreErrors = false, string|array $exclude = null): ImportConfigurator
{
$this->loader->setCurrentDir(\dirname($this->path));
@@ -68,10 +68,7 @@ class RoutingConfigurator
return $this->env;
}
/**
* @return static
*/
final public function withPath(string $path): self
final public function withPath(string $path): static
{
$clone = clone $this;
$clone->path = $clone->file = $path;

View File

@@ -35,7 +35,7 @@ trait AddTrait
*
* @param string|array $path the path, or the localized paths of the route
*/
public function add(string $name, $path): RouteConfigurator
public function add(string $name, string|array $path): RouteConfigurator
{
$parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null);
$route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes);
@@ -53,7 +53,7 @@ trait AddTrait
*
* @param string|array $path the path, or the localized paths of the route
*/
public function __invoke(string $name, $path): RouteConfigurator
public function __invoke(string $name, string|array $path): RouteConfigurator
{
return $this->add($name, $path);
}

View File

@@ -18,7 +18,7 @@ use Symfony\Component\Routing\RouteCollection;
*/
trait HostTrait
{
final protected function addHost(RouteCollection $routes, $hosts)
final protected function addHost(RouteCollection $routes, string|array $hosts)
{
if (!$hosts || !\is_array($hosts)) {
$routes->setHost($hosts ?: '');

View File

@@ -27,7 +27,7 @@ trait LocalizedRouteTrait
*
* @param string|array $path the path, or the localized paths of the route
*/
final protected function createLocalizedRoute(RouteCollection $collection, string $name, $path, string $namePrefix = '', array $prefixes = null): RouteCollection
final protected function createLocalizedRoute(RouteCollection $collection, string $name, string|array $path, string $namePrefix = '', array $prefixes = null): RouteCollection
{
$paths = [];

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Routing\RouteCollection;
*/
trait PrefixTrait
{
final protected function addPrefix(RouteCollection $routes, $prefix, bool $trailingSlashOnRoot)
final protected function addPrefix(RouteCollection $routes, string|array $prefix, bool $trailingSlashOnRoot)
{
if (\is_array($prefix)) {
foreach ($prefix as $locale => $localePrefix) {

View File

@@ -26,7 +26,7 @@ trait RouteTrait
*
* @return $this
*/
final public function defaults(array $defaults): self
final public function defaults(array $defaults): static
{
$this->route->addDefaults($defaults);
@@ -38,7 +38,7 @@ trait RouteTrait
*
* @return $this
*/
final public function requirements(array $requirements): self
final public function requirements(array $requirements): static
{
$this->route->addRequirements($requirements);
@@ -50,7 +50,7 @@ trait RouteTrait
*
* @return $this
*/
final public function options(array $options): self
final public function options(array $options): static
{
$this->route->addOptions($options);
@@ -62,7 +62,7 @@ trait RouteTrait
*
* @return $this
*/
final public function utf8(bool $utf8 = true): self
final public function utf8(bool $utf8 = true): static
{
$this->route->addOptions(['utf8' => $utf8]);
@@ -74,7 +74,7 @@ trait RouteTrait
*
* @return $this
*/
final public function condition(string $condition): self
final public function condition(string $condition): static
{
$this->route->setCondition($condition);
@@ -86,7 +86,7 @@ trait RouteTrait
*
* @return $this
*/
final public function host(string $pattern): self
final public function host(string $pattern): static
{
$this->route->setHost($pattern);
@@ -101,7 +101,7 @@ trait RouteTrait
*
* @return $this
*/
final public function schemes(array $schemes): self
final public function schemes(array $schemes): static
{
$this->route->setSchemes($schemes);
@@ -116,7 +116,7 @@ trait RouteTrait
*
* @return $this
*/
final public function methods(array $methods): self
final public function methods(array $methods): static
{
$this->route->setMethods($methods);
@@ -130,7 +130,7 @@ trait RouteTrait
*
* @return $this
*/
final public function controller($controller): self
final public function controller(callable|string|array $controller): static
{
$this->route->addDefaults(['_controller' => $controller]);
@@ -142,7 +142,7 @@ trait RouteTrait
*
* @return $this
*/
final public function locale(string $locale): self
final public function locale(string $locale): static
{
$this->route->addDefaults(['_locale' => $locale]);
@@ -154,7 +154,7 @@ trait RouteTrait
*
* @return $this
*/
final public function format(string $format): self
final public function format(string $format): static
{
$this->route->addDefaults(['_format' => $format]);
@@ -166,7 +166,7 @@ trait RouteTrait
*
* @return $this
*/
final public function stateless(bool $stateless = true): self
final public function stateless(bool $stateless = true): static
{
$this->route->addDefaults(['_stateless' => $stateless]);

View File

@@ -20,7 +20,7 @@ use Psr\Container\ContainerInterface;
*/
class ContainerLoader extends ObjectLoader
{
private $container;
private ContainerInterface $container;
public function __construct(ContainerInterface $container, string $env = null)
{
@@ -28,18 +28,12 @@ class ContainerLoader extends ObjectLoader
parent::__construct($env);
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return 'service' === $type && \is_string($resource);
}
/**
* {@inheritdoc}
*/
protected function getObject(string $id)
protected function getObject(string $id): object
{
return $this->container->get($id);
}

View File

@@ -17,10 +17,7 @@ use Symfony\Component\Routing\RouteCollection;
class DirectoryLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($file, string $type = null)
public function load(mixed $file, string $type = null): mixed
{
$path = $this->locator->locate($file);
@@ -46,10 +43,7 @@ class DirectoryLoader extends FileLoader
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
// only when type is forced to directory, not to conflict with AnnotationLoader

View File

@@ -21,10 +21,7 @@ use Symfony\Component\Routing\RouteCollection;
*/
class GlobFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
public function load(mixed $resource, string $type = null): mixed
{
$collection = new RouteCollection();
@@ -37,10 +34,7 @@ class GlobFileLoader extends FileLoader
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return 'glob' === $type;
}

View File

@@ -27,20 +27,13 @@ abstract class ObjectLoader extends Loader
*
* For example, if your application uses a service container,
* the $id may be a service id.
*
* @return object
*/
abstract protected function getObject(string $id);
abstract protected function getObject(string $id): object;
/**
* Calls the object method that will load the routes.
*
* @param string $resource object_id::method
* @param string|null $type The resource type
*
* @return RouteCollection
*/
public function load($resource, string $type = null)
public function load(mixed $resource, string $type = null): RouteCollection
{
if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) {
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object'));

View File

@@ -29,13 +29,8 @@ class PhpFileLoader extends FileLoader
{
/**
* Loads a PHP file.
*
* @param string $file A PHP file path
* @param string|null $type The resource type
*
* @return RouteCollection
*/
public function load($file, string $type = null)
public function load(mixed $file, string $type = null): RouteCollection
{
$path = $this->locator->locate($file);
$this->setCurrentDir(\dirname($path));
@@ -59,10 +54,7 @@ class PhpFileLoader extends FileLoader
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type);
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Loader;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\DirectoryAwareLoaderInterface;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Routing\RouteCollection;
/**
* A loader that discovers controller classes in a directory that follows PSR-4.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
final class Psr4DirectoryLoader extends Loader implements DirectoryAwareLoaderInterface
{
private ?string $currentDirectory = null;
public function __construct(
private readonly FileLocatorInterface $locator,
) {
// PSR-4 directory loader has no env-aware logic, so we drop the $env constructor parameter.
parent::__construct();
}
/**
* @param array{path: string, namespace: string} $resource
*/
public function load(mixed $resource, string $type = null): ?RouteCollection
{
$path = $this->locator->locate($resource['path'], $this->currentDirectory);
if (!is_dir($path)) {
return new RouteCollection();
}
return $this->loadFromDirectory($path, trim($resource['namespace'], '\\'));
}
public function supports(mixed $resource, string $type = null): bool
{
return ('attribute' === $type || 'annotation' === $type) && \is_array($resource) && isset($resource['path'], $resource['namespace']);
}
public function forDirectory(string $currentDirectory): static
{
$loader = clone $this;
$loader->currentDirectory = $currentDirectory;
return $loader;
}
private function loadFromDirectory(string $directory, string $psr4Prefix): RouteCollection
{
$collection = new RouteCollection();
$collection->addResource(new DirectoryResource($directory, '/\.php$/'));
$files = iterator_to_array(new \RecursiveIteratorIterator(
new \RecursiveCallbackFilterIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
function (\SplFileInfo $current) {
return !str_starts_with($current->getBasename(), '.');
}
),
\RecursiveIteratorIterator::SELF_FIRST
));
usort($files, function (\SplFileInfo $a, \SplFileInfo $b) {
return (string) $a > (string) $b ? 1 : -1;
});
/** @var \SplFileInfo $file */
foreach ($files as $file) {
if ($file->isDir()) {
$collection->addCollection($this->loadFromDirectory($file->getPathname(), $psr4Prefix.'\\'.$file->getFilename()));
continue;
}
if ('php' !== $file->getExtension() || !class_exists($className = $psr4Prefix.'\\'.$file->getBasename('.php')) || (new \ReflectionClass($className))->isAbstract()) {
continue;
}
$collection->addCollection($this->import($className, 'attribute'));
}
return $collection;
}
}

View File

@@ -35,17 +35,10 @@ class XmlFileLoader extends FileLoader
public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
/**
* Loads an XML file.
*
* @param string $file An XML file path
* @param string|null $type The resource type
*
* @return RouteCollection
*
* @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be
* parsed because it does not validate against the scheme
*/
public function load($file, string $type = null)
public function load(mixed $file, string $type = null): RouteCollection
{
$path = $this->locator->locate($file);
@@ -99,10 +92,7 @@ class XmlFileLoader extends FileLoader
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
}
@@ -161,8 +151,17 @@ class XmlFileLoader extends FileLoader
*/
protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file)
{
if ('' === $resource = $node->getAttribute('resource')) {
throw new \InvalidArgumentException(sprintf('The <import> element in file "%s" must have a "resource" attribute.', $path));
/** @var \DOMElement $resourceElement */
if (!($resource = $node->getAttribute('resource') ?: null) && $resourceElement = $node->getElementsByTagName('resource')[0] ?? null) {
$resource = [];
/** @var \DOMAttr $attribute */
foreach ($resourceElement->attributes as $attribute) {
$resource[$attribute->name] = $attribute->value;
}
}
if (!$resource) {
throw new \InvalidArgumentException(sprintf('The <import> element in file "%s" must have a "resource" attribute or element.', $path));
}
$type = $node->getAttribute('type');
@@ -229,13 +228,11 @@ class XmlFileLoader extends FileLoader
}
/**
* @return \DOMDocument
*
* @throws \InvalidArgumentException When loading of XML file fails because of syntax errors
* or when the XML structure is not as expected by the scheme -
* see validate()
*/
protected function loadFile(string $file)
protected function loadFile(string $file): \DOMDocument
{
return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH);
}
@@ -288,6 +285,8 @@ class XmlFileLoader extends FileLoader
case 'condition':
$condition = trim($n->textContent);
break;
case 'resource':
break;
default:
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path));
}
@@ -330,10 +329,8 @@ class XmlFileLoader extends FileLoader
/**
* Parses the "default" elements.
*
* @return array|bool|float|int|string|null
*/
private function parseDefaultsConfig(\DOMElement $element, string $path)
private function parseDefaultsConfig(\DOMElement $element, string $path): array|bool|float|int|string|null
{
if ($this->isElementValueNull($element)) {
return null;
@@ -363,11 +360,9 @@ class XmlFileLoader extends FileLoader
/**
* Recursively parses the value of a "default" element.
*
* @return array|bool|float|int|string|null
*
* @throws \InvalidArgumentException when the XML is invalid
*/
private function parseDefaultNode(\DOMElement $node, string $path)
private function parseDefaultNode(\DOMElement $node, string $path): array|bool|float|int|string|null
{
if ($this->isElementValueNull($node)) {
return null;

View File

@@ -36,19 +36,12 @@ class YamlFileLoader extends FileLoader
private const AVAILABLE_KEYS = [
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless',
];
private $yamlParser;
private YamlParser $yamlParser;
/**
* Loads a Yaml file.
*
* @param string $file A Yaml file path
* @param string|null $type The resource type
*
* @return RouteCollection
*
* @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid
*/
public function load($file, string $type = null)
public function load(mixed $file, string $type = null): RouteCollection
{
$path = $this->locator->locate($file);
@@ -60,9 +53,7 @@ class YamlFileLoader extends FileLoader
throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path));
}
if (null === $this->yamlParser) {
$this->yamlParser = new YamlParser();
}
$this->yamlParser ??= new YamlParser();
try {
$parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
@@ -84,7 +75,7 @@ class YamlFileLoader extends FileLoader
}
foreach ($parsedConfig as $name => $config) {
if (0 === strpos($name, 'when@')) {
if (str_starts_with($name, 'when@')) {
if (!$this->env || 'when@'.$this->env !== $name) {
continue;
}
@@ -114,10 +105,7 @@ class YamlFileLoader extends FileLoader
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
public function supports(mixed $resource, string $type = null): bool
{
return \is_string($resource) && \in_array(pathinfo($resource, \PATHINFO_EXTENSION), ['yml', 'yaml'], true) && (!$type || 'yaml' === $type);
}
@@ -250,16 +238,10 @@ class YamlFileLoader extends FileLoader
}
/**
* Validates the route configuration.
*
* @param array $config A resource config
* @param string $name The config key
* @param string $path The loaded file path
*
* @throws \InvalidArgumentException If one of the provided config keys is not supported,
* something is missing or the combination is nonsense
*/
protected function validate($config, string $name, string $path)
protected function validate(mixed $config, string $name, string $path)
{
if (!\is_array($config)) {
throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path));

View File

@@ -76,8 +76,9 @@
<xsd:element name="prefix" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="host" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="resource" type="resource" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="resource" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="prefix" type="xsd:string" />
@@ -93,6 +94,12 @@
<xsd:attribute name="stateless" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="resource">
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="namespace" type="xsd:string" />
<xsd:anyAttribute />
</xsd:complexType>
<xsd:complexType name="default" mixed="true">
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="bool" type="xsd:boolean" />

View File

@@ -26,18 +26,15 @@ use Symfony\Component\Routing\RouteCollection;
*/
class CompiledUrlMatcherDumper extends MatcherDumper
{
private $expressionLanguage;
private $signalingException;
private ExpressionLanguage $expressionLanguage;
private ?\Exception $signalingException = null;
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = [];
private array $expressionLanguageProviders = [];
/**
* {@inheritdoc}
*/
public function dump(array $options = [])
public function dump(array $options = []): string
{
return <<<EOF
<?php
@@ -115,7 +112,7 @@ EOF;
}
$checkConditionCode = <<<EOF
static function (\$condition, \$context, \$request) { // \$checkCondition
static function (\$condition, \$context, \$request, \$params) { // \$checkCondition
switch (\$condition) {
{$this->indent(implode("\n", $conditions), 3)}
}
@@ -332,7 +329,7 @@ EOF;
if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) {
$regex = substr($regex, 0, -1);
}
$hasTrailingVar = (bool) preg_match('#\{\w+\}/?$#', $route->getPath());
$hasTrailingVar = (bool) preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
$tree->addRoute($regex, [$name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar]);
}
@@ -416,7 +413,7 @@ EOF;
/**
* Compiles a single Route to PHP code used to match it against the path info.
*/
private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions): array
private function compileRoute(Route $route, string $name, string|array|null $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions): array
{
$defaults = $route->getDefaults();
@@ -426,8 +423,8 @@ EOF;
}
if ($condition = $route->getCondition()) {
$condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request']);
$condition = $conditions[$condition] ?? $conditions[$condition] = (str_contains($condition, '$request') ? 1 : -1) * \count($conditions);
$condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request', 'params']);
$condition = $conditions[$condition] ??= (str_contains($condition, '$request') ? 1 : -1) * \count($conditions);
} else {
$condition = null;
}
@@ -445,7 +442,7 @@ EOF;
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
if (!isset($this->expressionLanguage)) {
if (!class_exists(ExpressionLanguage::class)) {
throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
@@ -463,7 +460,7 @@ EOF;
/**
* @internal
*/
public static function export($value): string
public static function export(mixed $value): string
{
if (null === $value) {
return 'null';

View File

@@ -26,10 +26,10 @@ use Symfony\Component\Routing\RequestContext;
*/
trait CompiledUrlMatcherTrait
{
private $matchHost = false;
private $staticRoutes = [];
private $regexpList = [];
private $dynamicRoutes = [];
private bool $matchHost = false;
private array $staticRoutes = [];
private array $regexpList = [];
private array $dynamicRoutes = [];
/**
* @var callable|null
@@ -92,10 +92,6 @@ trait CompiledUrlMatcherTrait
$supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface;
foreach ($this->staticRoutes[$trimmedPathinfo] ?? [] as [$ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition]) {
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
continue;
}
if ($requiredHost) {
if ('{' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) {
continue;
@@ -106,6 +102,10 @@ trait CompiledUrlMatcherTrait
}
}
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null, $ret)) {
continue;
}
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
return $allow = $allowSchemes = [];
@@ -132,13 +132,8 @@ trait CompiledUrlMatcherTrait
foreach ($this->regexpList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as [$ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition]) {
if (null !== $condition) {
if (0 === $condition) { // marks the last route in the regexp
continue 3;
}
if (!($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
continue;
}
if (0 === $condition) { // marks the last route in the regexp
continue 3;
}
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar;
@@ -151,6 +146,16 @@ trait CompiledUrlMatcherTrait
}
}
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null, $ret)) {
continue;
}
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
return $allow = $allowSchemes = [];
@@ -158,12 +163,6 @@ trait CompiledUrlMatcherTrait
continue;
}
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) {
$allowSchemes += $requiredSchemes;
continue;

View File

@@ -20,17 +20,14 @@ use Symfony\Component\Routing\RouteCollection;
*/
abstract class MatcherDumper implements MatcherDumperInterface
{
private $routes;
private RouteCollection $routes;
public function __construct(RouteCollection $routes)
{
$this->routes = $routes;
}
/**
* {@inheritdoc}
*/
public function getRoutes()
public function getRoutes(): RouteCollection
{
return $this->routes;
}

View File

@@ -23,15 +23,11 @@ interface MatcherDumperInterface
/**
* Dumps a set of routes to a string representation of executable code
* that can then be used to match a request against these routes.
*
* @return string
*/
public function dump(array $options = []);
public function dump(array $options = []): string;
/**
* Gets the routes to dump.
*
* @return RouteCollection
*/
public function getRoutes();
public function getRoutes(): RouteCollection;
}

View File

@@ -23,22 +23,22 @@ use Symfony\Component\Routing\RouteCollection;
*/
class StaticPrefixCollection
{
private $prefix;
private string $prefix;
/**
* @var string[]
*/
private $staticPrefixes = [];
private array $staticPrefixes = [];
/**
* @var string[]
*/
private $prefixes = [];
private array $prefixes = [];
/**
* @var array[]|self[]
*/
private $items = [];
private array $items = [];
public function __construct(string $prefix = '/')
{
@@ -60,10 +60,8 @@ class StaticPrefixCollection
/**
* Adds a route to a group.
*
* @param array|self $route
*/
public function addRoute(string $prefix, $route)
public function addRoute(string $prefix, array|StaticPrefixCollection $route)
{
[$prefix, $staticPrefix] = $this->getCommonPrefix($prefix, $prefix);
@@ -154,7 +152,7 @@ class StaticPrefixCollection
try {
for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) {
if ('(' === $prefix[$i]) {
$staticLength = $staticLength ?? $i;
$staticLength ??= $i;
for ($j = 1 + $i, $n = 1; $j < $end && 0 < $n; ++$j) {
if ($prefix[$j] !== $anotherPrefix[$j]) {
break 2;

View File

@@ -22,17 +22,14 @@ use Symfony\Contracts\Service\ServiceProviderInterface;
*/
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
{
private $functions;
private ServiceProviderInterface $functions;
public function __construct(ServiceProviderInterface $functions)
{
$this->functions = $functions;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
public function getFunctions(): array
{
$functions = [];

View File

@@ -19,10 +19,7 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException;
*/
abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
{
/**
* {@inheritdoc}
*/
public function match(string $pathinfo)
public function match(string $pathinfo): array
{
try {
return parent::match($pathinfo);
@@ -39,7 +36,7 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
$ret = parent::match($pathinfo);
return $this->redirect($pathinfo, $ret['_route'] ?? null, $this->context->getScheme()) + $ret;
} catch (ExceptionInterface $e2) {
} catch (ExceptionInterface) {
throw $e;
} finally {
$this->context->setScheme($scheme);
@@ -52,7 +49,7 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
$ret = parent::match($pathinfo);
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;
} catch (ExceptionInterface $e2) {
} catch (ExceptionInterface) {
if ($this->allowSchemes) {
goto redirect_scheme;
}

View File

@@ -24,8 +24,6 @@ interface RedirectableUrlMatcherInterface
* @param string $path The path info to redirect to
* @param string $route The route name that matched
* @param string|null $scheme The URL scheme (null to keep the current one)
*
* @return array
*/
public function redirect(string $path, string $route, string $scheme = null);
public function redirect(string $path, string $route, string $scheme = null): array;
}

View File

@@ -29,11 +29,9 @@ interface RequestMatcherInterface
* If the matcher cannot find information, it must throw one of the exceptions documented
* below.
*
* @return array
*
* @throws NoConfigurationException If no routing configuration could be found
* @throws ResourceNotFoundException If no matching resource could be found
* @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed
*/
public function matchRequest(Request $request);
public function matchRequest(Request $request): array;
}

View File

@@ -35,7 +35,7 @@ class TraceableUrlMatcher extends UrlMatcher
try {
$this->match($pathinfo);
} catch (ExceptionInterface $e) {
} catch (ExceptionInterface) {
}
return $this->traces;
@@ -50,7 +50,7 @@ class TraceableUrlMatcher extends UrlMatcher
return $traces;
}
protected function matchCollection(string $pathinfo, RouteCollection $routes)
protected function matchCollection(string $pathinfo, RouteCollection $routes): array
{
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
@@ -99,7 +99,7 @@ class TraceableUrlMatcher extends UrlMatcher
continue;
}
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
if ($hasTrailingSlash) {
@@ -115,7 +115,9 @@ class TraceableUrlMatcher extends UrlMatcher
continue;
}
$status = $this->handleRouteRequirements($pathinfo, $name, $route);
$attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
$status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes);
if (self::REQUIREMENT_MISMATCH === $status[0]) {
$this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route);
@@ -146,7 +148,7 @@ class TraceableUrlMatcher extends UrlMatcher
$this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route);
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));
return array_replace($attributes, $status[1] ?? []);
}
return [];
@@ -158,7 +160,7 @@ class TraceableUrlMatcher extends UrlMatcher
'log' => $log,
'name' => $name,
'level' => $level,
'path' => null !== $route ? $route->getPath() : null,
'path' => $route?->getPath(),
];
}
}

View File

@@ -45,7 +45,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*
* @internal
*/
protected $allowSchemes = [];
protected array $allowSchemes = [];
protected $routes;
protected $request;
@@ -62,26 +62,17 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
$this->context = $context;
}
/**
* {@inheritdoc}
*/
public function setContext(RequestContext $context)
{
$this->context = $context;
}
/**
* {@inheritdoc}
*/
public function getContext()
public function getContext(): RequestContext
{
return $this->context;
}
/**
* {@inheritdoc}
*/
public function match(string $pathinfo)
public function match(string $pathinfo): array
{
$this->allow = $this->allowSchemes = [];
@@ -96,10 +87,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
throw 0 < \count($this->allow) ? new MethodNotAllowedException(array_unique($this->allow)) : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
}
/**
* {@inheritdoc}
*/
public function matchRequest(Request $request)
public function matchRequest(Request $request): array
{
$this->request = $request;
@@ -120,13 +108,11 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*
* @param string $pathinfo The path info to be parsed
*
* @return array
*
* @throws NoConfigurationException If no routing configuration could be found
* @throws ResourceNotFoundException If the resource could not be found
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed
*/
protected function matchCollection(string $pathinfo, RouteCollection $routes)
protected function matchCollection(string $pathinfo, RouteCollection $routes): array
{
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
@@ -154,7 +140,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
if ($hasTrailingSlash) {
@@ -169,7 +155,9 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
$status = $this->handleRouteRequirements($pathinfo, $name, $route);
$attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
$status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes);
if (self::REQUIREMENT_MISMATCH === $status[0]) {
continue;
@@ -192,7 +180,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));
return array_replace($attributes, $status[1] ?? []);
}
return [];
@@ -204,10 +192,8 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
* As this method requires the Route object, it is not available
* in matchers that do not have access to the matched Route instance
* (like the PHP and Apache matcher dumpers).
*
* @return array
*/
protected function getAttributes(Route $route, string $name, array $attributes)
protected function getAttributes(Route $route, string $name, array $attributes): array
{
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
@@ -224,10 +210,25 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*
* @return array The first element represents the status, the second contains additional information
*/
protected function handleRouteRequirements(string $pathinfo, string $name, Route $route)
protected function handleRouteRequirements(string $pathinfo, string $name, Route $route/* , array $routeParameters */): array
{
if (\func_num_args() < 4) {
trigger_deprecation('symfony/routing', '6.1', 'The "%s()" method will have a new "array $routeParameters" argument in version 7.0, not defining it is deprecated.', __METHOD__);
$routeParameters = [];
} else {
$routeParameters = func_get_arg(3);
if (!\is_array($routeParameters)) {
throw new \TypeError(sprintf('"%s": Argument $routeParameters is expected to be an array, got "%s".', __METHOD__, get_debug_type($routeParameters)));
}
}
// expression condition
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) {
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), [
'context' => $this->context,
'request' => $this->request ?: $this->createRequest($pathinfo),
'params' => $routeParameters,
])) {
return [self::REQUIREMENT_MISMATCH, null];
}
@@ -236,10 +237,8 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
/**
* Get merged default parameters.
*
* @return array
*/
protected function mergeDefaults(array $params, array $defaults)
protected function mergeDefaults(array $params, array $defaults): array
{
foreach ($params as $key => $value) {
if (!\is_int($key) && null !== $value) {

View File

@@ -31,11 +31,9 @@ interface UrlMatcherInterface extends RequestContextAwareInterface
*
* @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded)
*
* @return array
*
* @throws NoConfigurationException If no routing configuration could be found
* @throws ResourceNotFoundException If the resource could not be found
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed
*/
public function match(string $pathinfo);
public function match(string $pathinfo): array;
}

View File

@@ -23,15 +23,15 @@ use Symfony\Component\HttpFoundation\Request;
*/
class RequestContext
{
private $baseUrl;
private $pathInfo;
private $method;
private $host;
private $scheme;
private $httpPort;
private $httpsPort;
private $queryString;
private $parameters = [];
private string $baseUrl;
private string $pathInfo;
private string $method;
private string $host;
private string $scheme;
private int $httpPort;
private int $httpsPort;
private string $queryString;
private array $parameters = [];
public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '')
{
@@ -67,7 +67,7 @@ class RequestContext
*
* @return $this
*/
public function fromRequest(Request $request)
public function fromRequest(Request $request): static
{
$this->setBaseUrl($request->getBaseUrl());
$this->setPathInfo($request->getPathInfo());
@@ -83,10 +83,8 @@ class RequestContext
/**
* Gets the base URL.
*
* @return string
*/
public function getBaseUrl()
public function getBaseUrl(): string
{
return $this->baseUrl;
}
@@ -96,7 +94,7 @@ class RequestContext
*
* @return $this
*/
public function setBaseUrl(string $baseUrl)
public function setBaseUrl(string $baseUrl): static
{
$this->baseUrl = rtrim($baseUrl, '/');
@@ -105,10 +103,8 @@ class RequestContext
/**
* Gets the path info.
*
* @return string
*/
public function getPathInfo()
public function getPathInfo(): string
{
return $this->pathInfo;
}
@@ -118,7 +114,7 @@ class RequestContext
*
* @return $this
*/
public function setPathInfo(string $pathInfo)
public function setPathInfo(string $pathInfo): static
{
$this->pathInfo = $pathInfo;
@@ -129,10 +125,8 @@ class RequestContext
* Gets the HTTP method.
*
* The method is always an uppercased string.
*
* @return string
*/
public function getMethod()
public function getMethod(): string
{
return $this->method;
}
@@ -142,7 +136,7 @@ class RequestContext
*
* @return $this
*/
public function setMethod(string $method)
public function setMethod(string $method): static
{
$this->method = strtoupper($method);
@@ -153,10 +147,8 @@ class RequestContext
* Gets the HTTP host.
*
* The host is always lowercased because it must be treated case-insensitive.
*
* @return string
*/
public function getHost()
public function getHost(): string
{
return $this->host;
}
@@ -166,7 +158,7 @@ class RequestContext
*
* @return $this
*/
public function setHost(string $host)
public function setHost(string $host): static
{
$this->host = strtolower($host);
@@ -175,10 +167,8 @@ class RequestContext
/**
* Gets the HTTP scheme.
*
* @return string
*/
public function getScheme()
public function getScheme(): string
{
return $this->scheme;
}
@@ -188,7 +178,7 @@ class RequestContext
*
* @return $this
*/
public function setScheme(string $scheme)
public function setScheme(string $scheme): static
{
$this->scheme = strtolower($scheme);
@@ -197,10 +187,8 @@ class RequestContext
/**
* Gets the HTTP port.
*
* @return int
*/
public function getHttpPort()
public function getHttpPort(): int
{
return $this->httpPort;
}
@@ -210,7 +198,7 @@ class RequestContext
*
* @return $this
*/
public function setHttpPort(int $httpPort)
public function setHttpPort(int $httpPort): static
{
$this->httpPort = $httpPort;
@@ -219,10 +207,8 @@ class RequestContext
/**
* Gets the HTTPS port.
*
* @return int
*/
public function getHttpsPort()
public function getHttpsPort(): int
{
return $this->httpsPort;
}
@@ -232,7 +218,7 @@ class RequestContext
*
* @return $this
*/
public function setHttpsPort(int $httpsPort)
public function setHttpsPort(int $httpsPort): static
{
$this->httpsPort = $httpsPort;
@@ -241,10 +227,8 @@ class RequestContext
/**
* Gets the query string without the "?".
*
* @return string
*/
public function getQueryString()
public function getQueryString(): string
{
return $this->queryString;
}
@@ -254,7 +238,7 @@ class RequestContext
*
* @return $this
*/
public function setQueryString(?string $queryString)
public function setQueryString(?string $queryString): static
{
// string cast to be fault-tolerant, accepting null
$this->queryString = (string) $queryString;
@@ -264,10 +248,8 @@ class RequestContext
/**
* Returns the parameters.
*
* @return array
*/
public function getParameters()
public function getParameters(): array
{
return $this->parameters;
}
@@ -279,7 +261,7 @@ class RequestContext
*
* @return $this
*/
public function setParameters(array $parameters)
public function setParameters(array $parameters): static
{
$this->parameters = $parameters;
@@ -288,20 +270,16 @@ class RequestContext
/**
* Gets a parameter value.
*
* @return mixed
*/
public function getParameter(string $name)
public function getParameter(string $name): mixed
{
return $this->parameters[$name] ?? null;
}
/**
* Checks if a parameter value is set for the given parameter.
*
* @return bool
*/
public function hasParameter(string $name)
public function hasParameter(string $name): bool
{
return \array_key_exists($name, $this->parameters);
}
@@ -309,11 +287,9 @@ class RequestContext
/**
* Sets a parameter value.
*
* @param mixed $parameter The parameter value
*
* @return $this
*/
public function setParameter(string $name, $parameter)
public function setParameter(string $name, mixed $parameter): static
{
$this->parameters[$name] = $parameter;

View File

@@ -20,8 +20,6 @@ interface RequestContextAwareInterface
/**
* Gets the request context.
*
* @return RequestContext
*/
public function getContext();
public function getContext(): RequestContext;
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Requirement;
use Symfony\Component\Routing\Exception\InvalidArgumentException;
final class EnumRequirement implements \Stringable
{
private string $requirement;
/**
* @template T of \BackedEnum
*
* @param class-string<T>|list<T> $cases
*/
public function __construct(string|array $cases = [])
{
if (\is_string($cases)) {
if (!is_subclass_of($cases, \BackedEnum::class, true)) {
throw new InvalidArgumentException(sprintf('"%s" is not a "BackedEnum" class.', $cases));
}
$cases = $cases::cases();
} else {
$class = null;
foreach ($cases as $case) {
if (!$case instanceof \BackedEnum) {
throw new InvalidArgumentException(sprintf('Case must be a "BackedEnum" instance, "%s" given.', get_debug_type($case)));
}
$class ??= $case::class;
if (!$case instanceof $class) {
throw new InvalidArgumentException(sprintf('"%s::%s" is not a case of "%s".', get_debug_type($case), $case->name, $class));
}
}
}
$this->requirement = implode('|', array_map(static fn ($e) => preg_quote($e->value), $cases));
}
public function __toString(): string
{
return $this->requirement;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Requirement;
/*
* A collection of universal regular-expression constants to use as route parameter requirements.
*/
enum Requirement
{
public const ASCII_SLUG = '[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*'; // symfony/string AsciiSlugger default implementation
public const CATCH_ALL = '.+';
public const DATE_YMD = '[0-9]{4}-(?:0[1-9]|1[012])-(?:0[1-9]|[12][0-9]|(?<!02-)3[01])'; // YYYY-MM-DD
public const DIGITS = '[0-9]+';
public const POSITIVE_INT = '[1-9][0-9]*';
public const UID_BASE32 = '[0-9A-HJKMNP-TV-Z]{26}';
public const UID_BASE58 = '[1-9A-HJ-NP-Za-km-z]{22}';
public const UID_RFC4122 = '[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}';
public const ULID = '[0-7][0-9A-HJKMNP-TV-Z]{25}';
public const UUID = '[0-9a-f]{8}-[0-9a-f]{4}-[13-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V1 = '[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V3 = '[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V4 = '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V5 = '[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V6 = '[0-9a-f]{8}-[0-9a-f]{4}-6[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V7 = '[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
public const UUID_V8 = '[0-9a-f]{8}-[0-9a-f]{4}-8[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
}

View File

@@ -19,19 +19,15 @@ namespace Symfony\Component\Routing;
*/
class Route implements \Serializable
{
private $path = '/';
private $host = '';
private $schemes = [];
private $methods = [];
private $defaults = [];
private $requirements = [];
private $options = [];
private $condition = '';
/**
* @var CompiledRoute|null
*/
private $compiled;
private string $path = '/';
private string $host = '';
private array $schemes = [];
private array $methods = [];
private array $defaults = [];
private array $requirements = [];
private array $options = [];
private string $condition = '';
private ?CompiledRoute $compiled = null;
/**
* Constructor.
@@ -41,16 +37,16 @@ class Route implements \Serializable
* * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
* * utf8: Whether UTF-8 matching is enforced ot not
*
* @param string $path The path pattern to match
* @param array $defaults An array of default parameter values
* @param array $requirements An array of requirements for parameters (regexes)
* @param array $options An array of options
* @param string|null $host The host pattern to match
* @param string|string[] $schemes A required URI scheme or an array of restricted schemes
* @param string|string[] $methods A required HTTP method or an array of restricted methods
* @param string|null $condition A condition that should evaluate to true for the route to match
* @param string $path The path pattern to match
* @param array $defaults An array of default parameter values
* @param array<string|\Stringable> $requirements An array of requirements for parameters (regexes)
* @param array $options An array of options
* @param string|null $host The host pattern to match
* @param string|string[] $schemes A required URI scheme or an array of restricted schemes
* @param string|string[] $methods A required HTTP method or an array of restricted methods
* @param string|null $condition A condition that should evaluate to true for the route to match
*/
public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', $schemes = [], $methods = [], ?string $condition = '')
public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', string|array $schemes = [], string|array $methods = [], ?string $condition = '')
{
$this->setPath($path);
$this->addDefaults($defaults);
@@ -82,7 +78,7 @@ class Route implements \Serializable
*/
final public function serialize(): string
{
return serialize($this->__serialize());
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __unserialize(array $data): void
@@ -106,15 +102,12 @@ class Route implements \Serializable
/**
* @internal
*/
final public function unserialize($serialized)
final public function unserialize(string $serialized)
{
$this->__unserialize(unserialize($serialized));
}
/**
* @return string
*/
public function getPath()
public function getPath(): string
{
return $this->path;
}
@@ -122,7 +115,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setPath(string $pattern)
public function setPath(string $pattern): static
{
$pattern = $this->extractInlineDefaultsAndRequirements($pattern);
@@ -134,10 +127,7 @@ class Route implements \Serializable
return $this;
}
/**
* @return string
*/
public function getHost()
public function getHost(): string
{
return $this->host;
}
@@ -145,7 +135,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setHost(?string $pattern)
public function setHost(?string $pattern): static
{
$this->host = $this->extractInlineDefaultsAndRequirements((string) $pattern);
$this->compiled = null;
@@ -159,7 +149,7 @@ class Route implements \Serializable
*
* @return string[]
*/
public function getSchemes()
public function getSchemes(): array
{
return $this->schemes;
}
@@ -172,7 +162,7 @@ class Route implements \Serializable
*
* @return $this
*/
public function setSchemes($schemes)
public function setSchemes(string|array $schemes): static
{
$this->schemes = array_map('strtolower', (array) $schemes);
$this->compiled = null;
@@ -182,10 +172,8 @@ class Route implements \Serializable
/**
* Checks if a scheme requirement has been set.
*
* @return bool
*/
public function hasScheme(string $scheme)
public function hasScheme(string $scheme): bool
{
return \in_array(strtolower($scheme), $this->schemes, true);
}
@@ -196,7 +184,7 @@ class Route implements \Serializable
*
* @return string[]
*/
public function getMethods()
public function getMethods(): array
{
return $this->methods;
}
@@ -209,7 +197,7 @@ class Route implements \Serializable
*
* @return $this
*/
public function setMethods($methods)
public function setMethods(string|array $methods): static
{
$this->methods = array_map('strtoupper', (array) $methods);
$this->compiled = null;
@@ -217,10 +205,7 @@ class Route implements \Serializable
return $this;
}
/**
* @return array
*/
public function getOptions()
public function getOptions(): array
{
return $this->options;
}
@@ -228,10 +213,10 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setOptions(array $options)
public function setOptions(array $options): static
{
$this->options = [
'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
'compiler_class' => RouteCompiler::class,
];
return $this->addOptions($options);
@@ -240,7 +225,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function addOptions(array $options)
public function addOptions(array $options): static
{
foreach ($options as $name => $option) {
$this->options[$name] = $option;
@@ -253,11 +238,9 @@ class Route implements \Serializable
/**
* Sets an option value.
*
* @param mixed $value The option value
*
* @return $this
*/
public function setOption(string $name, $value)
public function setOption(string $name, mixed $value): static
{
$this->options[$name] = $value;
$this->compiled = null;
@@ -267,26 +250,18 @@ class Route implements \Serializable
/**
* Returns the option value or null when not found.
*
* @return mixed
*/
public function getOption(string $name)
public function getOption(string $name): mixed
{
return $this->options[$name] ?? null;
}
/**
* @return bool
*/
public function hasOption(string $name)
public function hasOption(string $name): bool
{
return \array_key_exists($name, $this->options);
}
/**
* @return array
*/
public function getDefaults()
public function getDefaults(): array
{
return $this->defaults;
}
@@ -294,7 +269,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setDefaults(array $defaults)
public function setDefaults(array $defaults): static
{
$this->defaults = [];
@@ -304,7 +279,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function addDefaults(array $defaults)
public function addDefaults(array $defaults): static
{
if (isset($defaults['_locale']) && $this->isLocalized()) {
unset($defaults['_locale']);
@@ -318,30 +293,20 @@ class Route implements \Serializable
return $this;
}
/**
* @return mixed
*/
public function getDefault(string $name)
public function getDefault(string $name): mixed
{
return $this->defaults[$name] ?? null;
}
/**
* @return bool
*/
public function hasDefault(string $name)
public function hasDefault(string $name): bool
{
return \array_key_exists($name, $this->defaults);
}
/**
* Sets a default value.
*
* @param mixed $default The default value
*
* @return $this
*/
public function setDefault(string $name, $default)
public function setDefault(string $name, mixed $default): static
{
if ('_locale' === $name && $this->isLocalized()) {
return $this;
@@ -353,10 +318,7 @@ class Route implements \Serializable
return $this;
}
/**
* @return array
*/
public function getRequirements()
public function getRequirements(): array
{
return $this->requirements;
}
@@ -364,7 +326,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setRequirements(array $requirements)
public function setRequirements(array $requirements): static
{
$this->requirements = [];
@@ -374,7 +336,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function addRequirements(array $requirements)
public function addRequirements(array $requirements): static
{
if (isset($requirements['_locale']) && $this->isLocalized()) {
unset($requirements['_locale']);
@@ -388,18 +350,12 @@ class Route implements \Serializable
return $this;
}
/**
* @return string|null
*/
public function getRequirement(string $key)
public function getRequirement(string $key): ?string
{
return $this->requirements[$key] ?? null;
}
/**
* @return bool
*/
public function hasRequirement(string $key)
public function hasRequirement(string $key): bool
{
return \array_key_exists($key, $this->requirements);
}
@@ -407,7 +363,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setRequirement(string $key, string $regex)
public function setRequirement(string $key, string $regex): static
{
if ('_locale' === $key && $this->isLocalized()) {
return $this;
@@ -419,10 +375,7 @@ class Route implements \Serializable
return $this;
}
/**
* @return string
*/
public function getCondition()
public function getCondition(): string
{
return $this->condition;
}
@@ -430,7 +383,7 @@ class Route implements \Serializable
/**
* @return $this
*/
public function setCondition(?string $condition)
public function setCondition(?string $condition): static
{
$this->condition = (string) $condition;
$this->compiled = null;
@@ -441,14 +394,12 @@ class Route implements \Serializable
/**
* Compiles the route.
*
* @return CompiledRoute
*
* @throws \LogicException If the Route cannot be compiled because the
* path or host pattern is invalid
*
* @see RouteCompiler which is responsible for the compilation process
*/
public function compile()
public function compile(): CompiledRoute
{
if (null !== $this->compiled) {
return $this->compiled;
@@ -465,7 +416,7 @@ class Route implements \Serializable
return $pattern;
}
return preg_replace_callback('#\{(!?)(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
return preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
if (isset($m[4][0])) {
$this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
}
@@ -482,7 +433,7 @@ class Route implements \Serializable
if ('' !== $regex) {
if ('^' === $regex[0]) {
$regex = substr($regex, 1);
} elseif (0 === strpos($regex, '\\A')) {
} elseif (str_starts_with($regex, '\\A')) {
$regex = substr($regex, 2);
}
}

View File

@@ -32,7 +32,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* @var array<string, Route>
*/
private $routes = [];
private array $routes = [];
/**
* @var array<string, Alias>
@@ -42,12 +42,12 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* @var array<string, ResourceInterface>
*/
private $resources = [];
private array $resources = [];
/**
* @var array<string, int>
*/
private $priorities = [];
private array $priorities = [];
public function __clone()
{
@@ -69,37 +69,26 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @return \ArrayIterator<string, Route>
*/
#[\ReturnTypeWillChange]
public function getIterator()
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->all());
}
/**
* Gets the number of Routes in this collection.
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
public function count(): int
{
return \count($this->routes);
}
/**
* @param int $priority
*/
public function add(string $name, Route $route/* , int $priority = 0 */)
public function add(string $name, Route $route, int $priority = 0)
{
if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) {
trigger_deprecation('symfony/routing', '5.1', 'The "%s()" method will have a new "int $priority = 0" argument in version 6.0, not defining it is deprecated.', __METHOD__);
}
unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]);
$this->routes[$name] = $route;
if ($priority = 3 <= \func_num_args() ? func_get_arg(2) : 0) {
if ($priority) {
$this->priorities[$name] = $priority;
}
}
@@ -109,7 +98,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @return array<string, Route>
*/
public function all()
public function all(): array
{
if ($this->priorities) {
$priorities = $this->priorities;
@@ -124,10 +113,8 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* Gets a route by name.
*
* @return Route|null
*/
public function get(string $name)
public function get(string $name): ?Route
{
$visited = [];
while (null !== $alias = $this->aliases[$name] ?? null) {
@@ -155,7 +142,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @param string|string[] $name The route name or an array of route names
*/
public function remove($name)
public function remove(string|array $name)
{
foreach ((array) $name as $n) {
unset($this->routes[$n], $this->priorities[$n], $this->aliases[$n]);
@@ -307,7 +294,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @param string|string[] $schemes The scheme or an array of schemes
*/
public function setSchemes($schemes)
public function setSchemes(string|array $schemes)
{
foreach ($this->routes as $route) {
$route->setSchemes($schemes);
@@ -319,7 +306,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @param string|string[] $methods The method or an array of methods
*/
public function setMethods($methods)
public function setMethods(string|array $methods)
{
foreach ($this->routes as $route) {
$route->setMethods($methods);
@@ -331,7 +318,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @return ResourceInterface[]
*/
public function getResources()
public function getResources(): array
{
return array_values($this->resources);
}

View File

@@ -1,364 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
trigger_deprecation('symfony/routing', '5.1', 'The "%s" class is deprecated, use "%s" instead.', RouteCollectionBuilder::class, RoutingConfigurator::class);
/**
* Helps add and import routes into a RouteCollection.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*
* @deprecated since Symfony 5.1, use RoutingConfigurator instead
*/
class RouteCollectionBuilder
{
/**
* @var Route[]|RouteCollectionBuilder[]
*/
private $routes = [];
private $loader;
private $defaults = [];
private $prefix;
private $host;
private $condition;
private $requirements = [];
private $options = [];
private $schemes;
private $methods;
private $resources = [];
public function __construct(LoaderInterface $loader = null)
{
$this->loader = $loader;
}
/**
* Import an external routing resource and returns the RouteCollectionBuilder.
*
* $routes->import('blog.yml', '/blog');
*
* @param mixed $resource
*
* @return self
*
* @throws LoaderLoadException
*/
public function import($resource, string $prefix = '/', string $type = null)
{
/** @var RouteCollection[] $collections */
$collections = $this->load($resource, $type);
// create a builder from the RouteCollection
$builder = $this->createBuilder();
foreach ($collections as $collection) {
if (null === $collection) {
continue;
}
foreach ($collection->all() as $name => $route) {
$builder->addRoute($route, $name);
}
foreach ($collection->getResources() as $resource) {
$builder->addResource($resource);
}
}
// mount into this builder
$this->mount($prefix, $builder);
return $builder;
}
/**
* Adds a route and returns it for future modification.
*
* @return Route
*/
public function add(string $path, string $controller, string $name = null)
{
$route = new Route($path);
$route->setDefault('_controller', $controller);
$this->addRoute($route, $name);
return $route;
}
/**
* Returns a RouteCollectionBuilder that can be configured and then added with mount().
*
* @return self
*/
public function createBuilder()
{
return new self($this->loader);
}
/**
* Add a RouteCollectionBuilder.
*/
public function mount(string $prefix, self $builder)
{
$builder->prefix = trim(trim($prefix), '/');
$this->routes[] = $builder;
}
/**
* Adds a Route object to the builder.
*
* @return $this
*/
public function addRoute(Route $route, string $name = null)
{
if (null === $name) {
// used as a flag to know which routes will need a name later
$name = '_unnamed_route_'.spl_object_hash($route);
}
$this->routes[$name] = $route;
return $this;
}
/**
* Sets the host on all embedded routes (unless already set).
*
* @return $this
*/
public function setHost(?string $pattern)
{
$this->host = $pattern;
return $this;
}
/**
* Sets a condition on all embedded routes (unless already set).
*
* @return $this
*/
public function setCondition(?string $condition)
{
$this->condition = $condition;
return $this;
}
/**
* Sets a default value that will be added to all embedded routes (unless that
* default value is already set).
*
* @param mixed $value
*
* @return $this
*/
public function setDefault(string $key, $value)
{
$this->defaults[$key] = $value;
return $this;
}
/**
* Sets a requirement that will be added to all embedded routes (unless that
* requirement is already set).
*
* @param mixed $regex
*
* @return $this
*/
public function setRequirement(string $key, $regex)
{
$this->requirements[$key] = $regex;
return $this;
}
/**
* Sets an option that will be added to all embedded routes (unless that
* option is already set).
*
* @param mixed $value
*
* @return $this
*/
public function setOption(string $key, $value)
{
$this->options[$key] = $value;
return $this;
}
/**
* Sets the schemes on all embedded routes (unless already set).
*
* @param array|string $schemes
*
* @return $this
*/
public function setSchemes($schemes)
{
$this->schemes = $schemes;
return $this;
}
/**
* Sets the methods on all embedded routes (unless already set).
*
* @param array|string $methods
*
* @return $this
*/
public function setMethods($methods)
{
$this->methods = $methods;
return $this;
}
/**
* Adds a resource for this collection.
*
* @return $this
*/
private function addResource(ResourceInterface $resource): self
{
$this->resources[] = $resource;
return $this;
}
/**
* Creates the final RouteCollection and returns it.
*
* @return RouteCollection
*/
public function build()
{
$routeCollection = new RouteCollection();
foreach ($this->routes as $name => $route) {
if ($route instanceof Route) {
$route->setDefaults(array_merge($this->defaults, $route->getDefaults()));
$route->setOptions(array_merge($this->options, $route->getOptions()));
foreach ($this->requirements as $key => $val) {
if (!$route->hasRequirement($key)) {
$route->setRequirement($key, $val);
}
}
if (null !== $this->prefix) {
$route->setPath('/'.$this->prefix.$route->getPath());
}
if (!$route->getHost()) {
$route->setHost($this->host);
}
if (!$route->getCondition()) {
$route->setCondition($this->condition);
}
if (!$route->getSchemes()) {
$route->setSchemes($this->schemes);
}
if (!$route->getMethods()) {
$route->setMethods($this->methods);
}
// auto-generate the route name if it's been marked
if ('_unnamed_route_' === substr($name, 0, 15)) {
$name = $this->generateRouteName($route);
}
$routeCollection->add($name, $route);
} else {
/* @var self $route */
$subCollection = $route->build();
if (null !== $this->prefix) {
$subCollection->addPrefix($this->prefix);
}
$routeCollection->addCollection($subCollection);
}
}
foreach ($this->resources as $resource) {
$routeCollection->addResource($resource);
}
return $routeCollection;
}
/**
* Generates a route name based on details of this route.
*/
private function generateRouteName(Route $route): string
{
$methods = implode('_', $route->getMethods()).'_';
$routeName = $methods.$route->getPath();
$routeName = str_replace(['/', ':', '|', '-'], '_', $routeName);
$routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
// Collapse consecutive underscores down into a single underscore.
$routeName = preg_replace('/_+/', '_', $routeName);
return $routeName;
}
/**
* Finds a loader able to load an imported resource and loads it.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return RouteCollection[]
*
* @throws LoaderLoadException If no loader is found
*/
private function load($resource, string $type = null): array
{
if (null === $this->loader) {
throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.');
}
if ($this->loader->supports($resource, $type)) {
$collections = $this->loader->load($resource, $type);
return \is_array($collections) ? $collections : [$collections];
}
if (null === $resolver = $this->loader->getResolver()) {
throw new LoaderLoadException($resource, null, 0, null, $type);
}
if (false === $loader = $resolver->resolve($resource, $type)) {
throw new LoaderLoadException($resource, null, 0, null, $type);
}
$collections = $loader->load($resource, $type);
return \is_array($collections) ? $collections : [$collections];
}
}

View File

@@ -19,11 +19,6 @@ namespace Symfony\Component\Routing;
*/
class RouteCompiler implements RouteCompilerInterface
{
/**
* @deprecated since Symfony 5.1, to be removed in 6.0
*/
public const REGEX_DELIMITER = '#';
/**
* This string defines the characters that are automatically considered separators in front of
* optional placeholders (with default and no static text following). Such a single separator
@@ -40,14 +35,12 @@ class RouteCompiler implements RouteCompilerInterface
public const VARIABLE_MAXIMUM_LENGTH = 32;
/**
* {@inheritdoc}
*
* @throws \InvalidArgumentException if a path variable is named _fragment
* @throws \LogicException if a variable is referenced more than once
* @throws \DomainException if a variable name starts with a digit or if it is too long to be successfully used as
* a PCRE subpattern
*/
public static function compile(Route $route)
public static function compile(Route $route): CompiledRoute
{
$hostVariables = [];
$variables = [];
@@ -122,7 +115,7 @@ class RouteCompiler implements RouteCompilerInterface
// Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
// in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
preg_match_all('#\{(!)?(\w+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
preg_match_all('#\{(!)?([\w\x80-\xFF]+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
foreach ($matches as $match) {
$important = $match[1][1] >= 0;
$varName = $match[2][0];
@@ -175,7 +168,7 @@ class RouteCompiler implements RouteCompilerInterface
preg_quote($defaultSeparator),
$defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator) : ''
);
if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
if (('' !== $nextSeparator && !preg_match('#^\{[\w\x80-\xFF]+\}#', $followingPattern)) || '' === $followingPattern) {
// When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
// quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
// Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
@@ -276,7 +269,7 @@ class RouteCompiler implements RouteCompilerInterface
return '';
}
// first remove all placeholders from the pattern so we can find the next real static character
if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) {
if ('' === $pattern = preg_replace('#\{[\w\x80-\xFF]+\}#', '', $pattern)) {
return '';
}
if ($useUtf8) {

View File

@@ -21,10 +21,8 @@ interface RouteCompilerInterface
/**
* Compiles the current route instance.
*
* @return CompiledRoute
*
* @throws \LogicException If the Route cannot be compiled because the
* path or host pattern is invalid
*/
public static function compile(Route $route);
public static function compile(Route $route): CompiledRoute;
}

View File

@@ -82,22 +82,16 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
protected $defaultLocale;
/**
* @var ConfigCacheFactoryInterface|null
*/
private $configCacheFactory;
private ConfigCacheFactoryInterface $configCacheFactory;
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = [];
private array $expressionLanguageProviders = [];
private static $cache = [];
private static ?array $cache = [];
/**
* @param mixed $resource The main resource to load
*/
public function __construct(LoaderInterface $loader, $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null, string $defaultLocale = null)
public function __construct(LoaderInterface $loader, mixed $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null, string $defaultLocale = null)
{
$this->loader = $loader;
$this->resource = $resource;
@@ -155,11 +149,9 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* Sets an option.
*
* @param mixed $value The value
*
* @throws \InvalidArgumentException
*/
public function setOption(string $key, $value)
public function setOption(string $key, mixed $value)
{
if (!\array_key_exists($key, $this->options)) {
throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
@@ -171,11 +163,9 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* Gets an option value.
*
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function getOption(string $key)
public function getOption(string $key): mixed
{
if (!\array_key_exists($key, $this->options)) {
throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
@@ -184,21 +174,11 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->options[$key];
}
/**
* {@inheritdoc}
*/
public function getRouteCollection()
{
if (null === $this->collection) {
$this->collection = $this->loader->load($this->resource, $this->options['resource_type']);
}
return $this->collection;
return $this->collection ??= $this->loader->load($this->resource, $this->options['resource_type']);
}
/**
* {@inheritdoc}
*/
public function setContext(RequestContext $context)
{
$this->context = $context;
@@ -211,10 +191,7 @@ class Router implements RouterInterface, RequestMatcherInterface
}
}
/**
* {@inheritdoc}
*/
public function getContext()
public function getContext(): RequestContext
{
return $this->context;
}
@@ -227,26 +204,17 @@ class Router implements RouterInterface, RequestMatcherInterface
$this->configCacheFactory = $configCacheFactory;
}
/**
* {@inheritdoc}
*/
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH)
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
{
return $this->getGenerator()->generate($name, $parameters, $referenceType);
}
/**
* {@inheritdoc}
*/
public function match(string $pathinfo)
public function match(string $pathinfo): array
{
return $this->getMatcher()->match($pathinfo);
}
/**
* {@inheritdoc}
*/
public function matchRequest(Request $request)
public function matchRequest(Request $request): array
{
$matcher = $this->getMatcher();
if (!$matcher instanceof RequestMatcherInterface) {
@@ -259,10 +227,8 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* Gets the UrlMatcher or RequestMatcher instance associated with this Router.
*
* @return UrlMatcherInterface|RequestMatcherInterface
*/
public function getMatcher()
public function getMatcher(): UrlMatcherInterface|RequestMatcherInterface
{
if (null !== $this->matcher) {
return $this->matcher;
@@ -302,10 +268,8 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* Gets the UrlGenerator instance associated with this Router.
*
* @return UrlGeneratorInterface
*/
public function getGenerator()
public function getGenerator(): UrlGeneratorInterface
{
if (null !== $this->generator) {
return $this->generator;
@@ -343,18 +307,12 @@ class Router implements RouterInterface, RequestMatcherInterface
$this->expressionLanguageProviders[] = $provider;
}
/**
* @return GeneratorDumperInterface
*/
protected function getGeneratorDumperInstance()
protected function getGeneratorDumperInstance(): GeneratorDumperInterface
{
return new $this->options['generator_dumper_class']($this->getRouteCollection());
}
/**
* @return MatcherDumperInterface
*/
protected function getMatcherDumperInstance()
protected function getMatcherDumperInstance(): MatcherDumperInterface
{
return new $this->options['matcher_dumper_class']($this->getRouteCollection());
}
@@ -365,16 +323,12 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
private function getConfigCacheFactory(): ConfigCacheFactoryInterface
{
if (null === $this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->options['debug']);
}
return $this->configCacheFactory;
return $this->configCacheFactory ??= new ConfigCacheFactory($this->options['debug']);
}
private static function getCompiledRoutes(string $path): array
{
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL))) {
self::$cache = null;
}
@@ -382,10 +336,6 @@ class Router implements RouterInterface, RequestMatcherInterface
return require $path;
}
if (isset(self::$cache[$path])) {
return self::$cache[$path];
}
return self::$cache[$path] = require $path;
return self::$cache[$path] ??= require $path;
}
}

View File

@@ -16,24 +16,22 @@
}
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-php80": "^1.16"
"php": ">=8.1"
},
"require-dev": {
"symfony/config": "^5.3|^6.0",
"symfony/http-foundation": "^4.4|^5.0|^6.0",
"symfony/yaml": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/config": "^6.2",
"symfony/http-foundation": "^5.4|^6.0",
"symfony/yaml": "^5.4|^6.0",
"symfony/expression-language": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"doctrine/annotations": "^1.12|^2",
"psr/log": "^1|^2|^3"
},
"conflict": {
"doctrine/annotations": "<1.12",
"symfony/config": "<5.3",
"symfony/dependency-injection": "<4.4",
"symfony/yaml": "<4.4"
"symfony/config": "<6.2",
"symfony/dependency-injection": "<5.4",
"symfony/yaml": "<5.4"
},
"suggest": {
"symfony/http-foundation": "For using a Symfony Request object",