From 9e72826641c82a2cb9e1a603c72cafb510e46093 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 18 Feb 2024 01:29:30 +0700 Subject: [PATCH 01/15] Test middleware attribute concept --- src/Middleware.php | 15 +++++++++++ src/MiddlewareFactory.php | 37 +++++++++++++++++++++++++--- tests/MiddlewareFactoryTest.php | 18 ++++++++++++++ tests/Support/ResponseMiddleware.php | 23 +++++++++++++++++ tests/Support/TestController.php | 10 ++++++++ 5 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/Middleware.php create mode 100644 tests/Support/ResponseMiddleware.php diff --git a/src/Middleware.php b/src/Middleware.php new file mode 100644 index 0000000..08740b9 --- /dev/null +++ b/src/Middleware.php @@ -0,0 +1,15 @@ +container, $this->parametersResolver, $class, $method) implements MiddlewareInterface { + return new class ( + $this->container, + $this, + null, + $this->parametersResolver, + $class, + $method, + ) implements MiddlewareInterface { /** * @var ReflectionParameter[] * @psalm-var array */ private array $actionParameters = []; + private array $middlewares = []; + public function __construct( private ContainerInterface $container, + private MiddlewareFactory $middlewareFactory, + private ?EventDispatcherInterface $eventDispatcher, private ?ParametersResolverInterface $parametersResolver, /** @var class-string */ private string $class, /** @var non-empty-string */ private string $method ) { - $actionParameters = (new ReflectionClass($this->class))->getMethod($this->method)->getParameters(); + $reflectionMethod = (new ReflectionClass($this->class))->getMethod($this->method); + $middlewareAttributes = $reflectionMethod->getAttributes(Middleware::class); + + $this->middlewares = array_map( + static fn (ReflectionAttribute $attribute) => $attribute->newInstance()->definition, + $middlewareAttributes + ); + $actionParameters = $reflectionMethod->getParameters(); foreach ($actionParameters as $parameter) { $this->actionParameters[$parameter->getName()] = $parameter; } @@ -267,8 +289,15 @@ public function process( ); } - /** @var mixed|ResponseInterface $response */ - $response = (new Injector($this->container))->invoke([$controller, $this->method], $parameters); + if ($this->middlewares !== []) { + $this->middlewares[] = [$controller, $this->method]; + $middlewareDispatcher = new MiddlewareDispatcher($this->middlewareFactory, $this->eventDispatcher); + $middlewareDispatcher = $middlewareDispatcher->withMiddlewares($this->middlewares); + $response = $middlewareDispatcher->dispatch($request, $handler); + } else { + /** @var mixed|ResponseInterface $response */ + $response = (new Injector($this->container))->invoke([$controller, $this->method], $parameters); + } if ($response instanceof ResponseInterface) { return $response; } diff --git a/tests/MiddlewareFactoryTest.php b/tests/MiddlewareFactoryTest.php index dd8515a..20979f4 100644 --- a/tests/MiddlewareFactoryTest.php +++ b/tests/MiddlewareFactoryTest.php @@ -323,6 +323,23 @@ public function testInvalidMiddlewareWithWrongArrayWithIntItems(): void ->create([7, 42]); } + + public function testControllerMiddleware(): void + { + $container = $this->getContainer([TestController::class => new TestController()]); + $middleware = $this + ->getMiddlewareFactory($container, new SimpleParametersResolver()) + ->create([TestController::class, 'error']); + $response = $middleware->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + self::assertSame( + 200, + $response->getStatusCode() + ); + } + private function getMiddlewareFactory( ContainerInterface $container = null, ParametersResolverInterface $parametersResolver = null @@ -348,4 +365,5 @@ public function handle(ServerRequestInterface $request): ResponseInterface } }; } + } diff --git a/tests/Support/ResponseMiddleware.php b/tests/Support/ResponseMiddleware.php new file mode 100644 index 0000000..5e694d3 --- /dev/null +++ b/tests/Support/ResponseMiddleware.php @@ -0,0 +1,23 @@ +code); + } +} diff --git a/tests/Support/TestController.php b/tests/Support/TestController.php index 371b446..685a359 100644 --- a/tests/Support/TestController.php +++ b/tests/Support/TestController.php @@ -6,6 +6,7 @@ use Nyholm\Psr7\Response; use Psr\Http\Message\ResponseInterface; +use Yiisoft\Middleware\Dispatcher\Middleware; final class TestController { @@ -25,4 +26,13 @@ public function compositeResolver(int $a = 0, int $b = 0, int $c = 0, int $d = 0 reason: $a . '-' . $b . '-' . $c . '-' . $d, ); } + + #[Middleware([ + 'class' => ResponseMiddleware::class, + '__construct()' => [200], + ])] + public function error(): ResponseInterface + { + return new Response(404); + } } From 30c63c85888b8ef9b942038e351a8adfe7baee1a Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 18 Feb 2024 01:30:36 +0700 Subject: [PATCH 02/15] Move attribute --- src/Attribute/Middleware.php | 17 +++++++++++++++++ src/Middleware.php | 15 --------------- src/MiddlewareFactory.php | 2 +- tests/Support/TestController.php | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 src/Attribute/Middleware.php delete mode 100644 src/Middleware.php diff --git a/src/Attribute/Middleware.php b/src/Attribute/Middleware.php new file mode 100644 index 0000000..645f82c --- /dev/null +++ b/src/Attribute/Middleware.php @@ -0,0 +1,17 @@ + Date: Sun, 18 Feb 2024 02:13:02 +0700 Subject: [PATCH 03/15] Support middleware for both callable and callable-like arrays --- src/MiddlewareDispatcher.php | 1 + src/MiddlewareFactory.php | 131 ++++++++--------------- tests/MiddlewareFactoryTest.php | 146 +++++++++++++++----------- tests/Support/SetHeaderMiddleware.php | 29 +++++ tests/Support/TestController.php | 17 +++ 5 files changed, 176 insertions(+), 148 deletions(-) create mode 100644 tests/Support/SetHeaderMiddleware.php diff --git a/src/MiddlewareDispatcher.php b/src/MiddlewareDispatcher.php index 1ac762b..a969e17 100644 --- a/src/MiddlewareDispatcher.php +++ b/src/MiddlewareDispatcher.php @@ -29,6 +29,7 @@ public function __construct( private MiddlewareFactory $middlewareFactory, private ?EventDispatcherInterface $eventDispatcher = null ) { + $this->middlewareFactory->withEventDispatcher($eventDispatcher); } /** diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index f2e77d4..d6dee1c 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -33,6 +33,8 @@ */ final class MiddlewareFactory { + private ?EventDispatcherInterface $eventDispatcher = null; + /** * @param ContainerInterface $container Container to use for resolving definitions. */ @@ -42,6 +44,13 @@ public function __construct( ) { } + public function withEventDispatcher(?EventDispatcherInterface $eventDispatcher): self + { + $new = clone $this; + $new->eventDispatcher = $eventDispatcher; + return $new; + } + /** * @param array|callable|string $middlewareDefinition Middleware definition in one of the following formats: * @@ -171,12 +180,18 @@ private function wrapCallable(array|callable $callable): MiddlewareInterface return $this->createCallableWrapper($callable); } - return $this->createActionWrapper($callable[0], $callable[1]); + return $this->createCallableWrapper([$this->container->get($callable[0]), $callable[1]]); } private function createCallableWrapper(callable $callback): MiddlewareInterface { - return new class ($callback, $this->container, $this->parametersResolver) implements MiddlewareInterface { + return new class ( + $callback, + $this->container, + $this, + $this->eventDispatcher, + $this->parametersResolver, + ) implements MiddlewareInterface { /** @var callable */ private $callback; /** @@ -184,16 +199,25 @@ private function createCallableWrapper(callable $callback): MiddlewareInterface * @psalm-var array */ private array $callableParameters = []; + public array $middlewares; public function __construct( callable $callback, private ContainerInterface $container, + private MiddlewareFactory $middlewareFactory, + private ?EventDispatcherInterface $eventDispatcher, private ?ParametersResolverInterface $parametersResolver ) { $this->callback = $callback; $callback = Closure::fromCallable($callback); - $callableParameters = (new ReflectionFunction($callback))->getParameters(); + $reflectionFunction = new ReflectionFunction($callback); + + $this->middlewares = array_map( + static fn (ReflectionAttribute $attribute) => $attribute->newInstance()->definition, + $reflectionFunction->getAttributes(Middleware::class) + ); + $callableParameters = $reflectionFunction->getParameters(); foreach ($callableParameters as $parameter) { $this->callableParameters[$parameter->getName()] = $parameter; } @@ -211,8 +235,15 @@ public function process( ); } - /** @var MiddlewareInterface|mixed|ResponseInterface $response */ - $response = (new Injector($this->container))->invoke($this->callback, $parameters); + if ($this->middlewares !== []) { + $middlewares = [...$this->middlewares, fn() => ($this->callback)()]; + $middlewareDispatcher = new MiddlewareDispatcher($this->middlewareFactory, $this->eventDispatcher); + $middlewareDispatcher = $middlewareDispatcher->withMiddlewares($middlewares); + $response = $middlewareDispatcher->dispatch($request, $handler); + } else { + /** @var MiddlewareInterface|mixed|ResponseInterface $response */ + $response = (new Injector($this->container))->invoke($this->callback, $parameters); + } if ($response instanceof ResponseInterface) { return $response; } @@ -225,92 +256,16 @@ public function process( public function __debugInfo(): array { + if (is_array($this->callback) + && isset($this->callback[0], $this->callback[1]) + && is_object($this->callback[0]) + && is_string($this->callback[1]) + ){ + return ['callback' => [$this->callback[0]::class, $this->callback[1]]]; + } return ['callback' => $this->callback]; } }; } - /** - * @param class-string $class - * @param non-empty-string $method - */ - private function createActionWrapper(string $class, string $method): MiddlewareInterface - { - return new class ( - $this->container, - $this, - null, - $this->parametersResolver, - $class, - $method, - ) implements MiddlewareInterface { - /** - * @var ReflectionParameter[] - * @psalm-var array - */ - private array $actionParameters = []; - - private array $middlewares = []; - - public function __construct( - private ContainerInterface $container, - private MiddlewareFactory $middlewareFactory, - private ?EventDispatcherInterface $eventDispatcher, - private ?ParametersResolverInterface $parametersResolver, - /** @var class-string */ - private string $class, - /** @var non-empty-string */ - private string $method - ) { - $reflectionMethod = (new ReflectionClass($this->class))->getMethod($this->method); - $middlewareAttributes = $reflectionMethod->getAttributes(Middleware::class); - - $this->middlewares = array_map( - static fn (ReflectionAttribute $attribute) => $attribute->newInstance()->definition, - $middlewareAttributes - ); - $actionParameters = $reflectionMethod->getParameters(); - foreach ($actionParameters as $parameter) { - $this->actionParameters[$parameter->getName()] = $parameter; - } - } - - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler - ): ResponseInterface { - /** @var mixed $controller */ - $controller = $this->container->get($this->class); - $parameters = [$request, $handler]; - if ($this->parametersResolver !== null) { - $parameters = array_merge( - $parameters, - $this->parametersResolver->resolve($this->actionParameters, $request) - ); - } - - if ($this->middlewares !== []) { - $this->middlewares[] = [$controller, $this->method]; - $middlewareDispatcher = new MiddlewareDispatcher($this->middlewareFactory, $this->eventDispatcher); - $middlewareDispatcher = $middlewareDispatcher->withMiddlewares($this->middlewares); - $response = $middlewareDispatcher->dispatch($request, $handler); - } else { - /** @var mixed|ResponseInterface $response */ - $response = (new Injector($this->container))->invoke([$controller, $this->method], $parameters); - } - if ($response instanceof ResponseInterface) { - return $response; - } - - throw new InvalidMiddlewareDefinitionException([$this->class, $this->method]); - } - - public function __debugInfo() - { - return [ - 'callback' => [$this->class, $this->method], - ]; - } - }; - } } diff --git a/tests/MiddlewareFactoryTest.php b/tests/MiddlewareFactoryTest.php index 20979f4..704baec 100644 --- a/tests/MiddlewareFactoryTest.php +++ b/tests/MiddlewareFactoryTest.php @@ -57,13 +57,14 @@ public function testCreateFromArray(): void $middleware = $this ->getMiddlewareFactory($container) ->create([TestController::class, 'index']); - self::assertSame( - 'yii', - $middleware + $response = $middleware ->process( $this->createMock(ServerRequestInterface::class), $this->createMock(RequestHandlerInterface::class) - ) + ); + self::assertSame( + 'yii', + $response ->getHeaderLine('test') ); self::assertSame( @@ -78,13 +79,14 @@ public function testCreateFromArrayWithResolver(): void $middleware = $this ->getMiddlewareFactory($container, new SimpleParametersResolver()) ->create([TestController::class, 'indexWithParams']); - self::assertSame( - 'yii', - $middleware + $response = $middleware ->process( $this->createMock(ServerRequestInterface::class), $this->createMock(RequestHandlerInterface::class) - ) + ); + self::assertSame( + 'yii', + $response ->getHeaderLine('test') ); } @@ -97,15 +99,14 @@ public function testCreateFromClosureResponse(): void ->create( static fn(): ResponseInterface => (new Response())->withStatus(418) ); - self::assertSame( - 418, - $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ) - ->getStatusCode() - ); + + $response = $middleware + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame(418, $response->getStatusCode()); } public function testCreateFromClosureWithResolver(): void @@ -116,15 +117,13 @@ public function testCreateFromClosureWithResolver(): void ->create( static fn(string $test = ''): ResponseInterface => (new Response())->withStatus(418, $test) ); - self::assertSame( - 'yii', - $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ) - ->getReasonPhrase() - ); + $response = $middleware + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame('yii', $response->getReasonPhrase()); } public function testCreateCallableFromArrayWithInstance(): void @@ -134,19 +133,15 @@ public function testCreateCallableFromArrayWithInstance(): void $middleware = $this ->getMiddlewareFactory($container) ->create([$controller, 'index']); - self::assertSame( - 'yii', - $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ) - ->getHeaderLine('test') - ); - self::assertSame( - [$controller, 'index'], - $middleware->__debugInfo()['callback'] - ); + + $response = $middleware + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame('yii', $response->getHeaderLine('test')); + self::assertSame([TestController::class, 'index'], $middleware->__debugInfo()['callback']); } public function testCreateCallableObject(): void @@ -155,15 +150,14 @@ public function testCreateCallableObject(): void $middleware = $this ->getMiddlewareFactory($container) ->create(new InvokeableAction()); - self::assertSame( - 'yii', - $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ) - ->getHeaderLine('test') - ); + + $response = $middleware + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame('yii', $response->getHeaderLine('test')); } public function testCreateFromClosureMiddleware(): void @@ -174,13 +168,14 @@ public function testCreateFromClosureMiddleware(): void ->create( static fn(): MiddlewareInterface => new TestMiddleware() ); - self::assertSame( - '42', - $middleware + $response = $middleware ->process( $this->createMock(ServerRequestInterface::class), $this->createMock(RequestHandlerInterface::class) - ) + ); + self::assertSame( + '42', + $response ->getHeaderLine('test') ); } @@ -233,13 +228,14 @@ public function testCreateWithArrayDefinition(): void ]); self::assertInstanceOf(TestMiddleware::class, $middleware); - self::assertSame( - '7', - $middleware + $response = $middleware ->process( $this->createMock(ServerRequestInterface::class), $this->createMock(RequestHandlerInterface::class) - ) + ); + self::assertSame( + '7', + $response ->getHeaderLine('test') ); } @@ -323,7 +319,9 @@ public function testInvalidMiddlewareWithWrongArrayWithIntItems(): void ->create([7, 42]); } - + /** + * @dataProvider dataControllerMiddlewares + */ public function testControllerMiddleware(): void { $container = $this->getContainer([TestController::class => new TestController()]); @@ -331,15 +329,43 @@ public function testControllerMiddleware(): void ->getMiddlewareFactory($container, new SimpleParametersResolver()) ->create([TestController::class, 'error']); $response = $middleware->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ); + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + self::assertSame( 200, $response->getStatusCode() ); } + public static function dataControllerMiddlewares(): iterable + { + yield 'controller class + action name' => [ + [TestController::class, 'error'], + ]; + yield 'controller object + action name (callable)' => [ + [new TestController(), 'error1'], + ]; + } + + public function testMultipleControllerMiddlewares(): void + { + $container = $this->getContainer([TestController::class => new TestController()]); + $middleware = $this + ->getMiddlewareFactory($container, new SimpleParametersResolver()) + ->create([TestController::class, 'severalMiddlewares']); + $response = $middleware->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame(404, $response->getStatusCode()); + self::assertSame('yii1', $response->getHeaderLine('x-test1')); + self::assertSame('yii2', $response->getHeaderLine('x-test2')); + self::assertSame('yii3', $response->getHeaderLine('x-test3')); + } + private function getMiddlewareFactory( ContainerInterface $container = null, ParametersResolverInterface $parametersResolver = null diff --git a/tests/Support/SetHeaderMiddleware.php b/tests/Support/SetHeaderMiddleware.php new file mode 100644 index 0000000..1f066b4 --- /dev/null +++ b/tests/Support/SetHeaderMiddleware.php @@ -0,0 +1,29 @@ +handle($request); + + return $response->withHeader($this->header, $this->value); + } +} diff --git a/tests/Support/TestController.php b/tests/Support/TestController.php index 651241a..6024228 100644 --- a/tests/Support/TestController.php +++ b/tests/Support/TestController.php @@ -35,4 +35,21 @@ public function error(): ResponseInterface { return new Response(404); } + + #[Middleware([ + 'class' => SetHeaderMiddleware::class, + '__construct()' => ['x-test1', 'yii1'], + ])] + #[Middleware([ + 'class' => SetHeaderMiddleware::class, + '__construct()' => ['x-test2', 'yii2'], + ])] + #[Middleware([ + 'class' => SetHeaderMiddleware::class, + '__construct()' => ['x-test3', 'yii3'], + ])] + public function severalMiddlewares(): ResponseInterface + { + return new Response(404); + } } From 61d5ab48932daa8ad94e90f2cc5329315bd453b1 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 17 Feb 2024 19:13:15 +0000 Subject: [PATCH 04/15] Apply fixes from StyleCI --- src/Attribute/Middleware.php | 1 - src/MiddlewareFactory.php | 5 +---- tests/MiddlewareFactoryTest.php | 1 - tests/Support/SetHeaderMiddleware.php | 5 +---- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Attribute/Middleware.php b/src/Attribute/Middleware.php index 645f82c..346edb8 100644 --- a/src/Attribute/Middleware.php +++ b/src/Attribute/Middleware.php @@ -13,5 +13,4 @@ public function __construct( public $definition ) { } - } diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index d6dee1c..f2681bf 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -5,7 +5,6 @@ namespace Yiisoft\Middleware\Dispatcher; use Closure; -use PhpBench\Reflection\ReflectionMethod; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; @@ -13,7 +12,6 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use ReflectionAttribute; -use ReflectionClass; use ReflectionFunction; use ReflectionParameter; use Yiisoft\Definitions\ArrayDefinition; @@ -260,12 +258,11 @@ public function __debugInfo(): array && isset($this->callback[0], $this->callback[1]) && is_object($this->callback[0]) && is_string($this->callback[1]) - ){ + ) { return ['callback' => [$this->callback[0]::class, $this->callback[1]]]; } return ['callback' => $this->callback]; } }; } - } diff --git a/tests/MiddlewareFactoryTest.php b/tests/MiddlewareFactoryTest.php index 704baec..b84dc39 100644 --- a/tests/MiddlewareFactoryTest.php +++ b/tests/MiddlewareFactoryTest.php @@ -391,5 +391,4 @@ public function handle(ServerRequestInterface $request): ResponseInterface } }; } - } diff --git a/tests/Support/SetHeaderMiddleware.php b/tests/Support/SetHeaderMiddleware.php index 1f066b4..a2c12f1 100644 --- a/tests/Support/SetHeaderMiddleware.php +++ b/tests/Support/SetHeaderMiddleware.php @@ -4,20 +4,17 @@ namespace Yiisoft\Middleware\Dispatcher\Tests\Support; -use Nyholm\Psr7\Response; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\Middleware\Dispatcher\Attribute\Middleware; final class SetHeaderMiddleware implements MiddlewareInterface { public function __construct( private string $header, private string $value, - ) - { + ) { } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface From db814185b202b767ffd07816b8afa16d43025bd5 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 18 Feb 2024 13:07:33 +0700 Subject: [PATCH 05/15] Use getter instead of public property --- src/Attribute/Middleware.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Attribute/Middleware.php b/src/Attribute/Middleware.php index 346edb8..b9b7bc1 100644 --- a/src/Attribute/Middleware.php +++ b/src/Attribute/Middleware.php @@ -9,8 +9,16 @@ #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] final class Middleware { + private mixed $definition; + public function __construct( - public $definition + array|callable|string $definition ) { + $this->definition = $definition; + } + + public function getDefinition(): mixed + { + return $this->definition; } } From 267bd7d662d47d1f9065ca6ff9408e8e8161e4fe Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 18 Feb 2024 13:07:42 +0700 Subject: [PATCH 06/15] Replace map with for --- src/MiddlewareFactory.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index f2681bf..1b85096 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -197,7 +197,7 @@ private function createCallableWrapper(callable $callback): MiddlewareInterface * @psalm-var array */ private array $callableParameters = []; - public array $middlewares; + public array $middlewares = []; public function __construct( callable $callback, @@ -207,16 +207,13 @@ public function __construct( private ?ParametersResolverInterface $parametersResolver ) { $this->callback = $callback; - $callback = Closure::fromCallable($callback); - $reflectionFunction = new ReflectionFunction($callback); + $reflectionFunction = new ReflectionFunction(Closure::fromCallable($callback)); - $this->middlewares = array_map( - static fn (ReflectionAttribute $attribute) => $attribute->newInstance()->definition, - $reflectionFunction->getAttributes(Middleware::class) - ); - $callableParameters = $reflectionFunction->getParameters(); - foreach ($callableParameters as $parameter) { + foreach ($reflectionFunction->getAttributes(Middleware::class) as $attribute) { + $this->middlewares[] = $attribute->newInstance()->getDefinition(); + } + foreach ($reflectionFunction->getParameters() as $parameter) { $this->callableParameters[$parameter->getName()] = $parameter; } } From a2bb149a420a5233a16fb9969268815b3ea26186 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 18 Feb 2024 06:07:57 +0000 Subject: [PATCH 07/15] Apply fixes from StyleCI --- src/MiddlewareFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index 1b85096..ef75078 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -11,7 +11,6 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use ReflectionAttribute; use ReflectionFunction; use ReflectionParameter; use Yiisoft\Definitions\ArrayDefinition; From 71b26a1f82ef126a06918e014af8ebedb5531bb5 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 18 Feb 2024 14:23:07 +0700 Subject: [PATCH 08/15] Check property before assign --- src/MiddlewareDispatcher.php | 4 +++- src/MiddlewareFactory.php | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/MiddlewareDispatcher.php b/src/MiddlewareDispatcher.php index a969e17..b77c1a3 100644 --- a/src/MiddlewareDispatcher.php +++ b/src/MiddlewareDispatcher.php @@ -29,7 +29,9 @@ public function __construct( private MiddlewareFactory $middlewareFactory, private ?EventDispatcherInterface $eventDispatcher = null ) { - $this->middlewareFactory->withEventDispatcher($eventDispatcher); + if ($eventDispatcher !== null && !$middlewareFactory->hasEventDispatcher()) { + $this->middlewareFactory = $this->middlewareFactory->withEventDispatcher($eventDispatcher); + } } /** diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index ef75078..679a7d6 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -48,6 +48,11 @@ public function withEventDispatcher(?EventDispatcherInterface $eventDispatcher): return $new; } + public function hasEventDispatcher(): bool + { + return $this->eventDispatcher !== null; + } + /** * @param array|callable|string $middlewareDefinition Middleware definition in one of the following formats: * From 8acb333cfc44ddfa4cfde5c811b9a16f936bb431 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 22 Feb 2024 02:55:34 +0700 Subject: [PATCH 09/15] Fix psalm --- src/Attribute/Middleware.php | 2 +- src/MiddlewareFactory.php | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Attribute/Middleware.php b/src/Attribute/Middleware.php index b9b7bc1..c089585 100644 --- a/src/Attribute/Middleware.php +++ b/src/Attribute/Middleware.php @@ -6,7 +6,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +#[Attribute(Attribute::TARGET_FUNCTION |Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] final class Middleware { private mixed $definition; diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index 679a7d6..a8431dc 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -194,7 +194,7 @@ private function createCallableWrapper(callable $callback): MiddlewareInterface $this->eventDispatcher, $this->parametersResolver, ) implements MiddlewareInterface { - /** @var callable */ + /** @var callable():mixed */ private $callback; /** * @var ReflectionParameter[] @@ -235,7 +235,7 @@ public function process( } if ($this->middlewares !== []) { - $middlewares = [...$this->middlewares, fn() => ($this->callback)()]; + $middlewares = [...$this->middlewares, fn(): mixed => ($this->callback)()]; $middlewareDispatcher = new MiddlewareDispatcher($this->middlewareFactory, $this->eventDispatcher); $middlewareDispatcher = $middlewareDispatcher->withMiddlewares($middlewares); $response = $middlewareDispatcher->dispatch($request, $handler); @@ -258,7 +258,6 @@ public function __debugInfo(): array if (is_array($this->callback) && isset($this->callback[0], $this->callback[1]) && is_object($this->callback[0]) - && is_string($this->callback[1]) ) { return ['callback' => [$this->callback[0]::class, $this->callback[1]]]; } From 3801492926d8c1561a6fed8a256bd556d90b6e72 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 22 Feb 2024 02:57:32 +0700 Subject: [PATCH 10/15] Fix psalm --- src/MiddlewareFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index a8431dc..100b32c 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -194,7 +194,7 @@ private function createCallableWrapper(callable $callback): MiddlewareInterface $this->eventDispatcher, $this->parametersResolver, ) implements MiddlewareInterface { - /** @var callable():mixed */ + /** @var callable */ private $callback; /** * @var ReflectionParameter[] @@ -237,6 +237,7 @@ public function process( if ($this->middlewares !== []) { $middlewares = [...$this->middlewares, fn(): mixed => ($this->callback)()]; $middlewareDispatcher = new MiddlewareDispatcher($this->middlewareFactory, $this->eventDispatcher); + /** @psalm-suppress MixedArgumentTypeCoercion */ $middlewareDispatcher = $middlewareDispatcher->withMiddlewares($middlewares); $response = $middlewareDispatcher->dispatch($request, $handler); } else { From 011c67d27adf8c10b7a60c6396a0ef976cf14725 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 21 Feb 2024 19:57:43 +0000 Subject: [PATCH 11/15] Apply fixes from StyleCI --- src/Attribute/Middleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Attribute/Middleware.php b/src/Attribute/Middleware.php index c089585..3907835 100644 --- a/src/Attribute/Middleware.php +++ b/src/Attribute/Middleware.php @@ -6,7 +6,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_FUNCTION |Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] final class Middleware { private mixed $definition; From 0e82a61d7d86b0d49a3f4611e62ea87f29fa9b78 Mon Sep 17 00:00:00 2001 From: xepozz Date: Wed, 21 Feb 2024 19:58:24 +0000 Subject: [PATCH 12/15] Apply Rector changes (CI) --- src/Attribute/Middleware.php | 2 +- src/MiddlewareFactory.php | 8 ++++---- tests/Support/ResponseMiddleware.php | 2 +- tests/Support/SetHeaderMiddleware.php | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Attribute/Middleware.php b/src/Attribute/Middleware.php index 3907835..479ec14 100644 --- a/src/Attribute/Middleware.php +++ b/src/Attribute/Middleware.php @@ -9,7 +9,7 @@ #[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] final class Middleware { - private mixed $definition; + private readonly mixed $definition; public function __construct( array|callable|string $definition diff --git a/src/MiddlewareFactory.php b/src/MiddlewareFactory.php index 100b32c..c01ea4f 100644 --- a/src/MiddlewareFactory.php +++ b/src/MiddlewareFactory.php @@ -205,10 +205,10 @@ private function createCallableWrapper(callable $callback): MiddlewareInterface public function __construct( callable $callback, - private ContainerInterface $container, - private MiddlewareFactory $middlewareFactory, - private ?EventDispatcherInterface $eventDispatcher, - private ?ParametersResolverInterface $parametersResolver + private readonly ContainerInterface $container, + private readonly MiddlewareFactory $middlewareFactory, + private readonly ?EventDispatcherInterface $eventDispatcher, + private readonly ?ParametersResolverInterface $parametersResolver ) { $this->callback = $callback; diff --git a/tests/Support/ResponseMiddleware.php b/tests/Support/ResponseMiddleware.php index 5e694d3..b0b96f2 100644 --- a/tests/Support/ResponseMiddleware.php +++ b/tests/Support/ResponseMiddleware.php @@ -12,7 +12,7 @@ final class ResponseMiddleware implements MiddlewareInterface { - public function __construct(private int $code) + public function __construct(private readonly int $code) { } diff --git a/tests/Support/SetHeaderMiddleware.php b/tests/Support/SetHeaderMiddleware.php index a2c12f1..29abf52 100644 --- a/tests/Support/SetHeaderMiddleware.php +++ b/tests/Support/SetHeaderMiddleware.php @@ -12,8 +12,8 @@ final class SetHeaderMiddleware implements MiddlewareInterface { public function __construct( - private string $header, - private string $value, + private readonly string $header, + private readonly string $value, ) { } From 76541a8ff7ae15d8ab772e65315384d6290a87c2 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 22 Feb 2024 03:03:33 +0700 Subject: [PATCH 13/15] Cover line --- tests/MiddlewareFactoryTest.php | 37 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/tests/MiddlewareFactoryTest.php b/tests/MiddlewareFactoryTest.php index b84dc39..136b6c0 100644 --- a/tests/MiddlewareFactoryTest.php +++ b/tests/MiddlewareFactoryTest.php @@ -51,24 +51,43 @@ public function testCreateFromRequestHandler(): void self::assertInstanceOf(MiddlewareInterface::class, $middleware); } - public function testCreateFromArray(): void + public function testDebugInfoFromArrayCallable(): void { $container = $this->getContainer([TestController::class => new TestController()]); $middleware = $this ->getMiddlewareFactory($container) ->create([TestController::class, 'index']); + $response = $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ); + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame('yii', $response->getHeaderLine('test')); self::assertSame( - 'yii', - $response - ->getHeaderLine('test') + [TestController::class, 'index'], + $middleware->__debugInfo()['callback'] ); + } + + public function testDebugInfoFromClosure(): void + { + $container = $this->getContainer([TestController::class => new TestController()]); + $middlewareDefinition = fn() => new Response(headers: ['test' => 'yii']); + $middleware = $this + ->getMiddlewareFactory($container) + ->create($middlewareDefinition); + + $response = $middleware + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + + self::assertSame('yii', $response->getHeaderLine('test')); self::assertSame( - [TestController::class, 'index'], + $middlewareDefinition, $middleware->__debugInfo()['callback'] ); } From ffe8c2dd8b71ed48de4f933a2fb9b7af9fa469e5 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 22 Feb 2024 03:06:58 +0700 Subject: [PATCH 14/15] Format code --- tests/MiddlewareFactoryTest.php | 42 ++++++++++++--------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/tests/MiddlewareFactoryTest.php b/tests/MiddlewareFactoryTest.php index 136b6c0..ba64b68 100644 --- a/tests/MiddlewareFactoryTest.php +++ b/tests/MiddlewareFactoryTest.php @@ -99,15 +99,11 @@ public function testCreateFromArrayWithResolver(): void ->getMiddlewareFactory($container, new SimpleParametersResolver()) ->create([TestController::class, 'indexWithParams']); $response = $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ); - self::assertSame( - 'yii', - $response - ->getHeaderLine('test') - ); + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + self::assertSame('yii', $response->getHeaderLine('test')); } public function testCreateFromClosureResponse(): void @@ -188,15 +184,11 @@ public function testCreateFromClosureMiddleware(): void static fn(): MiddlewareInterface => new TestMiddleware() ); $response = $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ); - self::assertSame( - '42', - $response - ->getHeaderLine('test') - ); + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + self::assertSame('42', $response->getHeaderLine('test')); } public function testCreateWithUseParamsMiddleware(): void @@ -248,15 +240,11 @@ public function testCreateWithArrayDefinition(): void self::assertInstanceOf(TestMiddleware::class, $middleware); $response = $middleware - ->process( - $this->createMock(ServerRequestInterface::class), - $this->createMock(RequestHandlerInterface::class) - ); - self::assertSame( - '7', - $response - ->getHeaderLine('test') - ); + ->process( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + self::assertSame('7', $response->getHeaderLine('test')); } public function testInvalidMiddlewareWithWrongCallable(): void From 89a00da5386b8a9e2d6298cbe70b4c4a0b70fc8e Mon Sep 17 00:00:00 2001 From: xepozz Date: Mon, 14 Oct 2024 16:37:20 +0000 Subject: [PATCH 15/15] Apply Rector changes (CI) --- src/MiddlewareStack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MiddlewareStack.php b/src/MiddlewareStack.php index 61bbb11..7a0be92 100644 --- a/src/MiddlewareStack.php +++ b/src/MiddlewareStack.php @@ -32,7 +32,7 @@ final class MiddlewareStack implements RequestHandlerInterface */ public function __construct( private readonly array $middlewares, - private RequestHandlerInterface $fallbackHandler, + private readonly RequestHandlerInterface $fallbackHandler, private readonly ?EventDispatcherInterface $eventDispatcher = null ) { if ($middlewares === []) {