From f0978de493c15de7ba16b2894569c6a8ec577247 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 3 Sep 2020 09:55:01 +0200 Subject: [PATCH] [Routing] Added the Route attribute. --- .github/patch-types.php | 1 + .../Component/Routing/Annotation/Route.php | 57 +++++- .../Routing/Loader/AnnotationClassLoader.php | 74 ++++++-- .../Fixtures/AnnotationFixtures/BazClass.php | 25 +++ .../AnnotationFixtures/EncodingClass.php | 15 ++ .../ActionPathController.php | 13 ++ .../Fixtures/AttributeFixtures/BazClass.php | 25 +++ .../DefaultValueController.php | 21 +++ .../AttributeFixtures/EncodingClass.php | 13 ++ .../ExplicitLocalizedActionPathController.php | 13 ++ .../AttributeFixtures/GlobalDefaultsClass.php | 28 +++ .../AttributeFixtures/InvokableController.php | 13 ++ .../InvokableLocalizedController.php | 13 ++ .../LocalizedActionPathController.php | 13 ++ .../LocalizedMethodActionControllers.php | 19 ++ ...calizedPrefixLocalizedActionController.php | 14 ++ .../LocalizedPrefixWithRouteWithoutLocale.php | 14 ++ .../MethodActionControllers.php | 19 ++ .../MissingRouteNameController.php | 13 ++ .../NothingButNameController.php | 13 ++ ...PrefixedActionLocalizedRouteController.php | 14 ++ .../PrefixedActionPathController.php | 14 ++ ...ementsWithoutPlaceholderNameController.php | 23 +++ .../RouteWithPrefixController.php | 14 ++ .../Utf8ActionControllers.php | 19 ++ .../Loader/AnnotationClassLoaderTest.php | 163 ++++-------------- ...notationClassLoaderWithAnnotationsTest.php | 35 ++++ ...nnotationClassLoaderWithAttributesTest.php | 34 ++++ src/Symfony/Component/Routing/composer.json | 2 +- 29 files changed, 590 insertions(+), 144 deletions(-) create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/BazClass.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/EncodingClass.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ActionPathController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/BazClass.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/DefaultValueController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/EncodingClass.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExplicitLocalizedActionPathController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/GlobalDefaultsClass.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/InvokableController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/InvokableLocalizedController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedActionPathController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedMethodActionControllers.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixLocalizedActionController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixWithRouteWithoutLocale.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/MethodActionControllers.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/MissingRouteNameController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/NothingButNameController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/PrefixedActionLocalizedRouteController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/PrefixedActionPathController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RequirementsWithoutPlaceholderNameController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithPrefixController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/Utf8ActionControllers.php create mode 100644 src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php create mode 100644 src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php diff --git a/.github/patch-types.php b/.github/patch-types.php index 70fea35aaae3e..e98f70e4669c5 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -31,6 +31,7 @@ case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php'): case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'): case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php80Dummy.php'): + case false !== strpos($file, '/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures'): case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php') && \PHP_VERSION_ID < 70400: diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index 43e185e6cf369..d575cb7fdfd84 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Routing\Annotation; +use Attribute; + /** * Annotation class for @Route(). * @@ -18,7 +20,9 @@ * @Target({"CLASS", "METHOD"}) * * @author Fabien Potencier + * @author Alexander M. Turek */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] class Route { private $path; @@ -34,12 +38,59 @@ class Route private $priority; /** - * @param array $data An array of key/value parameters + * @param array|string $data data array managed by the Doctrine Annotations library or the path + * @param array|string|null $path + * @param string[] $requirements + * @param string[] $methods + * @param string[] $schemes * * @throws \BadMethodCallException */ - public function __construct(array $data) - { + public function __construct( + $data = [], + $path = null, + string $name = null, + array $requirements = [], + array $options = [], + array $defaults = [], + string $host = null, + array $methods = [], + array $schemes = [], + string $condition = null, + int $priority = null, + string $locale = null, + string $format = null, + bool $utf8 = null, + bool $stateless = null + ) { + if (\is_string($data)) { + $data = ['path' => $data]; + } elseif (!\is_array($data)) { + throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); + } + if (null !== $path && !\is_string($path) && !\is_array($path)) { + throw new \TypeError(sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, get_debug_type($path))); + } + + $data['path'] = $data['path'] ?? $path; + $data['name'] = $data['name'] ?? $name; + $data['requirements'] = $data['requirements'] ?? $requirements; + $data['options'] = $data['options'] ?? $options; + $data['defaults'] = $data['defaults'] ?? $defaults; + $data['host'] = $data['host'] ?? $host; + $data['methods'] = $data['methods'] ?? $methods; + $data['schemes'] = $data['schemes'] ?? $schemes; + $data['condition'] = $data['condition'] ?? $condition; + $data['priority'] = $data['priority'] ?? $priority; + $data['locale'] = $data['locale'] ?? $locale; + $data['format'] = $data['format'] ?? $format; + $data['utf8'] = $data['utf8'] ?? $utf8; + $data['stateless'] = $data['stateless'] ?? $stateless; + + $data = array_filter($data, static function ($value): bool { + return null !== $value; + }); + if (isset($data['localized_paths'])) { throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', static::class)); } diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 89d0bfa31727e..bf8fe2af368b1 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -51,8 +51,24 @@ * { * } * } + * + * On PHP 8, the annotation class can be used as an attribute as well: + * #[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 + * @author Alexander M. Turek */ abstract class AnnotationClassLoader implements LoaderInterface { @@ -61,14 +77,14 @@ abstract class AnnotationClassLoader implements LoaderInterface /** * @var string */ - protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + protected $routeAnnotationClass = RouteAnnotation::class; /** * @var int */ protected $defaultRouteIndex = 0; - public function __construct(Reader $reader) + public function __construct(Reader $reader = null) { $this->reader = $reader; } @@ -108,19 +124,15 @@ public function load($class, string $type = null) foreach ($class->getMethods() as $method) { $this->defaultRouteIndex = 0; - foreach ($this->reader->getMethodAnnotations($method) as $annot) { - if ($annot instanceof $this->routeAnnotationClass) { - $this->addRoute($collection, $annot, $globals, $class, $method); - } + foreach ($this->getAnnotations($method) as $annot) { + $this->addRoute($collection, $annot, $globals, $class, $method); } } if (0 === $collection->count() && $class->hasMethod('__invoke')) { $globals = $this->resetGlobals(); - foreach ($this->reader->getClassAnnotations($class) as $annot) { - if ($annot instanceof $this->routeAnnotationClass) { - $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); - } + foreach ($this->getAnnotations($class) as $annot) { + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); } } @@ -130,7 +142,7 @@ public function load($class, string $type = null) /** * @param RouteAnnotation $annot or an object that exposes a similar interface */ - protected function addRoute(RouteCollection $collection, $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method) + protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method) { $name = $annot->getName(); if (null === $name) { @@ -257,7 +269,15 @@ protected function getGlobals(\ReflectionClass $class) { $globals = $this->resetGlobals(); - if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + $annot = null; + if (\PHP_VERSION_ID >= 80000 && ($attribute = $class->getAttributes($this->routeAnnotationClass)[0] ?? null)) { + $annot = $attribute->newInstance(); + } + if (!$annot && $this->reader) { + $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass); + } + + if ($annot) { if (null !== $annot->getName()) { $globals['name'] = $annot->getName(); } @@ -330,5 +350,33 @@ protected function createRoute(string $path, array $defaults, array $requirement return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); } - abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot); + + /** + * @param \ReflectionClass|\ReflectionMethod $reflection + * + * @return iterable|RouteAnnotation[] + */ + private function getAnnotations(object $reflection): iterable + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($reflection->getAttributes($this->routeAnnotationClass) as $attribute) { + yield $attribute->newInstance(); + } + } + + if (!$this->reader) { + return; + } + + $anntotations = $reflection instanceof \ReflectionClass + ? $this->reader->getClassAnnotations($reflection) + : $this->reader->getMethodAnnotations($reflection); + + foreach ($anntotations as $annotation) { + if ($annotation instanceof $this->routeAnnotationClass) { + yield $annotation; + } + } + } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/BazClass.php b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/BazClass.php new file mode 100644 index 0000000000000..e610806df4620 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/BazClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures; + +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("/1", name="route1", schemes={"https"}, methods={"GET"}) + * @Route("/2", name="route2", schemes={"https"}, methods={"GET"}) + */ +class BazClass +{ + public function __invoke() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/EncodingClass.php b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/EncodingClass.php new file mode 100644 index 0000000000000..52c7b267276ad --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/EncodingClass.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures; + +use Symfony\Component\Routing\Annotation\Route; + +#[ + Route(path: '/1', name: 'route1', schemes: ['https'], methods: ['GET']), + Route(path: '/2', name: 'route2', schemes: ['https'], methods: ['GET']), +] +class BazClass +{ + public function __invoke() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/DefaultValueController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/DefaultValueController.php new file mode 100644 index 0000000000000..5bbfb0126dd2b --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/DefaultValueController.php @@ -0,0 +1,21 @@ +}', name: 'hello_without_default'), + Route(path: 'hello/{name<\w+>?Symfony}', name: 'hello_with_default'), + ] + public function hello(string $name = 'World') + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/EncodingClass.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/EncodingClass.php new file mode 100644 index 0000000000000..36ab4dba450df --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/EncodingClass.php @@ -0,0 +1,13 @@ + '/path', 'nl' => '/pad'], name: 'action')] + public function action() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/GlobalDefaultsClass.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/GlobalDefaultsClass.php new file mode 100644 index 0000000000000..07d68e8d42280 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/GlobalDefaultsClass.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures; + +use Symfony\Component\Routing\Annotation\Route; + +#[Route(path: '/defaults', locale: 'g_locale', format: 'g_format')] +class GlobalDefaultsClass +{ + #[Route(path: '/specific-locale', name: 'specific_locale', locale: 's_locale')] + public function locale() + { + } + + #[Route(path: '/specific-format', name: 'specific_format', format: 's_format')] + public function format() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/InvokableController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/InvokableController.php new file mode 100644 index 0000000000000..9a3f729622b2d --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/InvokableController.php @@ -0,0 +1,13 @@ + "/hier", "en" => "/here"], name: 'action')] +class InvokableLocalizedController +{ + public function __invoke() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedActionPathController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedActionPathController.php new file mode 100644 index 0000000000000..96f0a8e22af2f --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedActionPathController.php @@ -0,0 +1,13 @@ + '/path', 'nl' => '/pad'], name: 'action')] + public function action() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedMethodActionControllers.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedMethodActionControllers.php new file mode 100644 index 0000000000000..afc8f7f905117 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedMethodActionControllers.php @@ -0,0 +1,19 @@ + '/the/path', 'nl' => '/het/pad'])] +class LocalizedMethodActionControllers +{ + #[Route(name: 'post', methods: ['POST'])] + public function post() + { + } + + #[Route(name: 'put', methods: ['PUT'])] + public function put() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixLocalizedActionController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixLocalizedActionController.php new file mode 100644 index 0000000000000..af74fb4a5b66a --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixLocalizedActionController.php @@ -0,0 +1,14 @@ + '/nl', 'en' => '/en'])] +class LocalizedPrefixLocalizedActionController +{ + #[Route(path: ['nl' => '/actie', 'en' => '/action'], name: 'action')] + public function action() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixWithRouteWithoutLocale.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixWithRouteWithoutLocale.php new file mode 100644 index 0000000000000..6edda5b7e5822 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/LocalizedPrefixWithRouteWithoutLocale.php @@ -0,0 +1,14 @@ + '/en', 'nl' => '/nl'])] +class LocalizedPrefixWithRouteWithoutLocale +{ + #[Route(path: '/suffix', name: 'action')] + public function action() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/MethodActionControllers.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/MethodActionControllers.php new file mode 100644 index 0000000000000..2891de1351575 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/MethodActionControllers.php @@ -0,0 +1,19 @@ + '/path', 'nl' => '/pad'], name: 'action')] + public function action() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/PrefixedActionPathController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/PrefixedActionPathController.php new file mode 100644 index 0000000000000..934da3061f41b --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/PrefixedActionPathController.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures; + +use Symfony\Component\Routing\Annotation\Route; + +#[Route(path: '/', requirements: ['foo', '\d+'])] +class RequirementsWithoutPlaceholderNameController +{ + #[Route(path: '/{foo}', name: 'foo', requirements: ['foo', '\d+'])] + public function foo() + { + } +} diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithPrefixController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithPrefixController.php new file mode 100644 index 0000000000000..e859692a828a9 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithPrefixController.php @@ -0,0 +1,14 @@ +loader = new class($reader) extends AnnotationClassLoader { - protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot): void - { - } - }; - AnnotationRegistry::registerLoader('class_exists'); - } + protected $loader; /** * @dataProvider provideTestSupportsChecksResource @@ -85,7 +51,7 @@ public function testSupportsChecksTypeIfSpecified() public function testSimplePathRoute() { - $routes = $this->loader->load(ActionPathController::class); + $routes = $this->loader->load($this->getNamespace().'\ActionPathController'); $this->assertCount(1, $routes); $this->assertEquals('/path', $routes->get('action')->getPath()); } @@ -95,12 +61,12 @@ public function testRequirementsWithoutPlaceholderName() $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('A placeholder name must be a string (0 given). Did you forget to specify the placeholder key for the requirement "foo"'); - $this->loader->load(RequirementsWithoutPlaceholderNameController::class); + $this->loader->load($this->getNamespace().'\RequirementsWithoutPlaceholderNameController'); } public function testInvokableControllerLoader() { - $routes = $this->loader->load(InvokableController::class); + $routes = $this->loader->load($this->getNamespace().'\InvokableController'); $this->assertCount(1, $routes); $this->assertEquals('/here', $routes->get('lol')->getPath()); $this->assertEquals(['GET', 'POST'], $routes->get('lol')->getMethods()); @@ -109,7 +75,7 @@ public function testInvokableControllerLoader() public function testInvokableLocalizedControllerLoading() { - $routes = $this->loader->load(InvokableLocalizedController::class); + $routes = $this->loader->load($this->getNamespace().'\InvokableLocalizedController'); $this->assertCount(2, $routes); $this->assertEquals('/here', $routes->get('action.en')->getPath()); $this->assertEquals('/hier', $routes->get('action.nl')->getPath()); @@ -117,7 +83,7 @@ public function testInvokableLocalizedControllerLoading() public function testLocalizedPathRoutes() { - $routes = $this->loader->load(LocalizedActionPathController::class); + $routes = $this->loader->load($this->getNamespace().'\LocalizedActionPathController'); $this->assertCount(2, $routes); $this->assertEquals('/path', $routes->get('action.en')->getPath()); $this->assertEquals('/pad', $routes->get('action.nl')->getPath()); @@ -128,7 +94,7 @@ public function testLocalizedPathRoutes() public function testLocalizedPathRoutesWithExplicitPathPropety() { - $routes = $this->loader->load(ExplicitLocalizedActionPathController::class); + $routes = $this->loader->load($this->getNamespace().'\ExplicitLocalizedActionPathController'); $this->assertCount(2, $routes); $this->assertEquals('/path', $routes->get('action.en')->getPath()); $this->assertEquals('/pad', $routes->get('action.nl')->getPath()); @@ -136,7 +102,7 @@ public function testLocalizedPathRoutesWithExplicitPathPropety() public function testDefaultValuesForMethods() { - $routes = $this->loader->load(DefaultValueController::class); + $routes = $this->loader->load($this->getNamespace().'\DefaultValueController'); $this->assertCount(3, $routes); $this->assertEquals('/{default}/path', $routes->get('action')->getPath()); $this->assertEquals('value', $routes->get('action')->getDefault('default')); @@ -146,7 +112,7 @@ public function testDefaultValuesForMethods() public function testMethodActionControllers() { - $routes = $this->loader->load(MethodActionControllers::class); + $routes = $this->loader->load($this->getNamespace().'\MethodActionControllers'); $this->assertSame(['put', 'post'], array_keys($routes->all())); $this->assertEquals('/the/path', $routes->get('put')->getPath()); $this->assertEquals('/the/path', $routes->get('post')->getPath()); @@ -154,7 +120,7 @@ public function testMethodActionControllers() public function testInvokableClassRouteLoadWithMethodAnnotation() { - $routes = $this->loader->load(LocalizedMethodActionControllers::class); + $routes = $this->loader->load($this->getNamespace().'\LocalizedMethodActionControllers'); $this->assertCount(4, $routes); $this->assertEquals('/the/path', $routes->get('put.en')->getPath()); $this->assertEquals('/the/path', $routes->get('post.en')->getPath()); @@ -162,7 +128,7 @@ public function testInvokableClassRouteLoadWithMethodAnnotation() public function testGlobalDefaultsRoutesLoadWithAnnotation() { - $routes = $this->loader->load(GlobalDefaultsClass::class); + $routes = $this->loader->load($this->getNamespace().'\GlobalDefaultsClass'); $this->assertCount(2, $routes); $specificLocaleRoute = $routes->get('specific_locale'); @@ -180,7 +146,7 @@ public function testGlobalDefaultsRoutesLoadWithAnnotation() public function testUtf8RoutesLoadWithAnnotation() { - $routes = $this->loader->load(Utf8ActionControllers::class); + $routes = $this->loader->load($this->getNamespace().'\Utf8ActionControllers'); $this->assertSame(['one', 'two'], array_keys($routes->all())); $this->assertTrue($routes->get('one')->getOption('utf8'), 'The route must accept utf8'); $this->assertFalse($routes->get('two')->getOption('utf8'), 'The route must not accept utf8'); @@ -188,7 +154,7 @@ public function testUtf8RoutesLoadWithAnnotation() public function testRouteWithPathWithPrefix() { - $routes = $this->loader->load(PrefixedActionPathController::class); + $routes = $this->loader->load($this->getNamespace().'\PrefixedActionPathController'); $this->assertCount(1, $routes); $route = $routes->get('action'); $this->assertEquals('/prefix/path', $route->getPath()); @@ -198,7 +164,7 @@ public function testRouteWithPathWithPrefix() public function testLocalizedRouteWithPathWithPrefix() { - $routes = $this->loader->load(PrefixedActionLocalizedRouteController::class); + $routes = $this->loader->load($this->getNamespace().'\PrefixedActionLocalizedRouteController'); $this->assertCount(2, $routes); $this->assertEquals('/prefix/path', $routes->get('action.en')->getPath()); $this->assertEquals('/prefix/pad', $routes->get('action.nl')->getPath()); @@ -206,7 +172,7 @@ public function testLocalizedRouteWithPathWithPrefix() public function testLocalizedPrefixLocalizedRoute() { - $routes = $this->loader->load(LocalizedPrefixLocalizedActionController::class); + $routes = $this->loader->load($this->getNamespace().'\LocalizedPrefixLocalizedActionController'); $this->assertCount(2, $routes); $this->assertEquals('/nl/actie', $routes->get('action.nl')->getPath()); $this->assertEquals('/en/action', $routes->get('action.en')->getPath()); @@ -214,73 +180,42 @@ public function testLocalizedPrefixLocalizedRoute() public function testInvokableClassMultipleRouteLoad() { - $classRouteData1 = [ - 'name' => 'route1', - 'path' => '/1', - 'schemes' => ['https'], - 'methods' => ['GET'], - ]; + $routeCollection = $this->loader->load($this->getNamespace().'\BazClass'); + $route = $routeCollection->get('route1'); - $classRouteData2 = [ - 'name' => 'route2', - 'path' => '/2', - 'schemes' => ['https'], - 'methods' => ['GET'], - ]; + $this->assertSame('/1', $route->getPath(), '->load preserves class route path'); + $this->assertSame(['https'], $route->getSchemes(), '->load preserves class route schemes'); + $this->assertSame(['GET'], $route->getMethods(), '->load preserves class route methods'); + + $route = $routeCollection->get('route2'); - $reader = $this->getReader(); - $reader - ->expects($this->exactly(1)) - ->method('getClassAnnotations') - ->willReturn([new RouteAnnotation($classRouteData1), new RouteAnnotation($classRouteData2)]) - ; - $reader - ->expects($this->once()) - ->method('getMethodAnnotations') - ->willReturn([]) - ; - $loader = new class($reader) extends AnnotationClassLoader { - protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot): void - { - } - }; - - $routeCollection = $loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); - $route = $routeCollection->get($classRouteData1['name']); - - $this->assertSame($classRouteData1['path'], $route->getPath(), '->load preserves class route path'); - $this->assertEquals($classRouteData1['schemes'], $route->getSchemes(), '->load preserves class route schemes'); - $this->assertEquals($classRouteData1['methods'], $route->getMethods(), '->load preserves class route methods'); - - $route = $routeCollection->get($classRouteData2['name']); - - $this->assertSame($classRouteData2['path'], $route->getPath(), '->load preserves class route path'); - $this->assertEquals($classRouteData2['schemes'], $route->getSchemes(), '->load preserves class route schemes'); - $this->assertEquals($classRouteData2['methods'], $route->getMethods(), '->load preserves class route methods'); + $this->assertSame('/2', $route->getPath(), '->load preserves class route path'); + $this->assertEquals(['https'], $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals(['GET'], $route->getMethods(), '->load preserves class route methods'); } public function testMissingPrefixLocale() { $this->expectException(\LogicException::class); - $this->loader->load(LocalizedPrefixMissingLocaleActionController::class); + $this->loader->load($this->getNamespace().'\LocalizedPrefixMissingLocaleActionController'); } public function testMissingRouteLocale() { $this->expectException(\LogicException::class); - $this->loader->load(LocalizedPrefixMissingRouteLocaleActionController::class); + $this->loader->load($this->getNamespace().'\LocalizedPrefixMissingRouteLocaleActionController'); } public function testRouteWithoutName() { - $routes = $this->loader->load(MissingRouteNameController::class)->all(); + $routes = $this->loader->load($this->getNamespace().'\MissingRouteNameController')->all(); $this->assertCount(1, $routes); $this->assertEquals('/path', reset($routes)->getPath()); } public function testNothingButName() { - $routes = $this->loader->load(NothingButNameController::class)->all(); + $routes = $this->loader->load($this->getNamespace().'\NothingButNameController')->all(); $this->assertCount(1, $routes); $this->assertEquals('/', reset($routes)->getPath()); } @@ -299,44 +234,18 @@ public function testLoadingAbstractClass() public function testLocalizedPrefixWithoutRouteLocale() { - $routes = $this->loader->load(LocalizedPrefixWithRouteWithoutLocale::class); + $routes = $this->loader->load($this->getNamespace().'\LocalizedPrefixWithRouteWithoutLocale'); $this->assertCount(2, $routes); $this->assertEquals('/en/suffix', $routes->get('action.en')->getPath()); $this->assertEquals('/nl/suffix', $routes->get('action.nl')->getPath()); } - /** - * @requires function mb_strtolower - */ - public function testDefaultRouteName() - { - $methodRouteData = [ - 'name' => null, - ]; - - $reader = $this->getReader(); - $reader - ->expects($this->once()) - ->method('getMethodAnnotations') - ->willReturn([new RouteAnnotation($methodRouteData)]) - ; - - $loader = new class($reader) extends AnnotationClassLoader { - protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot): void - { - } - }; - $routeCollection = $loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\EncodingClass'); - - $defaultName = array_keys($routeCollection->all())[0]; - - $this->assertSame($defaultName, 'symfony_component_routing_tests_fixtures_annotatedclasses_encodingclass_routeàction'); - } - public function testLoadingRouteWithPrefix() { - $routes = $this->loader->load(RouteWithPrefixController::class); + $routes = $this->loader->load($this->getNamespace().'\RouteWithPrefixController'); $this->assertCount(1, $routes); $this->assertEquals('/prefix/path', $routes->get('action')->getPath()); } + + abstract protected function getNamespace(): string; } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php new file mode 100644 index 0000000000000..ef9ca39e827fe --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php @@ -0,0 +1,35 @@ +loader = new class($reader) extends AnnotationClassLoader { + protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void + { + } + }; + AnnotationRegistry::registerLoader('class_exists'); + } + + public function testDefaultRouteName() + { + $routeCollection = $this->loader->load($this->getNamespace().'\EncodingClass'); + $defaultName = array_keys($routeCollection->all())[0]; + + $this->assertSame('symfony_component_routing_tests_fixtures_annotationfixtures_encodingclass_routeàction', $defaultName); + } + + protected function getNamespace(): string + { + return 'Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures'; + } +} diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php new file mode 100644 index 0000000000000..1545253e56d96 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php @@ -0,0 +1,34 @@ +loader = new class() extends AnnotationClassLoader { + protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void + { + } + }; + } + + public function testDefaultRouteName() + { + $routeCollection = $this->loader->load($this->getNamespace().'\EncodingClass'); + $defaultName = array_keys($routeCollection->all())[0]; + + $this->assertSame('symfony_component_routing_tests_fixtures_attributefixtures_encodingclass_routeàction', $defaultName); + } + + protected function getNamespace(): string + { + return 'Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures'; + } +} diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 2ba2005775b7d..19a6bf220110a 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -26,7 +26,7 @@ "symfony/yaml": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", - "doctrine/annotations": "~1.2", + "doctrine/annotations": "^1.7", "psr/log": "~1.0" }, "conflict": {