From 1a8af5cd7cab114abe8791084f9b3d0499a798e9 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 20 Feb 2024 14:21:56 +0300 Subject: [PATCH] Add `RouteArgument` attribute for Yii Hydrator (#237) * Add `RouteArgument` attribute for Yii Hydrator (#203) (cherry picked from commit f3f1091369f3c452d9a735135e072b19716e8f16) * fix --- CHANGELOG.md | 1 + composer-require-checker.json | 9 ++ composer.json | 4 +- src/HydratorAttribute/RouteArgument.php | 27 +++++ .../RouteArgumentResolver.php | 41 +++++++ tests/HydratorAttribute/RouteArgumentTest.php | 100 ++++++++++++++++++ 6 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 composer-require-checker.json create mode 100644 src/HydratorAttribute/RouteArgument.php create mode 100644 src/HydratorAttribute/RouteArgumentResolver.php create mode 100644 tests/HydratorAttribute/RouteArgumentTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 855c426..0713450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.0.1 under development - Enh #202: Add support for `psr/http-message` version `^2.0` (@vjik) +- New #203, #237: Add `RouteArgument` attribute for Yii Hydrator (@vjik) ## 3.0.0 February 17, 2023 diff --git a/composer-require-checker.json b/composer-require-checker.json new file mode 100644 index 0000000..4b939b4 --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,9 @@ +{ + "symbol-whitelist" : [ + "Yiisoft\\Hydrator\\Attribute\\Parameter\\ParameterAttributeInterface", + "Yiisoft\\Hydrator\\Attribute\\Parameter\\ParameterAttributeResolverInterface", + "Yiisoft\\Hydrator\\AttributeHandling\\ParameterAttributeResolveContext", + "Yiisoft\\Hydrator\\AttributeHandling\\Exception\\UnexpectedAttributeException", + "Yiisoft\\Hydrator\\Result" + ] +} diff --git a/composer.json b/composer.json index f0072e1..f7dcaab 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,7 @@ "vimeo/psalm": "^4.30|^5.22", "yiisoft/di": "^1.0", "yiisoft/dummy-provider": "^1.0.0", + "yiisoft/hydrator": "^1.0", "yiisoft/test-support": "^3.0" }, "autoload": { @@ -52,7 +53,8 @@ } }, "suggest": { - "yiisoft/router-fastroute": "Router implementation based on nikic/FastRoute" + "yiisoft/router-fastroute": "Router implementation based on nikic/FastRoute", + "yiisoft/hydrator": "Needed to use `RouteArgument` attribute" }, "extra": { "config-plugin-options": { diff --git a/src/HydratorAttribute/RouteArgument.php b/src/HydratorAttribute/RouteArgument.php new file mode 100644 index 0000000..aca5947 --- /dev/null +++ b/src/HydratorAttribute/RouteArgument.php @@ -0,0 +1,27 @@ +name; + } + + public function getResolver(): string + { + return RouteArgumentResolver::class; + } +} diff --git a/src/HydratorAttribute/RouteArgumentResolver.php b/src/HydratorAttribute/RouteArgumentResolver.php new file mode 100644 index 0000000..2c8d0c0 --- /dev/null +++ b/src/HydratorAttribute/RouteArgumentResolver.php @@ -0,0 +1,41 @@ +currentRoute->getArguments(); + + $name = $attribute->getName() ?? $context->getParameter()->getName(); + + if (array_key_exists($name, $arguments)) { + return Result::success($arguments[$name]); + } + + return Result::fail(); + } +} diff --git a/tests/HydratorAttribute/RouteArgumentTest.php b/tests/HydratorAttribute/RouteArgumentTest.php new file mode 100644 index 0000000..eedf943 --- /dev/null +++ b/tests/HydratorAttribute/RouteArgumentTest.php @@ -0,0 +1,100 @@ +createHydrator([ + 'a' => 'one', + 'b' => 'two', + 'c' => 'three', + ]); + + $input = new class () { + #[RouteArgument('a')] + public string $a = ''; + #[RouteArgument('b')] + public string $b = ''; + #[RouteArgument] + public string $c = ''; + }; + + $hydrator->hydrate($input); + + $this->assertSame('one', $input->a); + $this->assertSame('two', $input->b); + $this->assertSame('three', $input->c); + } + + public function testWithoutArguments(): void + { + $hydrator = $this->createHydrator([]); + + $input = new class () { + #[RouteArgument('a')] + public string $a = ''; + #[RouteArgument('b')] + public string $b = ''; + #[RouteArgument] + public string $c = ''; + }; + + $hydrator->hydrate($input); + + $this->assertSame('', $input->a); + $this->assertSame('', $input->b); + $this->assertSame('', $input->c); + } + + public function testUnexpectedAttributeException(): void + { + $resolver = new RouteArgumentResolver(new CurrentRoute()); + + $attribute = new ToString(); + $context = $this->createParameterAttributeResolveContext(); + + $this->expectException(UnexpectedAttributeException::class); + $this->expectExceptionMessage('Expected "' . RouteArgument::class . '", but "' . ToString::class . '" given.'); + $resolver->getParameterValue($attribute, $context); + } + + private function createHydrator(array $arguments): Hydrator + { + $currentRoute = new CurrentRoute(); + $currentRoute->setRouteWithArguments(RouterRoute::get('/'), $arguments); + + return new Hydrator( + attributeResolverFactory: new ContainerAttributeResolverFactory( + new SimpleContainer([ + RouteArgumentResolver::class => new RouteArgumentResolver($currentRoute), + ]) + ), + ); + } + + private function createParameterAttributeResolveContext(): ParameterAttributeResolveContext + { + $reflection = new ReflectionFunction(static fn (int $a) => null); + + return new ParameterAttributeResolveContext($reflection->getParameters()[0], Result::fail(), new ArrayData()); + } +}