mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-12 19:21:33 +00:00
Import latest app updates from streamline
An updated set of source files and initialization was provided by streamline to address issues observed during initial testing. These files have been updated in order to generate a new set of app images.
This commit is contained in:
-25
@@ -1,25 +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\Loader;
|
||||
|
||||
trigger_deprecation('symfony/routing', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AnnotationClassLoader::class, AttributeClassLoader::class);
|
||||
|
||||
class_exists(AttributeClassLoader::class);
|
||||
|
||||
if (false) {
|
||||
/**
|
||||
* @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeClassLoader} instead
|
||||
*/
|
||||
abstract class AnnotationClassLoader extends AttributeClassLoader
|
||||
{
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +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\Loader;
|
||||
|
||||
trigger_deprecation('symfony/routing', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AnnotationDirectoryLoader::class, AttributeDirectoryLoader::class);
|
||||
|
||||
class_exists(AttributeDirectoryLoader::class);
|
||||
|
||||
if (false) {
|
||||
/**
|
||||
* @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeDirectoryLoader} instead
|
||||
*/
|
||||
class AnnotationDirectoryLoader extends AttributeDirectoryLoader
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +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\Loader;
|
||||
|
||||
trigger_deprecation('symfony/routing', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AnnotationFileLoader::class, AttributeFileLoader::class);
|
||||
|
||||
class_exists(AttributeFileLoader::class);
|
||||
|
||||
if (false) {
|
||||
/**
|
||||
* @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeFileLoader} instead
|
||||
*/
|
||||
class AnnotationFileLoader extends AttributeFileLoader
|
||||
{
|
||||
}
|
||||
}
|
||||
-431
@@ -1,431 +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\Loader;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\Config\Loader\LoaderResolverInterface;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Routing\Attribute\Route as RouteAnnotation;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* AttributeClassLoader loads routing information from a PHP class and its methods.
|
||||
*
|
||||
* You need to define an implementation for the configureRoute() method. Most of the
|
||||
* time, this method should define some PHP callable to be called for the route
|
||||
* (a controller in MVC speak).
|
||||
*
|
||||
* The #[Route] attribute can be set on the class (for global parameters),
|
||||
* and on each method.
|
||||
*
|
||||
* The #[Route] attribute main value is the route path. The attribute also
|
||||
* recognizes several parameters: requirements, options, defaults, schemes,
|
||||
* methods, host, and name. The name parameter is mandatory.
|
||||
* Here is an example of how you should be able to use it:
|
||||
*
|
||||
* #[Route('/Blog')]
|
||||
* class Blog
|
||||
* {
|
||||
* #[Route('/', name: 'blog_index')]
|
||||
* public function index()
|
||||
* {
|
||||
* }
|
||||
* #[Route('/{id}', name: 'blog_post', requirements: ["id" => '\d+'])]
|
||||
* public function show()
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
* @author Alexandre Daubois <alex.daubois@gmail.com>
|
||||
*/
|
||||
abstract class AttributeClassLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var Reader|null
|
||||
*
|
||||
* @deprecated in Symfony 6.4, this property will be removed in Symfony 7.
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $env;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $routeAnnotationClass = RouteAnnotation::class;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $defaultRouteIndex = 0;
|
||||
|
||||
private bool $hasDeprecatedAnnotations = false;
|
||||
|
||||
/**
|
||||
* @param string|null $env
|
||||
*/
|
||||
public function __construct($env = null)
|
||||
{
|
||||
if ($env instanceof Reader || null === $env && \func_num_args() > 1 && null !== func_get_arg(1)) {
|
||||
trigger_deprecation('symfony/routing', '6.4', 'Passing an instance of "%s" as first and the environment as second argument to "%s" is deprecated. Pass the environment as first argument instead.', Reader::class, __METHOD__);
|
||||
|
||||
$this->reader = $env;
|
||||
$env = \func_num_args() > 1 ? func_get_arg(1) : null;
|
||||
}
|
||||
|
||||
if (\is_string($env) || null === $env) {
|
||||
$this->env = $env;
|
||||
} elseif ($env instanceof \Stringable || \is_scalar($env)) {
|
||||
$this->env = (string) $env;
|
||||
} else {
|
||||
throw new \TypeError(__METHOD__.sprintf(': Parameter $env was expected to be a string or null, "%s" given.', get_debug_type($env)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the annotation class to read route properties from.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setRouteAnnotationClass(string $class)
|
||||
{
|
||||
$this->routeAnnotationClass = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException When route can't be parsed
|
||||
*/
|
||||
public function load(mixed $class, ?string $type = null): RouteCollection
|
||||
{
|
||||
if (!class_exists($class)) {
|
||||
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
|
||||
}
|
||||
|
||||
$class = new \ReflectionClass($class);
|
||||
if ($class->isAbstract()) {
|
||||
throw new \InvalidArgumentException(sprintf('Attributes from class "%s" cannot be read as it is abstract.', $class->getName()));
|
||||
}
|
||||
|
||||
$this->hasDeprecatedAnnotations = false;
|
||||
|
||||
try {
|
||||
$globals = $this->getGlobals($class);
|
||||
$collection = new RouteCollection();
|
||||
$collection->addResource(new FileResource($class->getFileName()));
|
||||
if ($globals['env'] && $this->env !== $globals['env']) {
|
||||
return $collection;
|
||||
}
|
||||
$fqcnAlias = false;
|
||||
foreach ($class->getMethods() as $method) {
|
||||
$this->defaultRouteIndex = 0;
|
||||
$routeNamesBefore = array_keys($collection->all());
|
||||
foreach ($this->getAnnotations($method) as $annot) {
|
||||
$this->addRoute($collection, $annot, $globals, $class, $method);
|
||||
if ('__invoke' === $method->name) {
|
||||
$fqcnAlias = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (1 === $collection->count() - \count($routeNamesBefore)) {
|
||||
$newRouteName = current(array_diff(array_keys($collection->all()), $routeNamesBefore));
|
||||
if ($newRouteName !== $aliasName = sprintf('%s::%s', $class->name, $method->name)) {
|
||||
$collection->addAlias($aliasName, $newRouteName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (0 === $collection->count() && $class->hasMethod('__invoke')) {
|
||||
$globals = $this->resetGlobals();
|
||||
foreach ($this->getAnnotations($class) as $annot) {
|
||||
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
|
||||
$fqcnAlias = true;
|
||||
}
|
||||
}
|
||||
if ($fqcnAlias && 1 === $collection->count()) {
|
||||
$invokeRouteName = key($collection->all());
|
||||
if ($invokeRouteName !== $class->name) {
|
||||
$collection->addAlias($class->name, $invokeRouteName);
|
||||
}
|
||||
|
||||
if ($invokeRouteName !== $aliasName = sprintf('%s::__invoke', $class->name)) {
|
||||
$collection->addAlias($aliasName, $invokeRouteName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasDeprecatedAnnotations) {
|
||||
trigger_deprecation('symfony/routing', '6.4', 'Class "%s" uses Doctrine Annotations to configure routes, which is deprecated. Use PHP attributes instead.', $class->getName());
|
||||
}
|
||||
} finally {
|
||||
$this->hasDeprecatedAnnotations = false;
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RouteAnnotation $annot or an object that exposes a similar interface
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method)
|
||||
{
|
||||
if ($annot->getEnv() && $annot->getEnv() !== $this->env) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $annot->getName() ?? $this->getDefaultRouteName($class, $method);
|
||||
$name = $globals['name'].$name;
|
||||
|
||||
$requirements = $annot->getRequirements();
|
||||
|
||||
foreach ($requirements as $placeholder => $requirement) {
|
||||
if (\is_int($placeholder)) {
|
||||
throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
$defaults = array_replace($globals['defaults'], $annot->getDefaults());
|
||||
$requirements = array_replace($globals['requirements'], $requirements);
|
||||
$options = array_replace($globals['options'], $annot->getOptions());
|
||||
$schemes = array_unique(array_merge($globals['schemes'], $annot->getSchemes()));
|
||||
$methods = array_unique(array_merge($globals['methods'], $annot->getMethods()));
|
||||
|
||||
$host = $annot->getHost() ?? $globals['host'];
|
||||
$condition = $annot->getCondition() ?? $globals['condition'];
|
||||
$priority = $annot->getPriority() ?? $globals['priority'];
|
||||
|
||||
$path = $annot->getLocalizedPaths() ?: $annot->getPath();
|
||||
$prefix = $globals['localized_paths'] ?: $globals['path'];
|
||||
$paths = [];
|
||||
|
||||
if (\is_array($path)) {
|
||||
if (!\is_array($prefix)) {
|
||||
foreach ($path as $locale => $localePath) {
|
||||
$paths[$locale] = $prefix.$localePath;
|
||||
}
|
||||
} elseif ($missing = array_diff_key($prefix, $path)) {
|
||||
throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name.'::'.$method->name, implode('", "', array_keys($missing))));
|
||||
} else {
|
||||
foreach ($path as $locale => $localePath) {
|
||||
if (!isset($prefix[$locale])) {
|
||||
throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name));
|
||||
}
|
||||
|
||||
$paths[$locale] = $prefix[$locale].$localePath;
|
||||
}
|
||||
}
|
||||
} elseif (\is_array($prefix)) {
|
||||
foreach ($prefix as $locale => $localePrefix) {
|
||||
$paths[$locale] = $localePrefix.$path;
|
||||
}
|
||||
} else {
|
||||
$paths[] = $prefix.$path;
|
||||
}
|
||||
|
||||
foreach ($method->getParameters() as $param) {
|
||||
if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) {
|
||||
continue;
|
||||
}
|
||||
foreach ($paths as $locale => $path) {
|
||||
if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/', preg_quote($param->name)), $path)) {
|
||||
if (\is_scalar($defaultValue = $param->getDefaultValue()) || null === $defaultValue) {
|
||||
$defaults[$param->name] = $defaultValue;
|
||||
} elseif ($defaultValue instanceof \BackedEnum) {
|
||||
$defaults[$param->name] = $defaultValue->value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($paths as $locale => $path) {
|
||||
$route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
|
||||
$this->configureRoute($route, $class, $method, $annot);
|
||||
if (0 !== $locale) {
|
||||
$route->setDefault('_locale', $locale);
|
||||
$route->setRequirement('_locale', preg_quote($locale));
|
||||
$route->setDefault('_canonical_route', $name);
|
||||
$collection->add($name.'.'.$locale, $route, $priority);
|
||||
} else {
|
||||
$collection->add($name, $route, $priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
if ('annotation' === $type) {
|
||||
trigger_deprecation('symfony/routing', '6.4', 'The "annotation" route type is deprecated, use the "attribute" route type instead.');
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public function setResolver(LoaderResolverInterface $resolver): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getResolver(): LoaderResolverInterface
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default route name for a class method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
|
||||
{
|
||||
$name = str_replace('\\', '_', $class->name).'_'.$method->name;
|
||||
$name = \function_exists('mb_strtolower') && preg_match('//u', $name) ? mb_strtolower($name, 'UTF-8') : strtolower($name);
|
||||
if ($this->defaultRouteIndex > 0) {
|
||||
$name .= '_'.$this->defaultRouteIndex;
|
||||
}
|
||||
++$this->defaultRouteIndex;
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function getGlobals(\ReflectionClass $class)
|
||||
{
|
||||
$globals = $this->resetGlobals();
|
||||
|
||||
$annot = null;
|
||||
if ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
|
||||
$annot = $attribute->newInstance();
|
||||
}
|
||||
if (!$annot && $annot = $this->reader?->getClassAnnotation($class, $this->routeAnnotationClass)) {
|
||||
$this->hasDeprecatedAnnotations = true;
|
||||
}
|
||||
|
||||
if ($annot) {
|
||||
if (null !== $annot->getName()) {
|
||||
$globals['name'] = $annot->getName();
|
||||
}
|
||||
|
||||
if (null !== $annot->getPath()) {
|
||||
$globals['path'] = $annot->getPath();
|
||||
}
|
||||
|
||||
$globals['localized_paths'] = $annot->getLocalizedPaths();
|
||||
|
||||
if (null !== $annot->getRequirements()) {
|
||||
$globals['requirements'] = $annot->getRequirements();
|
||||
}
|
||||
|
||||
if (null !== $annot->getOptions()) {
|
||||
$globals['options'] = $annot->getOptions();
|
||||
}
|
||||
|
||||
if (null !== $annot->getDefaults()) {
|
||||
$globals['defaults'] = $annot->getDefaults();
|
||||
}
|
||||
|
||||
if (null !== $annot->getSchemes()) {
|
||||
$globals['schemes'] = $annot->getSchemes();
|
||||
}
|
||||
|
||||
if (null !== $annot->getMethods()) {
|
||||
$globals['methods'] = $annot->getMethods();
|
||||
}
|
||||
|
||||
if (null !== $annot->getHost()) {
|
||||
$globals['host'] = $annot->getHost();
|
||||
}
|
||||
|
||||
if (null !== $annot->getCondition()) {
|
||||
$globals['condition'] = $annot->getCondition();
|
||||
}
|
||||
|
||||
$globals['priority'] = $annot->getPriority() ?? 0;
|
||||
$globals['env'] = $annot->getEnv();
|
||||
|
||||
foreach ($globals['requirements'] as $placeholder => $requirement) {
|
||||
if (\is_int($placeholder)) {
|
||||
throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?', $placeholder, $requirement, $class->getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $globals;
|
||||
}
|
||||
|
||||
private function resetGlobals(): array
|
||||
{
|
||||
return [
|
||||
'path' => null,
|
||||
'localized_paths' => [],
|
||||
'requirements' => [],
|
||||
'options' => [],
|
||||
'defaults' => [],
|
||||
'schemes' => [],
|
||||
'methods' => [],
|
||||
'host' => '',
|
||||
'condition' => '',
|
||||
'name' => '',
|
||||
'priority' => 0,
|
||||
'env' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Route
|
||||
*/
|
||||
protected function createRoute(string $path, array $defaults, array $requirements, array $options, ?string $host, array $schemes, array $methods, ?string $condition)
|
||||
{
|
||||
return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot);
|
||||
|
||||
/**
|
||||
* @return iterable<int, RouteAnnotation>
|
||||
*/
|
||||
private function getAnnotations(\ReflectionClass|\ReflectionMethod $reflection): iterable
|
||||
{
|
||||
foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
|
||||
yield $attribute->newInstance();
|
||||
}
|
||||
|
||||
if (!$this->reader) {
|
||||
return;
|
||||
}
|
||||
|
||||
$annotations = $reflection instanceof \ReflectionClass
|
||||
? $this->reader->getClassAnnotations($reflection)
|
||||
: $this->reader->getMethodAnnotations($reflection);
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
if ($annotation instanceof $this->routeAnnotationClass) {
|
||||
$this->hasDeprecatedAnnotations = true;
|
||||
|
||||
yield $annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(AnnotationClassLoader::class, false)) {
|
||||
class_alias(AttributeClassLoader::class, AnnotationClassLoader::class);
|
||||
}
|
||||
-92
@@ -1,92 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Resource\DirectoryResource;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* AttributeDirectoryLoader loads routing information from attributes set
|
||||
* on PHP classes and methods.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Alexandre Daubois <alex.daubois@gmail.com>
|
||||
*/
|
||||
class AttributeDirectoryLoader extends AttributeFileLoader
|
||||
{
|
||||
/**
|
||||
* @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
$collection = new RouteCollection();
|
||||
$collection->addResource(new DirectoryResource($dir, '/\.php$/'));
|
||||
$files = iterator_to_array(new \RecursiveIteratorIterator(
|
||||
new \RecursiveCallbackFilterIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
|
||||
fn (\SplFileInfo $current) => !str_starts_with($current->getBasename(), '.')
|
||||
),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
));
|
||||
usort($files, fn (\SplFileInfo $a, \SplFileInfo $b) => (string) $a > (string) $b ? 1 : -1);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isFile() || !str_ends_with($file->getFilename(), '.php')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($class = $this->findClass($file)) {
|
||||
$refl = new \ReflectionClass($class);
|
||||
if ($refl->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$collection->addCollection($this->loader->load($class, $type));
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
if (!\is_string($resource)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\in_array($type, ['annotation', 'attribute'], true)) {
|
||||
if ('annotation' === $type) {
|
||||
trigger_deprecation('symfony/routing', '6.4', 'The "annotation" route type is deprecated, use the "attribute" route type instead.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return is_dir($this->locator->locate($resource));
|
||||
} catch (\Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(AnnotationDirectoryLoader::class, false)) {
|
||||
class_alias(AttributeDirectoryLoader::class, AnnotationDirectoryLoader::class);
|
||||
}
|
||||
@@ -1,145 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\FileLocatorInterface;
|
||||
use Symfony\Component\Config\Loader\FileLoader;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* AttributeFileLoader loads routing information from attributes set
|
||||
* on a PHP class and its methods.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Alexandre Daubois <alex.daubois@gmail.com>
|
||||
*/
|
||||
class AttributeFileLoader extends FileLoader
|
||||
{
|
||||
protected $loader;
|
||||
|
||||
public function __construct(FileLocatorInterface $locator, AttributeClassLoader $loader)
|
||||
{
|
||||
if (!\function_exists('token_get_all')) {
|
||||
throw new \LogicException('The Tokenizer extension is required for the routing attribute loader.');
|
||||
}
|
||||
|
||||
parent::__construct($locator);
|
||||
|
||||
$this->loader = $loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads from attributes from a file.
|
||||
*
|
||||
* @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed
|
||||
*/
|
||||
public function load(mixed $file, ?string $type = null): ?RouteCollection
|
||||
{
|
||||
$path = $this->locator->locate($file);
|
||||
|
||||
$collection = new RouteCollection();
|
||||
if ($class = $this->findClass($path)) {
|
||||
$refl = new \ReflectionClass($class);
|
||||
if ($refl->isAbstract()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$collection->addResource(new FileResource($path));
|
||||
$collection->addCollection($this->loader->load($class, $type));
|
||||
}
|
||||
|
||||
gc_mem_caches();
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
if ('annotation' === $type) {
|
||||
trigger_deprecation('symfony/routing', '6.4', 'The "annotation" route type is deprecated, use the "attribute" route type instead.');
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
protected function findClass(string $file): string|false
|
||||
{
|
||||
$class = false;
|
||||
$namespace = false;
|
||||
$tokens = token_get_all(file_get_contents($file));
|
||||
|
||||
if (1 === \count($tokens) && \T_INLINE_HTML === $tokens[0][0]) {
|
||||
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the "<?php" start tag at the beginning of the file?', $file));
|
||||
}
|
||||
|
||||
$nsTokens = [\T_NS_SEPARATOR => true, \T_STRING => true];
|
||||
if (\defined('T_NAME_QUALIFIED')) {
|
||||
$nsTokens[\T_NAME_QUALIFIED] = true;
|
||||
}
|
||||
for ($i = 0; isset($tokens[$i]); ++$i) {
|
||||
$token = $tokens[$i];
|
||||
if (!isset($token[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (true === $class && \T_STRING === $token[0]) {
|
||||
return $namespace.'\\'.$token[1];
|
||||
}
|
||||
|
||||
if (true === $namespace && isset($nsTokens[$token[0]])) {
|
||||
$namespace = $token[1];
|
||||
while (isset($tokens[++$i][1], $nsTokens[$tokens[$i][0]])) {
|
||||
$namespace .= $tokens[$i][1];
|
||||
}
|
||||
$token = $tokens[$i];
|
||||
}
|
||||
|
||||
if (\T_CLASS === $token[0]) {
|
||||
// Skip usage of ::class constant and anonymous classes
|
||||
$skipClassToken = false;
|
||||
for ($j = $i - 1; $j > 0; --$j) {
|
||||
if (!isset($tokens[$j][1])) {
|
||||
if ('(' === $tokens[$j] || ',' === $tokens[$j]) {
|
||||
$skipClassToken = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (\T_DOUBLE_COLON === $tokens[$j][0] || \T_NEW === $tokens[$j][0]) {
|
||||
$skipClassToken = true;
|
||||
break;
|
||||
} elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$skipClassToken) {
|
||||
$class = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (\T_NAMESPACE === $token[0]) {
|
||||
$namespace = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(AnnotationFileLoader::class, false)) {
|
||||
class_alias(AttributeFileLoader::class, AnnotationFileLoader::class);
|
||||
}
|
||||
@@ -1,38 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\Loader;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* ClosureLoader loads routes from a PHP closure.
|
||||
*
|
||||
* The Closure must return a RouteCollection instance.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ClosureLoader extends Loader
|
||||
{
|
||||
/**
|
||||
* Loads a Closure.
|
||||
*/
|
||||
public function load(mixed $closure, ?string $type = null): RouteCollection
|
||||
{
|
||||
return $closure($this->env);
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
return $resource instanceof \Closure && (!$type || 'closure' === $type);
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +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\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Routing\Alias;
|
||||
|
||||
class AliasConfigurator
|
||||
{
|
||||
private Alias $alias;
|
||||
|
||||
public function __construct(Alias $alias)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this alias is deprecated, that means it should not be called anymore.
|
||||
*
|
||||
* @param string $package The name of the composer package that is triggering the deprecation
|
||||
* @param string $version The version of the package that introduced the deprecation
|
||||
* @param string $message The deprecation message to use
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException when the message template is invalid
|
||||
*/
|
||||
public function deprecate(string $package, string $version, string $message): static
|
||||
{
|
||||
$this->alias->setDeprecated($package, $version, $message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Vendored
-128
@@ -1,128 +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\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class CollectionConfigurator
|
||||
{
|
||||
use Traits\AddTrait;
|
||||
use Traits\HostTrait;
|
||||
use Traits\RouteTrait;
|
||||
|
||||
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)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->name = $name;
|
||||
$this->collection = new RouteCollection();
|
||||
$this->route = new Route('');
|
||||
$this->parentConfigurator = $parentConfigurator; // for GC control
|
||||
$this->parentPrefixes = $parentPrefixes;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (null === $this->prefixes) {
|
||||
$this->collection->addPrefix($this->route->getPath());
|
||||
}
|
||||
if (null !== $this->host) {
|
||||
$this->addHost($this->collection, $this->host);
|
||||
}
|
||||
|
||||
$this->parent->addCollection($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sub-collection.
|
||||
*/
|
||||
final public function collection(string $name = ''): self
|
||||
{
|
||||
return new self($this->collection, $this->name.$name, $this, $this->prefixes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to add to the path of all child routes.
|
||||
*
|
||||
* @param string|array $prefix the prefix, or the localized prefixes
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function prefix(string|array $prefix): static
|
||||
{
|
||||
if (\is_array($prefix)) {
|
||||
if (null === $this->parentPrefixes) {
|
||||
// no-op
|
||||
} elseif ($missing = array_diff_key($this->parentPrefixes, $prefix)) {
|
||||
throw new \LogicException(sprintf('Collection "%s" is missing prefixes for locale(s) "%s".', $this->name, implode('", "', array_keys($missing))));
|
||||
} else {
|
||||
foreach ($prefix as $locale => $localePrefix) {
|
||||
if (!isset($this->parentPrefixes[$locale])) {
|
||||
throw new \LogicException(sprintf('Collection "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $this->name, $locale));
|
||||
}
|
||||
|
||||
$prefix[$locale] = $this->parentPrefixes[$locale].$localePrefix;
|
||||
}
|
||||
}
|
||||
$this->prefixes = $prefix;
|
||||
$this->route->setPath('/');
|
||||
} else {
|
||||
$this->prefixes = null;
|
||||
$this->route->setPath($prefix);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host to use for all child routes.
|
||||
*
|
||||
* @param string|array $host the host, or the localized hosts
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function host(string|array $host): static
|
||||
{
|
||||
$this->host = $host;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method overrides the one from LocalizedRouteTrait.
|
||||
*/
|
||||
private function createRoute(string $path): Route
|
||||
{
|
||||
return (clone $this->route)->setPath($path);
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +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\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ImportConfigurator
|
||||
{
|
||||
use Traits\HostTrait;
|
||||
use Traits\PrefixTrait;
|
||||
use Traits\RouteTrait;
|
||||
|
||||
private RouteCollection $parent;
|
||||
|
||||
public function __construct(RouteCollection $parent, RouteCollection $route)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->route = $route;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->parent->addCollection($this->route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to add to the path of all child routes.
|
||||
*
|
||||
* @param string|array $prefix the prefix, or the localized prefixes
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function prefix(string|array $prefix, bool $trailingSlashOnRoot = true): static
|
||||
{
|
||||
$this->addPrefix($this->route, $prefix, $trailingSlashOnRoot);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix to add to the name of all child routes.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function namePrefix(string $namePrefix): static
|
||||
{
|
||||
$this->route->addNamePrefix($namePrefix);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host to use for all child routes.
|
||||
*
|
||||
* @param string|array $host the host, or the localized hosts
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function host(string|array $host): static
|
||||
{
|
||||
$this->addHost($this->route, $host);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +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\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class RouteConfigurator
|
||||
{
|
||||
use Traits\AddTrait;
|
||||
use Traits\HostTrait;
|
||||
use Traits\RouteTrait;
|
||||
|
||||
protected $parentConfigurator;
|
||||
|
||||
public function __construct(RouteCollection $collection, RouteCollection $route, string $name = '', ?CollectionConfigurator $parentConfigurator = null, ?array $prefixes = null)
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->route = $route;
|
||||
$this->name = $name;
|
||||
$this->parentConfigurator = $parentConfigurator; // for GC control
|
||||
$this->prefixes = $prefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host to use for all child routes.
|
||||
*
|
||||
* @param string|array $host the host, or the localized hosts
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function host(string|array $host): static
|
||||
{
|
||||
$this->addHost($this->route, $host);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
-78
@@ -1,78 +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\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Routing\Loader\PhpFileLoader;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class RoutingConfigurator
|
||||
{
|
||||
use Traits\AddTrait;
|
||||
|
||||
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)
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->loader = $loader;
|
||||
$this->path = $path;
|
||||
$this->file = $file;
|
||||
$this->env = $env;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[]|null $exclude Glob patterns to exclude from the import
|
||||
*/
|
||||
final public function import(string|array $resource, ?string $type = null, bool $ignoreErrors = false, string|array|null $exclude = null): ImportConfigurator
|
||||
{
|
||||
$this->loader->setCurrentDir(\dirname($this->path));
|
||||
|
||||
$imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file, $exclude) ?: [];
|
||||
if (!\is_array($imported)) {
|
||||
return new ImportConfigurator($this->collection, $imported);
|
||||
}
|
||||
|
||||
$mergedCollection = new RouteCollection();
|
||||
foreach ($imported as $subCollection) {
|
||||
$mergedCollection->addCollection($subCollection);
|
||||
}
|
||||
|
||||
return new ImportConfigurator($this->collection, $mergedCollection);
|
||||
}
|
||||
|
||||
final public function collection(string $name = ''): CollectionConfigurator
|
||||
{
|
||||
return new CollectionConfigurator($this->collection, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current environment to be able to write conditional configuration.
|
||||
*/
|
||||
final public function env(): ?string
|
||||
{
|
||||
return $this->env;
|
||||
}
|
||||
|
||||
final public function withPath(string $path): static
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->path = $clone->file = $path;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
-60
@@ -1,60 +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\Loader\Configurator\Traits;
|
||||
|
||||
use Symfony\Component\Routing\Loader\Configurator\AliasConfigurator;
|
||||
use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator;
|
||||
use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
trait AddTrait
|
||||
{
|
||||
use LocalizedRouteTrait;
|
||||
|
||||
/**
|
||||
* @var RouteCollection
|
||||
*/
|
||||
protected $collection;
|
||||
protected $name = '';
|
||||
protected $prefixes;
|
||||
|
||||
/**
|
||||
* Adds a route.
|
||||
*
|
||||
* @param string|array $path the path, or the localized paths of the route
|
||||
*/
|
||||
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);
|
||||
|
||||
return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes);
|
||||
}
|
||||
|
||||
public function alias(string $name, string $alias): AliasConfigurator
|
||||
{
|
||||
return new AliasConfigurator($this->collection->addAlias($name, $alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a route.
|
||||
*
|
||||
* @param string|array $path the path, or the localized paths of the route
|
||||
*/
|
||||
public function __invoke(string $name, string|array $path): RouteConfigurator
|
||||
{
|
||||
return $this->add($name, $path);
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +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\Loader\Configurator\Traits;
|
||||
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait HostTrait
|
||||
{
|
||||
final protected function addHost(RouteCollection $routes, string|array $hosts): void
|
||||
{
|
||||
if (!$hosts || !\is_array($hosts)) {
|
||||
$routes->setHost($hosts ?: '');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($routes->all() as $name => $route) {
|
||||
if (null === $locale = $route->getDefault('_locale')) {
|
||||
$routes->remove($name);
|
||||
foreach ($hosts as $locale => $host) {
|
||||
$localizedRoute = clone $route;
|
||||
$localizedRoute->setDefault('_locale', $locale);
|
||||
$localizedRoute->setRequirement('_locale', preg_quote($locale));
|
||||
$localizedRoute->setDefault('_canonical_route', $name);
|
||||
$localizedRoute->setHost($host);
|
||||
$routes->add($name.'.'.$locale, $localizedRoute);
|
||||
}
|
||||
} elseif (!isset($hosts[$locale])) {
|
||||
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding host in its parent collection.', $name, $locale));
|
||||
} else {
|
||||
$route->setHost($hosts[$locale]);
|
||||
$route->setRequirement('_locale', preg_quote($locale));
|
||||
$routes->add($name, $route);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-76
@@ -1,76 +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\Loader\Configurator\Traits;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
trait LocalizedRouteTrait
|
||||
{
|
||||
/**
|
||||
* Creates one or many routes.
|
||||
*
|
||||
* @param string|array $path the path, or the localized paths of the route
|
||||
*/
|
||||
final protected function createLocalizedRoute(RouteCollection $collection, string $name, string|array $path, string $namePrefix = '', ?array $prefixes = null): RouteCollection
|
||||
{
|
||||
$paths = [];
|
||||
|
||||
$routes = new RouteCollection();
|
||||
|
||||
if (\is_array($path)) {
|
||||
if (null === $prefixes) {
|
||||
$paths = $path;
|
||||
} elseif ($missing = array_diff_key($prefixes, $path)) {
|
||||
throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing))));
|
||||
} else {
|
||||
foreach ($path as $locale => $localePath) {
|
||||
if (!isset($prefixes[$locale])) {
|
||||
throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
|
||||
}
|
||||
|
||||
$paths[$locale] = $prefixes[$locale].$localePath;
|
||||
}
|
||||
}
|
||||
} elseif (null !== $prefixes) {
|
||||
foreach ($prefixes as $locale => $prefix) {
|
||||
$paths[$locale] = $prefix.$path;
|
||||
}
|
||||
} else {
|
||||
$routes->add($namePrefix.$name, $route = $this->createRoute($path));
|
||||
$collection->add($namePrefix.$name, $route);
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
foreach ($paths as $locale => $path) {
|
||||
$routes->add($name.'.'.$locale, $route = $this->createRoute($path));
|
||||
$collection->add($namePrefix.$name.'.'.$locale, $route);
|
||||
$route->setDefault('_locale', $locale);
|
||||
$route->setRequirement('_locale', preg_quote($locale));
|
||||
$route->setDefault('_canonical_route', $namePrefix.$name);
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
private function createRoute(string $path): Route
|
||||
{
|
||||
return new Route($path);
|
||||
}
|
||||
}
|
||||
-63
@@ -1,63 +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\Loader\Configurator\Traits;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
trait PrefixTrait
|
||||
{
|
||||
final protected function addPrefix(RouteCollection $routes, string|array $prefix, bool $trailingSlashOnRoot): void
|
||||
{
|
||||
if (\is_array($prefix)) {
|
||||
foreach ($prefix as $locale => $localePrefix) {
|
||||
$prefix[$locale] = trim(trim($localePrefix), '/');
|
||||
}
|
||||
foreach ($routes->all() as $name => $route) {
|
||||
if (null === $locale = $route->getDefault('_locale')) {
|
||||
$priority = $routes->getPriority($name) ?? 0;
|
||||
$routes->remove($name);
|
||||
foreach ($prefix as $locale => $localePrefix) {
|
||||
$localizedRoute = clone $route;
|
||||
$localizedRoute->setDefault('_locale', $locale);
|
||||
$localizedRoute->setRequirement('_locale', preg_quote($locale));
|
||||
$localizedRoute->setDefault('_canonical_route', $name);
|
||||
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||
$routes->add($name.'.'.$locale, $localizedRoute, $priority);
|
||||
}
|
||||
} elseif (!isset($prefix[$locale])) {
|
||||
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
|
||||
} else {
|
||||
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||
$routes->add($name, $route, $routes->getPriority($name) ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$routes->addPrefix($prefix);
|
||||
if (!$trailingSlashOnRoot) {
|
||||
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
|
||||
foreach ($routes->all() as $route) {
|
||||
if ($route->getPath() === $rootPath) {
|
||||
$route->setPath(rtrim($rootPath, '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-175
@@ -1,175 +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\Loader\Configurator\Traits;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
trait RouteTrait
|
||||
{
|
||||
/**
|
||||
* @var RouteCollection|Route
|
||||
*/
|
||||
protected $route;
|
||||
|
||||
/**
|
||||
* Adds defaults.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function defaults(array $defaults): static
|
||||
{
|
||||
$this->route->addDefaults($defaults);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds requirements.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function requirements(array $requirements): static
|
||||
{
|
||||
$this->route->addRequirements($requirements);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function options(array $options): static
|
||||
{
|
||||
$this->route->addOptions($options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether paths should accept utf8 encoding.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function utf8(bool $utf8 = true): static
|
||||
{
|
||||
$this->route->addOptions(['utf8' => $utf8]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the condition.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function condition(string $condition): static
|
||||
{
|
||||
$this->route->setCondition($condition);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pattern for the host.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function host(string $pattern): static
|
||||
{
|
||||
$this->route->setHost($pattern);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the schemes (e.g. 'https') this route is restricted to.
|
||||
* So an empty array means that any scheme is allowed.
|
||||
*
|
||||
* @param string[] $schemes
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function schemes(array $schemes): static
|
||||
{
|
||||
$this->route->setSchemes($schemes);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP methods (e.g. 'POST') this route is restricted to.
|
||||
* So an empty array means that any method is allowed.
|
||||
*
|
||||
* @param string[] $methods
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function methods(array $methods): static
|
||||
{
|
||||
$this->route->setMethods($methods);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the "_controller" entry to defaults.
|
||||
*
|
||||
* @param callable|string|array $controller a callable or parseable pseudo-callable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function controller(callable|string|array $controller): static
|
||||
{
|
||||
$this->route->addDefaults(['_controller' => $controller]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the "_locale" entry to defaults.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function locale(string $locale): static
|
||||
{
|
||||
$this->route->addDefaults(['_locale' => $locale]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the "_format" entry to defaults.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function format(string $format): static
|
||||
{
|
||||
$this->route->addDefaults(['_format' => $format]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the "_stateless" entry to defaults.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function stateless(bool $stateless = true): static
|
||||
{
|
||||
$this->route->addDefaults(['_stateless' => $stateless]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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\Loader;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A route loader that executes a service from a PSR-11 container to load the routes.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class ContainerLoader extends ObjectLoader
|
||||
{
|
||||
private ContainerInterface $container;
|
||||
|
||||
public function __construct(ContainerInterface $container, ?string $env = null)
|
||||
{
|
||||
$this->container = $container;
|
||||
parent::__construct($env);
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
return 'service' === $type && \is_string($resource);
|
||||
}
|
||||
|
||||
protected function getObject(string $id): object
|
||||
{
|
||||
return $this->container->get($id);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\FileLoader;
|
||||
use Symfony\Component\Config\Resource\DirectoryResource;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
class DirectoryLoader extends FileLoader
|
||||
{
|
||||
public function load(mixed $file, ?string $type = null): mixed
|
||||
{
|
||||
$path = $this->locator->locate($file);
|
||||
|
||||
$collection = new RouteCollection();
|
||||
$collection->addResource(new DirectoryResource($path));
|
||||
|
||||
foreach (scandir($path) as $dir) {
|
||||
if ('.' !== $dir[0]) {
|
||||
$this->setCurrentDir($path);
|
||||
$subPath = $path.'/'.$dir;
|
||||
$subType = null;
|
||||
|
||||
if (is_dir($subPath)) {
|
||||
$subPath .= '/';
|
||||
$subType = 'directory';
|
||||
}
|
||||
|
||||
$subCollection = $this->import($subPath, $subType, false, $path);
|
||||
$collection->addCollection($subCollection);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
// only when type is forced to directory, not to conflict with AttributeLoader
|
||||
|
||||
return 'directory' === $type;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\FileLoader;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* GlobFileLoader loads files from a glob pattern.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class GlobFileLoader extends FileLoader
|
||||
{
|
||||
public function load(mixed $resource, ?string $type = null): mixed
|
||||
{
|
||||
$collection = new RouteCollection();
|
||||
|
||||
foreach ($this->glob($resource, false, $globResource) as $path => $info) {
|
||||
$collection->addCollection($this->import($path));
|
||||
}
|
||||
|
||||
$collection->addResource($globResource);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
return 'glob' === $type;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\Loader;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* A route loader that calls a method on an object to load the routes.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
abstract class ObjectLoader extends Loader
|
||||
{
|
||||
/**
|
||||
* Returns the object that the method will be called on to load routes.
|
||||
*
|
||||
* For example, if your application uses a service container,
|
||||
* the $id may be a service id.
|
||||
*/
|
||||
abstract protected function getObject(string $id): object;
|
||||
|
||||
/**
|
||||
* Calls the object method that will load the routes.
|
||||
*/
|
||||
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'));
|
||||
}
|
||||
|
||||
$parts = explode('::', $resource);
|
||||
$method = $parts[1] ?? '__invoke';
|
||||
|
||||
$loaderObject = $this->getObject($parts[0]);
|
||||
|
||||
if (!\is_object($loaderObject)) {
|
||||
throw new \TypeError(sprintf('"%s:getObject()" must return an object: "%s" returned.', static::class, get_debug_type($loaderObject)));
|
||||
}
|
||||
|
||||
if (!\is_callable([$loaderObject, $method])) {
|
||||
throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource));
|
||||
}
|
||||
|
||||
$routeCollection = $loaderObject->$method($this, $this->env);
|
||||
|
||||
if (!$routeCollection instanceof RouteCollection) {
|
||||
$type = get_debug_type($routeCollection);
|
||||
|
||||
throw new \LogicException(sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', get_debug_type($loaderObject), $method, $type));
|
||||
}
|
||||
|
||||
// make the object file tracked so that if it changes, the cache rebuilds
|
||||
$this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection);
|
||||
|
||||
return $routeCollection;
|
||||
}
|
||||
|
||||
private function addClassResource(\ReflectionClass $class, RouteCollection $collection): void
|
||||
{
|
||||
do {
|
||||
if (is_file($class->getFileName())) {
|
||||
$collection->addResource(new FileResource($class->getFileName()));
|
||||
}
|
||||
} while ($class = $class->getParentClass());
|
||||
}
|
||||
}
|
||||
@@ -1,77 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\FileLoader;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* PhpFileLoader loads routes from a PHP file.
|
||||
*
|
||||
* The file must return a RouteCollection instance.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas grekas <p@tchwork.com>
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
class PhpFileLoader extends FileLoader
|
||||
{
|
||||
/**
|
||||
* Loads a PHP file.
|
||||
*/
|
||||
public function load(mixed $file, ?string $type = null): RouteCollection
|
||||
{
|
||||
$path = $this->locator->locate($file);
|
||||
$this->setCurrentDir(\dirname($path));
|
||||
|
||||
// the closure forbids access to the private scope in the included file
|
||||
$loader = $this;
|
||||
$load = \Closure::bind(static function ($file) use ($loader) {
|
||||
return include $file;
|
||||
}, null, ProtectedPhpFileLoader::class);
|
||||
|
||||
$result = $load($path);
|
||||
|
||||
if (\is_object($result) && \is_callable($result)) {
|
||||
$collection = $this->callConfigurator($result, $path, $file);
|
||||
} else {
|
||||
$collection = $result;
|
||||
}
|
||||
|
||||
$collection->addResource(new FileResource($path));
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type);
|
||||
}
|
||||
|
||||
protected function callConfigurator(callable $result, string $path, string $file): RouteCollection
|
||||
{
|
||||
$collection = new RouteCollection();
|
||||
|
||||
$result(new RoutingConfigurator($collection, $this, $path, $file, $this->env));
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ProtectedPhpFileLoader extends PhpFileLoader
|
||||
{
|
||||
}
|
||||
@@ -1,91 +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\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),
|
||||
fn (\SplFileInfo $current) => !str_starts_with($current->getBasename(), '.')
|
||||
),
|
||||
\RecursiveIteratorIterator::SELF_FIRST
|
||||
));
|
||||
usort($files, fn (\SplFileInfo $a, \SplFileInfo $b) => (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;
|
||||
}
|
||||
}
|
||||
@@ -1,470 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\FileLoader;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Config\Util\XmlUtils;
|
||||
use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait;
|
||||
use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
|
||||
use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* XmlFileLoader loads XML routing files.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
*/
|
||||
class XmlFileLoader extends FileLoader
|
||||
{
|
||||
use HostTrait;
|
||||
use LocalizedRouteTrait;
|
||||
use PrefixTrait;
|
||||
|
||||
public const NAMESPACE_URI = 'http://symfony.com/schema/routing';
|
||||
public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
|
||||
|
||||
/**
|
||||
* @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(mixed $file, ?string $type = null): RouteCollection
|
||||
{
|
||||
$path = $this->locator->locate($file);
|
||||
|
||||
$xml = $this->loadFile($path);
|
||||
|
||||
$collection = new RouteCollection();
|
||||
$collection->addResource(new FileResource($path));
|
||||
|
||||
// process routes and imports
|
||||
foreach ($xml->documentElement->childNodes as $node) {
|
||||
if (!$node instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->parseNode($collection, $node, $path, $file);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a node from a loaded XML file.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException When the XML is invalid
|
||||
*/
|
||||
protected function parseNode(RouteCollection $collection, \DOMElement $node, string $path, string $file)
|
||||
{
|
||||
if (self::NAMESPACE_URI !== $node->namespaceURI) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($node->localName) {
|
||||
case 'route':
|
||||
$this->parseRoute($collection, $node, $path);
|
||||
break;
|
||||
case 'import':
|
||||
$this->parseImport($collection, $node, $path, $file);
|
||||
break;
|
||||
case 'when':
|
||||
if (!$this->env || $node->getAttribute('env') !== $this->env) {
|
||||
break;
|
||||
}
|
||||
foreach ($node->childNodes as $node) {
|
||||
if ($node instanceof \DOMElement) {
|
||||
$this->parseNode($collection, $node, $path, $file);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path));
|
||||
}
|
||||
}
|
||||
|
||||
public function supports(mixed $resource, ?string $type = null): bool
|
||||
{
|
||||
return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a route and adds it to the RouteCollection.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException When the XML is invalid
|
||||
*/
|
||||
protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $path)
|
||||
{
|
||||
if ('' === $id = $node->getAttribute('id')) {
|
||||
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have an "id" attribute.', $path));
|
||||
}
|
||||
|
||||
if ('' !== $alias = $node->getAttribute('alias')) {
|
||||
$alias = $collection->addAlias($id, $alias);
|
||||
|
||||
if ($deprecationInfo = $this->parseDeprecation($node, $path)) {
|
||||
$alias->setDeprecated($deprecationInfo['package'], $deprecationInfo['version'], $deprecationInfo['message']);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY);
|
||||
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
[$defaults, $requirements, $options, $condition, $paths, /* $prefixes */, $hosts] = $this->parseConfigs($node, $path);
|
||||
|
||||
if (!$paths && '' === $node->getAttribute('path')) {
|
||||
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have a "path" attribute or <path> child nodes.', $path));
|
||||
}
|
||||
|
||||
if ($paths && '' !== $node->getAttribute('path')) {
|
||||
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must not have both a "path" attribute and <path> child nodes.', $path));
|
||||
}
|
||||
|
||||
$routes = $this->createLocalizedRoute($collection, $id, $paths ?: $node->getAttribute('path'));
|
||||
$routes->addDefaults($defaults);
|
||||
$routes->addRequirements($requirements);
|
||||
$routes->addOptions($options);
|
||||
$routes->setSchemes($schemes);
|
||||
$routes->setMethods($methods);
|
||||
$routes->setCondition($condition);
|
||||
|
||||
if (null !== $hosts) {
|
||||
$this->addHost($routes, $hosts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an import and adds the routes in the resource to the RouteCollection.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException When the XML is invalid
|
||||
*/
|
||||
protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file)
|
||||
{
|
||||
/** @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');
|
||||
$prefix = $node->getAttribute('prefix');
|
||||
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY) : null;
|
||||
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY) : null;
|
||||
$trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true;
|
||||
$namePrefix = $node->getAttribute('name-prefix') ?: null;
|
||||
|
||||
[$defaults, $requirements, $options, $condition, /* $paths */, $prefixes, $hosts] = $this->parseConfigs($node, $path);
|
||||
|
||||
if ('' !== $prefix && $prefixes) {
|
||||
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must not have both a "prefix" attribute and <prefix> child nodes.', $path));
|
||||
}
|
||||
|
||||
$exclude = [];
|
||||
foreach ($node->childNodes as $child) {
|
||||
if ($child instanceof \DOMElement && $child->localName === $exclude && self::NAMESPACE_URI === $child->namespaceURI) {
|
||||
$exclude[] = $child->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($node->hasAttribute('exclude')) {
|
||||
if ($exclude) {
|
||||
throw new \InvalidArgumentException('You cannot use both the attribute "exclude" and <exclude> tags at the same time.');
|
||||
}
|
||||
$exclude = [$node->getAttribute('exclude')];
|
||||
}
|
||||
|
||||
$this->setCurrentDir(\dirname($path));
|
||||
|
||||
/** @var RouteCollection[] $imported */
|
||||
$imported = $this->import($resource, '' !== $type ? $type : null, false, $file, $exclude) ?: [];
|
||||
|
||||
if (!\is_array($imported)) {
|
||||
$imported = [$imported];
|
||||
}
|
||||
|
||||
foreach ($imported as $subCollection) {
|
||||
$this->addPrefix($subCollection, $prefixes ?: $prefix, $trailingSlashOnRoot);
|
||||
|
||||
if (null !== $hosts) {
|
||||
$this->addHost($subCollection, $hosts);
|
||||
}
|
||||
|
||||
if (null !== $condition) {
|
||||
$subCollection->setCondition($condition);
|
||||
}
|
||||
if (null !== $schemes) {
|
||||
$subCollection->setSchemes($schemes);
|
||||
}
|
||||
if (null !== $methods) {
|
||||
$subCollection->setMethods($methods);
|
||||
}
|
||||
if (null !== $namePrefix) {
|
||||
$subCollection->addNamePrefix($namePrefix);
|
||||
}
|
||||
$subCollection->addDefaults($defaults);
|
||||
$subCollection->addRequirements($requirements);
|
||||
$subCollection->addOptions($options);
|
||||
|
||||
$collection->addCollection($subCollection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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): \DOMDocument
|
||||
{
|
||||
return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the config elements (default, requirement, option).
|
||||
*
|
||||
* @throws \InvalidArgumentException When the XML is invalid
|
||||
*/
|
||||
private function parseConfigs(\DOMElement $node, string $path): array
|
||||
{
|
||||
$defaults = [];
|
||||
$requirements = [];
|
||||
$options = [];
|
||||
$condition = null;
|
||||
$prefixes = [];
|
||||
$paths = [];
|
||||
$hosts = [];
|
||||
|
||||
/** @var \DOMElement $n */
|
||||
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
|
||||
if ($node !== $n->parentNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($n->localName) {
|
||||
case 'path':
|
||||
$paths[$n->getAttribute('locale')] = trim($n->textContent);
|
||||
break;
|
||||
case 'host':
|
||||
$hosts[$n->getAttribute('locale')] = trim($n->textContent);
|
||||
break;
|
||||
case 'prefix':
|
||||
$prefixes[$n->getAttribute('locale')] = trim($n->textContent);
|
||||
break;
|
||||
case 'default':
|
||||
if ($this->isElementValueNull($n)) {
|
||||
$defaults[$n->getAttribute('key')] = null;
|
||||
} else {
|
||||
$defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'requirement':
|
||||
$requirements[$n->getAttribute('key')] = trim($n->textContent);
|
||||
break;
|
||||
case 'option':
|
||||
$options[$n->getAttribute('key')] = XmlUtils::phpize(trim($n->textContent));
|
||||
break;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
if ($controller = $node->getAttribute('controller')) {
|
||||
if (isset($defaults['_controller'])) {
|
||||
$name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName);
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for ', $path).$name);
|
||||
}
|
||||
|
||||
$defaults['_controller'] = $controller;
|
||||
}
|
||||
if ($node->hasAttribute('locale')) {
|
||||
$defaults['_locale'] = $node->getAttribute('locale');
|
||||
}
|
||||
if ($node->hasAttribute('format')) {
|
||||
$defaults['_format'] = $node->getAttribute('format');
|
||||
}
|
||||
if ($node->hasAttribute('utf8')) {
|
||||
$options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8'));
|
||||
}
|
||||
if ($stateless = $node->getAttribute('stateless')) {
|
||||
if (isset($defaults['_stateless'])) {
|
||||
$name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName);
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" attribute and the defaults key "_stateless" for ', $path).$name);
|
||||
}
|
||||
|
||||
$defaults['_stateless'] = XmlUtils::phpize($stateless);
|
||||
}
|
||||
|
||||
if (!$hosts) {
|
||||
$hosts = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
|
||||
}
|
||||
|
||||
return [$defaults, $requirements, $options, $condition, $paths, $prefixes, $hosts];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the "default" elements.
|
||||
*/
|
||||
private function parseDefaultsConfig(\DOMElement $element, string $path): array|bool|float|int|string|null
|
||||
{
|
||||
if ($this->isElementValueNull($element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for existing element nodes in the default element. There can
|
||||
// only be a single element inside a default element. So this element
|
||||
// (if one was found) can safely be returned.
|
||||
foreach ($element->childNodes as $child) {
|
||||
if (!$child instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::NAMESPACE_URI !== $child->namespaceURI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $this->parseDefaultNode($child, $path);
|
||||
}
|
||||
|
||||
// If the default element doesn't contain a nested "bool", "int", "float",
|
||||
// "string", "list", or "map" element, the element contents will be treated
|
||||
// as the string value of the associated default option.
|
||||
return trim($element->textContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively parses the value of a "default" element.
|
||||
*
|
||||
* @throws \InvalidArgumentException when the XML is invalid
|
||||
*/
|
||||
private function parseDefaultNode(\DOMElement $node, string $path): array|bool|float|int|string|null
|
||||
{
|
||||
if ($this->isElementValueNull($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($node->localName) {
|
||||
case 'bool':
|
||||
return 'true' === trim($node->nodeValue) || '1' === trim($node->nodeValue);
|
||||
case 'int':
|
||||
return (int) trim($node->nodeValue);
|
||||
case 'float':
|
||||
return (float) trim($node->nodeValue);
|
||||
case 'string':
|
||||
return trim($node->nodeValue);
|
||||
case 'list':
|
||||
$list = [];
|
||||
|
||||
foreach ($node->childNodes as $element) {
|
||||
if (!$element instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::NAMESPACE_URI !== $element->namespaceURI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $this->parseDefaultNode($element, $path);
|
||||
}
|
||||
|
||||
return $list;
|
||||
case 'map':
|
||||
$map = [];
|
||||
|
||||
foreach ($node->childNodes as $element) {
|
||||
if (!$element instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::NAMESPACE_URI !== $element->namespaceURI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path);
|
||||
}
|
||||
|
||||
return $map;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path));
|
||||
}
|
||||
}
|
||||
|
||||
private function isElementValueNull(\DOMElement $element): bool
|
||||
{
|
||||
$namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance';
|
||||
|
||||
if (!$element->hasAttributeNS($namespaceUri, 'nil')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the deprecation elements.
|
||||
*
|
||||
* @throws \InvalidArgumentException When the XML is invalid
|
||||
*/
|
||||
private function parseDeprecation(\DOMElement $node, string $path): array
|
||||
{
|
||||
$deprecatedNode = null;
|
||||
foreach ($node->childNodes as $child) {
|
||||
if (!$child instanceof \DOMElement || self::NAMESPACE_URI !== $child->namespaceURI) {
|
||||
continue;
|
||||
}
|
||||
if ('deprecated' !== $child->localName) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $node->getAttribute('id'), $path));
|
||||
}
|
||||
|
||||
$deprecatedNode = $child;
|
||||
}
|
||||
|
||||
if (null === $deprecatedNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!$deprecatedNode->hasAttribute('package')) {
|
||||
throw new \InvalidArgumentException(sprintf('The <deprecated> element in file "%s" must have a "package" attribute.', $path));
|
||||
}
|
||||
if (!$deprecatedNode->hasAttribute('version')) {
|
||||
throw new \InvalidArgumentException(sprintf('The <deprecated> element in file "%s" must have a "version" attribute.', $path));
|
||||
}
|
||||
|
||||
return [
|
||||
'package' => $deprecatedNode->getAttribute('package'),
|
||||
'version' => $deprecatedNode->getAttribute('version'),
|
||||
'message' => trim($deprecatedNode->nodeValue),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,302 +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\Loader;
|
||||
|
||||
use Symfony\Component\Config\Loader\FileLoader;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait;
|
||||
use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
|
||||
use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* YamlFileLoader loads Yaml routing files.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Tobias Schultze <http://tobion.de>
|
||||
*/
|
||||
class YamlFileLoader extends FileLoader
|
||||
{
|
||||
use HostTrait;
|
||||
use LocalizedRouteTrait;
|
||||
use PrefixTrait;
|
||||
|
||||
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 $yamlParser;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid
|
||||
*/
|
||||
public function load(mixed $file, ?string $type = null): RouteCollection
|
||||
{
|
||||
$path = $this->locator->locate($file);
|
||||
|
||||
if (!stream_is_local($path)) {
|
||||
throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path));
|
||||
}
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path));
|
||||
}
|
||||
|
||||
$this->yamlParser ??= new YamlParser();
|
||||
|
||||
try {
|
||||
$parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
|
||||
} catch (ParseException $e) {
|
||||
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $path).$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$collection = new RouteCollection();
|
||||
$collection->addResource(new FileResource($path));
|
||||
|
||||
// empty file
|
||||
if (null === $parsedConfig) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
// not an array
|
||||
if (!\is_array($parsedConfig)) {
|
||||
throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path));
|
||||
}
|
||||
|
||||
foreach ($parsedConfig as $name => $config) {
|
||||
if (str_starts_with($name, 'when@')) {
|
||||
if (!$this->env || 'when@'.$this->env !== $name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($config as $name => $config) {
|
||||
$this->validate($config, $name.'" when "@'.$this->env, $path);
|
||||
|
||||
if (isset($config['resource'])) {
|
||||
$this->parseImport($collection, $config, $path, $file);
|
||||
} else {
|
||||
$this->parseRoute($collection, $name, $config, $path);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->validate($config, $name, $path);
|
||||
|
||||
if (isset($config['resource'])) {
|
||||
$this->parseImport($collection, $config, $path, $file);
|
||||
} else {
|
||||
$this->parseRoute($collection, $name, $config, $path);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a route and adds it to the RouteCollection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseRoute(RouteCollection $collection, string $name, array $config, string $path)
|
||||
{
|
||||
if (isset($config['alias'])) {
|
||||
$alias = $collection->addAlias($name, $config['alias']);
|
||||
$deprecation = $config['deprecated'] ?? null;
|
||||
if (null !== $deprecation) {
|
||||
$alias->setDeprecated(
|
||||
$deprecation['package'],
|
||||
$deprecation['version'],
|
||||
$deprecation['message'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = $config['defaults'] ?? [];
|
||||
$requirements = $config['requirements'] ?? [];
|
||||
$options = $config['options'] ?? [];
|
||||
|
||||
foreach ($requirements as $placeholder => $requirement) {
|
||||
if (\is_int($placeholder)) {
|
||||
throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s"?', $placeholder, $requirement, $name, $path));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['controller'])) {
|
||||
$defaults['_controller'] = $config['controller'];
|
||||
}
|
||||
if (isset($config['locale'])) {
|
||||
$defaults['_locale'] = $config['locale'];
|
||||
}
|
||||
if (isset($config['format'])) {
|
||||
$defaults['_format'] = $config['format'];
|
||||
}
|
||||
if (isset($config['utf8'])) {
|
||||
$options['utf8'] = $config['utf8'];
|
||||
}
|
||||
if (isset($config['stateless'])) {
|
||||
$defaults['_stateless'] = $config['stateless'];
|
||||
}
|
||||
|
||||
$routes = $this->createLocalizedRoute($collection, $name, $config['path']);
|
||||
$routes->addDefaults($defaults);
|
||||
$routes->addRequirements($requirements);
|
||||
$routes->addOptions($options);
|
||||
$routes->setSchemes($config['schemes'] ?? []);
|
||||
$routes->setMethods($config['methods'] ?? []);
|
||||
$routes->setCondition($config['condition'] ?? null);
|
||||
|
||||
if (isset($config['host'])) {
|
||||
$this->addHost($routes, $config['host']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an import and adds the routes in the resource to the RouteCollection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseImport(RouteCollection $collection, array $config, string $path, string $file)
|
||||
{
|
||||
$type = $config['type'] ?? null;
|
||||
$prefix = $config['prefix'] ?? '';
|
||||
$defaults = $config['defaults'] ?? [];
|
||||
$requirements = $config['requirements'] ?? [];
|
||||
$options = $config['options'] ?? [];
|
||||
$host = $config['host'] ?? null;
|
||||
$condition = $config['condition'] ?? null;
|
||||
$schemes = $config['schemes'] ?? null;
|
||||
$methods = $config['methods'] ?? null;
|
||||
$trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true;
|
||||
$namePrefix = $config['name_prefix'] ?? null;
|
||||
$exclude = $config['exclude'] ?? null;
|
||||
|
||||
if (isset($config['controller'])) {
|
||||
$defaults['_controller'] = $config['controller'];
|
||||
}
|
||||
if (isset($config['locale'])) {
|
||||
$defaults['_locale'] = $config['locale'];
|
||||
}
|
||||
if (isset($config['format'])) {
|
||||
$defaults['_format'] = $config['format'];
|
||||
}
|
||||
if (isset($config['utf8'])) {
|
||||
$options['utf8'] = $config['utf8'];
|
||||
}
|
||||
if (isset($config['stateless'])) {
|
||||
$defaults['_stateless'] = $config['stateless'];
|
||||
}
|
||||
|
||||
$this->setCurrentDir(\dirname($path));
|
||||
|
||||
/** @var RouteCollection[] $imported */
|
||||
$imported = $this->import($config['resource'], $type, false, $file, $exclude) ?: [];
|
||||
|
||||
if (!\is_array($imported)) {
|
||||
$imported = [$imported];
|
||||
}
|
||||
|
||||
foreach ($imported as $subCollection) {
|
||||
$this->addPrefix($subCollection, $prefix, $trailingSlashOnRoot);
|
||||
|
||||
if (null !== $host) {
|
||||
$this->addHost($subCollection, $host);
|
||||
}
|
||||
if (null !== $condition) {
|
||||
$subCollection->setCondition($condition);
|
||||
}
|
||||
if (null !== $schemes) {
|
||||
$subCollection->setSchemes($schemes);
|
||||
}
|
||||
if (null !== $methods) {
|
||||
$subCollection->setMethods($methods);
|
||||
}
|
||||
if (null !== $namePrefix) {
|
||||
$subCollection->addNamePrefix($namePrefix);
|
||||
}
|
||||
$subCollection->addDefaults($defaults);
|
||||
$subCollection->addRequirements($requirements);
|
||||
$subCollection->addOptions($options);
|
||||
|
||||
$collection->addCollection($subCollection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException If one of the provided config keys is not supported,
|
||||
* something is missing or the combination is nonsense
|
||||
*/
|
||||
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));
|
||||
}
|
||||
if (isset($config['alias'])) {
|
||||
$this->validateAlias($config, $name, $path);
|
||||
|
||||
return;
|
||||
}
|
||||
if ($extraKeys = array_diff(array_keys($config), self::AVAILABLE_KEYS)) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::AVAILABLE_KEYS)));
|
||||
}
|
||||
if (isset($config['resource']) && isset($config['path'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', $path, $name));
|
||||
}
|
||||
if (!isset($config['resource']) && isset($config['type'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', $name, $path));
|
||||
}
|
||||
if (!isset($config['resource']) && !isset($config['path'])) {
|
||||
throw new \InvalidArgumentException(sprintf('You must define a "path" for the route "%s" in file "%s".', $name, $path));
|
||||
}
|
||||
if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name));
|
||||
}
|
||||
if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".', $path, $name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException If one of the provided config keys is not supported,
|
||||
* something is missing or the combination is nonsense
|
||||
*/
|
||||
private function validateAlias(array $config, string $name, string $path): void
|
||||
{
|
||||
foreach ($config as $key => $value) {
|
||||
if (!\in_array($key, ['alias', 'deprecated'], true)) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify other keys than "alias" and "deprecated" for "%s".', $path, $name));
|
||||
}
|
||||
|
||||
if ('deprecated' === $key) {
|
||||
if (!isset($value['package'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "package" of the "deprecated" option for "%s".', $path, $name));
|
||||
}
|
||||
|
||||
if (!isset($value['version'])) {
|
||||
throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "version" of the "deprecated" option for "%s".', $path, $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<xsd:schema xmlns="http://symfony.com/schema/routing"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://symfony.com/schema/routing"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Symfony XML Routing Schema, version 1.0
|
||||
Authors: Fabien Potencier, Tobias Schultze
|
||||
|
||||
This scheme defines the elements and attributes that can be used to define
|
||||
routes. A route maps an HTTP request to a set of configuration variables.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
<xsd:element name="routes" type="routes" />
|
||||
|
||||
<xsd:complexType name="routes">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="import" type="import" />
|
||||
<xsd:element name="route" type="route" />
|
||||
<xsd:element name="when" type="when" />
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="when">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="import" type="import" />
|
||||
<xsd:element name="route" type="route" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="env" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="localized-path">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="locale" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:group name="configs">
|
||||
<xsd:choice>
|
||||
<xsd:element name="default" nillable="true" type="default" />
|
||||
<xsd:element name="requirement" type="element" />
|
||||
<xsd:element name="option" type="element" />
|
||||
<xsd:element name="condition" type="xsd:string" />
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
|
||||
<xsd:complexType name="route">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="path" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="host" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="path" type="xsd:string" />
|
||||
<xsd:attribute name="host" type="xsd:string" />
|
||||
<xsd:attribute name="schemes" type="xsd:string" />
|
||||
<xsd:attribute name="methods" type="xsd:string" />
|
||||
<xsd:attribute name="controller" type="xsd:string" />
|
||||
<xsd:attribute name="locale" type="xsd:string" />
|
||||
<xsd:attribute name="format" type="xsd:string" />
|
||||
<xsd:attribute name="utf8" type="xsd:boolean" />
|
||||
<xsd:attribute name="stateless" type="xsd:boolean" />
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="import">
|
||||
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
|
||||
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
|
||||
<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" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="exclude" type="xsd:string" />
|
||||
<xsd:attribute name="prefix" type="xsd:string" />
|
||||
<xsd:attribute name="name-prefix" type="xsd:string" />
|
||||
<xsd:attribute name="host" type="xsd:string" />
|
||||
<xsd:attribute name="schemes" type="xsd:string" />
|
||||
<xsd:attribute name="methods" type="xsd:string" />
|
||||
<xsd:attribute name="controller" type="xsd:string" />
|
||||
<xsd:attribute name="locale" type="xsd:string" />
|
||||
<xsd:attribute name="format" type="xsd:string" />
|
||||
<xsd:attribute name="trailing-slash-on-root" type="xsd:boolean" />
|
||||
<xsd:attribute name="utf8" type="xsd:boolean" />
|
||||
<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" />
|
||||
<xsd:element name="int" type="xsd:integer" />
|
||||
<xsd:element name="float" type="xsd:float" />
|
||||
<xsd:element name="string" type="xsd:string" />
|
||||
<xsd:element name="list" type="list" />
|
||||
<xsd:element name="map" type="map" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="element">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="list">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="bool" nillable="true" type="xsd:boolean" />
|
||||
<xsd:element name="int" nillable="true" type="xsd:integer" />
|
||||
<xsd:element name="float" nillable="true" type="xsd:float" />
|
||||
<xsd:element name="string" nillable="true" type="xsd:string" />
|
||||
<xsd:element name="list" nillable="true" type="list" />
|
||||
<xsd:element name="map" nillable="true" type="map" />
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="bool" nillable="true" type="map-bool-entry" />
|
||||
<xsd:element name="int" nillable="true" type="map-int-entry" />
|
||||
<xsd:element name="float" nillable="true" type="map-float-entry" />
|
||||
<xsd:element name="string" nillable="true" type="map-string-entry" />
|
||||
<xsd:element name="list" nillable="true" type="map-list-entry" />
|
||||
<xsd:element name="map" nillable="true" type="map-map-entry" />
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map-bool-entry">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:boolean">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map-int-entry">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:integer">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map-float-entry">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:float">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map-string-entry">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map-list-entry">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="list">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="map-map-entry">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="map">
|
||||
<xsd:attribute name="key" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="deprecated">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="package" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="version" type="xsd:string" use="required" />
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
Reference in New Issue
Block a user