From d7df3be9562670f23e7f5e905605a091df6b34d9 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 21 Oct 2022 14:45:14 +0200 Subject: [PATCH] Nicer config syntax for PSR-4 route loading --- .../Functional/app/Psr4Routing/routing.yml | 6 +++-- .../Config/Exception/LoaderLoadException.php | 12 ++++++++-- .../Component/Config/Loader/FileLoader.php | 6 ++++- .../Loader/AnnotationDirectoryLoader.php | 6 ++++- .../Routing/Loader/Psr4DirectoryLoader.php | 20 +++++++++------- .../Routing/Loader/XmlFileLoader.php | 15 ++++++++++-- .../Loader/schema/routing/routing-1.0.xsd | 9 +++++++- .../Tests/Fixtures/psr4-attributes.xml | 5 ++-- .../Tests/Fixtures/psr4-attributes.yaml | 6 +++-- .../Tests/Loader/Psr4DirectoryLoaderTest.php | 23 ++++++++++--------- src/Symfony/Component/Routing/composer.json | 4 ++-- 11 files changed, 78 insertions(+), 34 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml index 94c328341c969..d1dbf4abe9d5c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml @@ -1,4 +1,6 @@ test_bundle: prefix: /annotated - resource: "@TestBundle/Controller" - type: attribute@Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller + resource: + path: "@TestBundle/Controller" + namespace: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller + type: attribute diff --git a/src/Symfony/Component/Config/Exception/LoaderLoadException.php b/src/Symfony/Component/Config/Exception/LoaderLoadException.php index 9b5ffeb40c724..5f9bfbc0726d5 100644 --- a/src/Symfony/Component/Config/Exception/LoaderLoadException.php +++ b/src/Symfony/Component/Config/Exception/LoaderLoadException.php @@ -19,14 +19,22 @@ class LoaderLoadException extends \Exception { /** - * @param string $resource The resource that could not be imported + * @param mixed $resource The resource that could not be imported * @param string|null $sourceResource The original resource importing the new resource * @param int $code The error code * @param \Throwable|null $previous A previous exception * @param string|null $type The type of resource */ - public function __construct(string $resource, string $sourceResource = null, int $code = 0, \Throwable $previous = null, string $type = null) + public function __construct(mixed $resource, string $sourceResource = null, int $code = 0, \Throwable $previous = null, string $type = null) { + if (!\is_string($resource)) { + try { + $resource = json_encode($resource, \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + $resource = sprintf('resource of type "%s"', get_debug_type($resource)); + } + } + $message = ''; if ($previous) { // Include the previous exception, to help the user see what might be the underlying cause diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php index c479f75d341a2..bdf0eada6a6d8 100644 --- a/src/Symfony/Component/Config/Loader/FileLoader.php +++ b/src/Symfony/Component/Config/Loader/FileLoader.php @@ -136,7 +136,11 @@ private function doImport(mixed $resource, string $type = null, bool $ignoreErro try { $loader = $this->resolve($resource, $type); - if ($loader instanceof self && null !== $this->currentDir) { + if (!$loader instanceof self) { + return $loader->load($resource, $type); + } + + if (null !== $this->currentDir) { $resource = $loader->getLocator()->locate($resource, $this->currentDir, false); } diff --git a/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php index 4699a5c6bce69..258673593625b 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php @@ -66,11 +66,15 @@ function (\SplFileInfo $current) { public function supports(mixed $resource, string $type = null): bool { + if (!\is_string($resource)) { + return false; + } + if (\in_array($type, ['annotation', 'attribute'], true)) { return true; } - if ($type || !\is_string($resource)) { + if ($type) { return false; } diff --git a/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php b/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php index 701b79b01098f..0493cea15c258 100644 --- a/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php +++ b/src/Symfony/Component/Routing/Loader/Psr4DirectoryLoader.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Routing\Loader; use Symfony\Component\Config\FileLocatorInterface; -use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\RouteCollection; /** @@ -20,27 +20,31 @@ * * @author Alexander M. Turek */ -final class Psr4DirectoryLoader extends FileLoader +final class Psr4DirectoryLoader extends Loader { - public function __construct(FileLocatorInterface $locator) - { + 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($locator); + 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->locator->locate($resource['path']); if (!is_dir($path)) { return new RouteCollection(); } - return $this->loadFromDirectory($path, trim(substr($type, 10), ' \\')); + return $this->loadFromDirectory($path, trim($resource['namespace'], '\\')); } public function supports(mixed $resource, string $type = null): bool { - return \is_string($resource) && null !== $type && str_starts_with($type, 'attribute@'); + return ('attribute' === $type || 'annotation' === $type) && \is_array($resource) && isset($resource['path'], $resource['namespace']); } private function loadFromDirectory(string $directory, string $psr4Prefix): RouteCollection diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 4d360d2ae9ab7..93c37ebb35500 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -151,8 +151,17 @@ protected function parseRoute(RouteCollection $collection, \DOMElement $node, st */ protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file) { - if ('' === $resource = $node->getAttribute('resource')) { - throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); + /** @var \DOMElement $resourceElement */ + if (!($resource = $node->getAttribute('resource') ?: null) && $resourceElement = $node->getElementsByTagName('resource')[0] ?? null) { + $resource = []; + /** @var \DOMAttr $attribute */ + foreach ($resourceElement->attributes as $attribute) { + $resource[$attribute->name] = $attribute->value; + } + } + + if (!$resource) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute or element.', $path)); } $type = $node->getAttribute('type'); @@ -276,6 +285,8 @@ private function parseConfigs(\DOMElement $node, string $path): array 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)); } diff --git a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd index 66c40a0d86033..1b24dfdc89695 100644 --- a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd +++ b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd @@ -76,8 +76,9 @@ + - + @@ -93,6 +94,12 @@ + + + + + + diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.xml b/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.xml index 7c01826044436..5f778842a7a72 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.xml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.xml @@ -4,6 +4,7 @@ xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + + + diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.yaml b/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.yaml index d9902860a321b..81cd17cf84c2a 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.yaml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/psr4-attributes.yaml @@ -1,4 +1,6 @@ my_controllers: - resource: ./Psr4Controllers - type: attribute@Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers + resource: + path: ./Psr4Controllers + namespace: Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers + type: attribute prefix: /my-prefix diff --git a/src/Symfony/Component/Routing/Tests/Loader/Psr4DirectoryLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/Psr4DirectoryLoaderTest.php index 6ad14da22590e..541d352746c90 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/Psr4DirectoryLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/Psr4DirectoryLoaderTest.php @@ -57,34 +57,35 @@ public function testTraitController() } /** - * @dataProvider provideResourceTypesThatNeedTrimming + * @dataProvider provideNamespacesThatNeedTrimming */ - public function testPsr4NamespaceTrim(string $resourceType) + public function testPsr4NamespaceTrim(string $namespace) { $route = $this->getLoader() - ->load('Psr4Controllers', $resourceType) + ->load( + ['path' => 'Psr4Controllers', 'namespace' => $namespace], + 'attribute', + ) ->get('my_route'); $this->assertSame('/my/route', $route->getPath()); $this->assertSame(MyController::class.'::__invoke', $route->getDefault('_controller')); } - public function provideResourceTypesThatNeedTrimming(): array + public function provideNamespacesThatNeedTrimming(): array { return [ - ['attribute@ Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers'], - ['attribute@\\Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers'], - ['attribute@Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers\\'], - ['attribute@\\Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers\\'], - ['attribute@ \\Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers'], + ['\\Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers'], + ['Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers\\'], + ['\\Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers\\'], ]; } private function loadPsr4Controllers(): RouteCollection { return $this->getLoader()->load( - 'Psr4Controllers', - 'attribute@Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers' + ['path' => 'Psr4Controllers', 'namespace' => 'Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers'], + 'attribute' ); } diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 5c7c9cdb8cb23..be4bce3ebdf4c 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -19,7 +19,7 @@ "php": ">=8.1" }, "require-dev": { - "symfony/config": "^5.4|^6.0", + "symfony/config": "^6.2", "symfony/http-foundation": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", @@ -29,7 +29,7 @@ }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.4", + "symfony/config": "<6.2", "symfony/dependency-injection": "<5.4", "symfony/yaml": "<5.4" },