From 5a74a11ed2bb6b0e4efd13cd9bc9e44c1b62e73c Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 22 Jan 2024 14:16:43 +0200 Subject: [PATCH 01/84] Add HttpRequest scope --- src/Hmvc/src/AbstractCore.php | 23 ++++++------- src/Router/src/CoreHandler.php | 10 +++--- tests/Framework/Router/CoreHandlerTest.php | 17 ++++++++++ tests/app/src/Bootloader/RoutesBootloader.php | 2 ++ tests/app/src/Controller/ScopeController.php | 32 +++++++++++++++++++ 5 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 tests/Framework/Router/CoreHandlerTest.php create mode 100644 tests/app/src/Controller/ScopeController.php diff --git a/src/Hmvc/src/AbstractCore.php b/src/Hmvc/src/AbstractCore.php index 9b473e189..95f549893 100644 --- a/src/Hmvc/src/AbstractCore.php +++ b/src/Hmvc/src/AbstractCore.php @@ -6,16 +6,11 @@ use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Exception\ControllerException; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; use Spiral\Core\Exception\Resolver\InvalidArgumentException; -use TypeError; -/** - * Provides ability to call controllers in IoC scope. - * - * Make sure to bind ScopeInterface in your container. - */ abstract class AbstractCore implements CoreInterface { /** @internal */ @@ -23,10 +18,12 @@ abstract class AbstractCore implements CoreInterface public function __construct( /** @internal */ - protected ContainerInterface $container + #[Proxy] protected ContainerInterface $container ) { - // resolver is usually the container itself - $this->resolver = $container->get(ResolverInterface::class); + // TODO: can we simplify this? + $this->resolver = $container + ->get(InvokerInterface::class) + ->invoke(static fn (#[Proxy] ResolverInterface $resolver) => $resolver); } public function callAction(string $controller, string $action, array $parameters = []): mixed @@ -64,11 +61,9 @@ public function callAction(string $controller, string $action, array $parameters ); } - $container = $this->container; - return ContainerScope::runScope( - $container, - static fn () => $method->invokeArgs($container->get($controller), $args) - ); + return $method->invokeArgs(new $controller( + ...$this->resolver->resolveArguments(new \ReflectionMethod($controller, '__construct')) + ), $args); } protected function resolveArguments(\ReflectionMethod $method, array $parameters): array diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index 7eac6c1d4..5f67225eb 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -10,7 +10,9 @@ use Psr\Http\Server\RequestHandlerInterface; use Spiral\Core\CoreInterface; use Spiral\Core\Exception\ControllerException; +use Spiral\Core\Scope; use Spiral\Core\ScopeInterface; +use Spiral\Framework\ScopeName; use Spiral\Http\Exception\ClientException; use Spiral\Http\Exception\ClientException\BadRequestException; use Spiral\Http\Exception\ClientException\ForbiddenException; @@ -96,10 +98,10 @@ public function handle(Request $request): Response // run the core withing PSR-7 Request/Response scope $result = $this->scope->runScope( - [ - Request::class => $request, - Response::class => $response, - ], + new Scope( + name: ScopeName::HttpRequest, + bindings: [Request::class => $request, Response::class => $response], + ), fn (): mixed => $this->tracer->trace( name: 'Controller [' . $controller . ':' . $action . ']', callback: fn (): mixed => $this->core->callAction( diff --git a/tests/Framework/Router/CoreHandlerTest.php b/tests/Framework/Router/CoreHandlerTest.php new file mode 100644 index 000000000..4fd2fcdd1 --- /dev/null +++ b/tests/Framework/Router/CoreHandlerTest.php @@ -0,0 +1,17 @@ +getHttp()->get('/scope/construct')->assertBodySame(ScopeName::HttpRequest->value); + $this->getHttp()->get('/scope/method')->assertBodySame(ScopeName::HttpRequest->value); + } +} diff --git a/tests/app/src/Bootloader/RoutesBootloader.php b/tests/app/src/Bootloader/RoutesBootloader.php index 0cdb43c31..49c5b99a8 100644 --- a/tests/app/src/Bootloader/RoutesBootloader.php +++ b/tests/app/src/Bootloader/RoutesBootloader.php @@ -8,6 +8,7 @@ use Psr\Http\Server\MiddlewareInterface; use Spiral\App\Controller\AuthController; use Spiral\App\Controller\InterceptedController; +use Spiral\App\Controller\ScopeController; use Spiral\App\Controller\TestController; use Spiral\App\Interceptor; use Spiral\Auth\Middleware\AuthMiddleware; @@ -59,6 +60,7 @@ protected function middlewareGroups(): array protected function defineRoutes(RoutingConfigurator $routes): void { $routes->add('auth', '/auth/')->controller(AuthController::class); + $routes->add('scope', '/scope/')->controller(ScopeController::class); $routes ->add('intercepted:without', '/intercepted/without') ->action(InterceptedController::class, 'without') diff --git a/tests/app/src/Controller/ScopeController.php b/tests/app/src/Controller/ScopeController.php new file mode 100644 index 000000000..575841301 --- /dev/null +++ b/tests/app/src/Controller/ScopeController.php @@ -0,0 +1,32 @@ +getScopeName($this->container); + } + + public function method(ContainerInterface $container): string + { + return $this->getScopeName($container); + } + + private function getScopeName(ContainerInterface $container): string + { + $scope = (new \ReflectionProperty($container, 'scope'))->getValue($container); + + return (new \ReflectionProperty($scope, 'scopeName'))->getValue($scope); + } +} From a0d9d0382dc601e6b2cf81c38eba01ba4435d096 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 22 Jan 2024 19:41:01 +0200 Subject: [PATCH 02/84] Bind controller in scope --- src/Hmvc/src/AbstractCore.php | 6 +- src/Hmvc/tests/CoreTest.php | 211 ++++++++++++++--------- src/Hmvc/tests/InterceptableCoreTest.php | 66 ++++--- src/Router/src/CoreHandler.php | 2 +- 4 files changed, 171 insertions(+), 114 deletions(-) diff --git a/src/Hmvc/src/AbstractCore.php b/src/Hmvc/src/AbstractCore.php index 95f549893..280797ec4 100644 --- a/src/Hmvc/src/AbstractCore.php +++ b/src/Hmvc/src/AbstractCore.php @@ -18,7 +18,7 @@ abstract class AbstractCore implements CoreInterface public function __construct( /** @internal */ - #[Proxy] protected ContainerInterface $container + protected ContainerInterface $container ) { // TODO: can we simplify this? $this->resolver = $container @@ -61,9 +61,7 @@ public function callAction(string $controller, string $action, array $parameters ); } - return $method->invokeArgs(new $controller( - ...$this->resolver->resolveArguments(new \ReflectionMethod($controller, '__construct')) - ), $args); + return $method->invokeArgs($this->container->get($controller), $args); } protected function resolveArguments(\ReflectionMethod $method, array $parameters): array diff --git a/src/Hmvc/tests/CoreTest.php b/src/Hmvc/tests/CoreTest.php index 37863db8e..43ac5a644 100644 --- a/src/Hmvc/tests/CoreTest.php +++ b/src/Hmvc/tests/CoreTest.php @@ -7,191 +7,232 @@ use PHPUnit\Framework\TestCase; use Spiral\Core\Container; use Spiral\Core\Exception\ControllerException; +use Spiral\Core\Scope; +use Spiral\Framework\ScopeName; use Spiral\Tests\Core\Fixtures\CleanController; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; -class CoreTest extends TestCase +final class CoreTest extends TestCase { + private Container $root; + + protected function setUp(): void + { + $this->root = new Container(); + } + public function testCallAction(): void { - $core = new SampleCore(new Container()); - $this->assertSame('Hello, Antony.', $core->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); + $core = new SampleCore($this->root); + + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('Hello, Antony.', $core->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); + }); } public function testCallActionDefaultParameter(): void { - $core = new SampleCore(new Container()); - $this->assertSame('Hello, Dave.', $core->callAction( - DummyController::class, - 'index' - )); + $core = new SampleCore($this->root); + + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('Hello, Dave.', $core->callAction( + DummyController::class, + 'index' + )); + }); } public function testCallActionDefaultAction(): void { - $core = new SampleCore(new Container()); - $this->assertSame('Hello, Dave.', $core->callAction( - DummyController::class, - 'index' - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('Hello, Dave.', $core->callAction( + DummyController::class, + 'index' + )); + }); } public function testCallActionDefaultActionWithParameter(): void { - $core = new SampleCore(new Container()); - $this->assertSame('Hello, Antony.', $core->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('Hello, Antony.', $core->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); + }); } public function testCallActionMissingParameter(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(DummyController::class, 'required'); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(DummyController::class, 'required'); + }); } public function testCallActionInvalidParameter(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(DummyController::class, 'required', ['id' => null]); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(DummyController::class, 'required', ['id' => null]); + }); } public function testCallWrongController(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(BadController::class, 'index', ['name' => 'Antony']); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(BadController::class, 'index', ['name' => 'Antony']); + }); } public function testCallBadAction(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(DummyController::class, 'missing', [ - 'name' => 'Antony', - ]); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(DummyController::class, 'missing', [ + 'name' => 'Antony', + ]); + }); } public function testStaticAction(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(DummyController::class, 'inner'); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(DummyController::class, 'inner'); + }); } public function testInheritedAction(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(DummyController::class, 'execute'); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(DummyController::class, 'execute'); + }); } public function testInheritedActionCall(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(DummyController::class, 'call'); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(DummyController::class, 'call'); + }); } public function testCallNotController(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - $core->callAction(SampleCore::class, 'index', [ - 'name' => 'Antony', - ]); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $core->callAction(SampleCore::class, 'index', [ + 'name' => 'Antony', + ]); + }); } public function testCleanController(): void { - $core = new SampleCore(new Container()); - - $this->assertSame('900', $core->callAction( - CleanController::class, - 'test', - ['id' => '900'] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('900', $core->callAction( + CleanController::class, + 'test', + ['id' => '900'] + )); + }); } public function testCleanControllerError(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - - $this->assertSame('900', $core->callAction( - CleanController::class, - 'test', - ['id' => null] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('900', $core->callAction( + CleanController::class, + 'test', + ['id' => null] + )); + }); } public function testCleanControllerError2(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - - $this->assertSame('900', $core->callAction( - CleanController::class, - 'test', - [] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('900', $core->callAction( + CleanController::class, + 'test', + [] + )); + }); } public function testCleanControllerError3(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - - $this->assertSame('900', $core->callAction( - CleanController::class, - 'invalid', - [] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('900', $core->callAction( + CleanController::class, + 'invalid', + [] + )); + }); } public function testCleanControllerError4(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - - $this->assertSame('900', $core->callAction( - CleanController::class, - 'another', - [] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('900', $core->callAction( + CleanController::class, + 'another', + [] + )); + }); } public function testMissingDependency(): void { $this->expectException(ControllerException::class); - $core = new SampleCore(new Container()); - - $this->assertSame('900', $core->callAction( - CleanController::class, - 'missing', - [] - )); + $core = new SampleCore($this->root); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { + $this->assertSame('900', $core->callAction( + CleanController::class, + 'missing', + [] + )); + }); } } diff --git a/src/Hmvc/tests/InterceptableCoreTest.php b/src/Hmvc/tests/InterceptableCoreTest.php index 03102977f..5d84ed883 100644 --- a/src/Hmvc/tests/InterceptableCoreTest.php +++ b/src/Hmvc/tests/InterceptableCoreTest.php @@ -9,48 +9,63 @@ use Spiral\Core\Exception\InterceptorException; use Spiral\Core\InterceptableCore; use Spiral\Core\InterceptorPipeline; +use Spiral\Core\Scope; +use Spiral\Framework\ScopeName; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; -class InterceptableCoreTest extends TestCase +final class InterceptableCoreTest extends TestCase { + private Container $root; + + protected function setUp(): void + { + $this->root = new Container(); + } + public function testNoInterceptors(): void { - $core = new SampleCore(new Container()); + $core = new SampleCore($this->root); $int = new InterceptableCore($core); - $this->assertSame('Hello, Antony.', $int->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($int): void { + $this->assertSame('Hello, Antony.', $int->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); + }); } public function testNoInterceptors2(): void { - $core = new SampleCore(new Container()); + $core = new SampleCore($this->root); $int = new InterceptableCore($core); $int->addInterceptor(new DemoInterceptor()); - $this->assertSame('?Hello, Antony.!', $int->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($int): void { + $this->assertSame('?Hello, Antony.!', $int->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); + }); } public function testNoInterceptors22(): void { - $core = new SampleCore(new Container()); + $core = new SampleCore($this->root); $int = new InterceptableCore($core); $int->addInterceptor(new DemoInterceptor()); $int->addInterceptor(new DemoInterceptor()); - $this->assertSame('??Hello, Antony.!!', $int->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); + $this->root->runScope(new Scope(ScopeName::Http), function () use ($int): void { + $this->assertSame('??Hello, Antony.!!', $int->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); + }); } public function testInvalidPipeline(): void @@ -58,10 +73,13 @@ public function testInvalidPipeline(): void $this->expectException(InterceptorException::class); $pipeline = new InterceptorPipeline(); - $pipeline->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - ); + + $this->root->runScope(new Scope(ScopeName::Http), function () use ($pipeline): void { + $pipeline->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + ); + }); } } diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index 5f67225eb..aa2f3869d 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -100,7 +100,7 @@ public function handle(Request $request): Response $result = $this->scope->runScope( new Scope( name: ScopeName::HttpRequest, - bindings: [Request::class => $request, Response::class => $response], + bindings: [Request::class => $request, Response::class => $response, $controller => $controller], ), fn (): mixed => $this->tracer->trace( name: 'Controller [' . $controller . ':' . $action . ']', From a56620b60c9687b7fa5ef7632268aa0327330314 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 23 Jan 2024 01:28:19 +0200 Subject: [PATCH 03/84] Add Proxy attributes --- src/Framework/Bootloader/Http/HttpBootloader.php | 3 ++- src/Framework/Bootloader/Http/RouterBootloader.php | 3 ++- src/Hmvc/src/AbstractCore.php | 2 +- src/Http/src/Pipeline.php | 3 ++- src/Telemetry/src/AbstractTracer.php | 3 ++- src/Telemetry/src/LogTracerFactory.php | 3 ++- src/Telemetry/src/NullTracerFactory.php | 3 ++- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index dd7d3bdd1..d8d5e8a93 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -11,6 +11,7 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Config\ConfiguratorInterface; use Spiral\Config\Patch\Append; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Attribute\Singleton; use Spiral\Core\Container\Autowire; use Spiral\Http\Config\HttpConfig; @@ -78,7 +79,7 @@ protected function httpCore( Pipeline $pipeline, RequestHandlerInterface $handler, ResponseFactoryInterface $responseFactory, - ContainerInterface $container, + #[Proxy] ContainerInterface $container, TracerFactoryInterface $tracerFactory ): Http { $core = new Http($config, $pipeline, $responseFactory, $container, $tracerFactory); diff --git a/src/Framework/Bootloader/Http/RouterBootloader.php b/src/Framework/Bootloader/Http/RouterBootloader.php index 1c49a8446..a6c86baa1 100644 --- a/src/Framework/Bootloader/Http/RouterBootloader.php +++ b/src/Framework/Bootloader/Http/RouterBootloader.php @@ -11,6 +11,7 @@ use Spiral\Boot\AbstractKernel; use Spiral\Boot\Bootloader\Bootloader; use Spiral\Config\ConfiguratorInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Core; use Spiral\Core\CoreInterface; use Spiral\Core\Exception\ScopeException; @@ -79,7 +80,7 @@ public function boot(AbstractKernel $kernel): void */ private function router( UriHandler $uriHandler, - ContainerInterface $container, + #[Proxy] ContainerInterface $container, TracerInterface $tracer, ?EventDispatcherInterface $dispatcher = null ): RouterInterface { diff --git a/src/Hmvc/src/AbstractCore.php b/src/Hmvc/src/AbstractCore.php index 280797ec4..a3952b18e 100644 --- a/src/Hmvc/src/AbstractCore.php +++ b/src/Hmvc/src/AbstractCore.php @@ -18,7 +18,7 @@ abstract class AbstractCore implements CoreInterface public function __construct( /** @internal */ - protected ContainerInterface $container + #[Proxy] protected ContainerInterface $container ) { // TODO: can we simplify this? $this->resolver = $container diff --git a/src/Http/src/Pipeline.php b/src/Http/src/Pipeline.php index e9d4b98de..8d25c190e 100644 --- a/src/Http/src/Pipeline.php +++ b/src/Http/src/Pipeline.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\ScopeInterface; use Spiral\Http\Event\MiddlewareProcessing; use Spiral\Http\Exception\PipelineException; @@ -29,7 +30,7 @@ final class Pipeline implements RequestHandlerInterface, MiddlewareInterface private ?RequestHandlerInterface $handler = null; public function __construct( - private readonly ScopeInterface $scope, + #[Proxy] private readonly ScopeInterface $scope, private readonly ?EventDispatcherInterface $dispatcher = null, ?TracerInterface $tracer = null ) { diff --git a/src/Telemetry/src/AbstractTracer.php b/src/Telemetry/src/AbstractTracer.php index ade2fb35a..5596742c4 100644 --- a/src/Telemetry/src/AbstractTracer.php +++ b/src/Telemetry/src/AbstractTracer.php @@ -4,6 +4,7 @@ namespace Spiral\Telemetry; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Container; use Spiral\Core\InvokerInterface; use Spiral\Core\ScopeInterface; @@ -16,7 +17,7 @@ abstract class AbstractTracer implements TracerInterface { public function __construct( - private readonly ?ScopeInterface $scope = new Container(), + #[Proxy] private readonly ?ScopeInterface $scope = new Container(), ) { } diff --git a/src/Telemetry/src/LogTracerFactory.php b/src/Telemetry/src/LogTracerFactory.php index cb1c0ab07..0efbaaa4d 100644 --- a/src/Telemetry/src/LogTracerFactory.php +++ b/src/Telemetry/src/LogTracerFactory.php @@ -6,6 +6,7 @@ use Psr\Log\LoggerInterface; use Ramsey\Uuid\UuidFactory; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\ScopeInterface; use Spiral\Logger\LogsInterface; @@ -21,7 +22,7 @@ final class LogTracerFactory implements TracerFactoryInterface private readonly LoggerInterface $logger; public function __construct( - private readonly ScopeInterface $scope, + #[Proxy] private readonly ScopeInterface $scope, private readonly ClockInterface $clock, LogsInterface $logs, string $channel = self::LOG_CHANNEL diff --git a/src/Telemetry/src/NullTracerFactory.php b/src/Telemetry/src/NullTracerFactory.php index ff47882ef..26fe07ced 100644 --- a/src/Telemetry/src/NullTracerFactory.php +++ b/src/Telemetry/src/NullTracerFactory.php @@ -4,6 +4,7 @@ namespace Spiral\Telemetry; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Container; use Spiral\Core\ScopeInterface; @@ -15,7 +16,7 @@ final class NullTracerFactory implements TracerFactoryInterface { public function __construct( - private readonly ?ScopeInterface $scope = new Container(), + #[Proxy] private readonly ?ScopeInterface $scope = new Container(), ) { } From 94bd5260b975048b31975e58fb53f54acf38e34d Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 23 Jan 2024 10:14:39 +0200 Subject: [PATCH 04/84] Fix tests --- src/AnnotatedRoutes/tests/IntegrationTest.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/AnnotatedRoutes/tests/IntegrationTest.php b/src/AnnotatedRoutes/tests/IntegrationTest.php index c441ef85a..5ec1e2027 100644 --- a/src/AnnotatedRoutes/tests/IntegrationTest.php +++ b/src/AnnotatedRoutes/tests/IntegrationTest.php @@ -7,9 +7,11 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; +use Spiral\Core\Scope; +use Spiral\Framework\ScopeName; use Spiral\Tests\Router\App\App; -class IntegrationTest extends TestCase +final class IntegrationTest extends TestCase { private App $app; @@ -63,7 +65,10 @@ public function get( array $headers = [], array $cookies = [] ): ResponseInterface { - return $this->app->getHttp()->handle($this->request($uri, 'GET', $query, $headers, $cookies)); + return $this->app->getContainer()->runScope( + new Scope(ScopeName::Http), + fn () => $this->app->getHttp()->handle($this->request($uri, 'GET', $query, $headers, $cookies)) + ); } public function getWithAttributes( @@ -85,8 +90,11 @@ public function post( array $headers = [], array $cookies = [] ): ResponseInterface { - return $this->app->getHttp()->handle( - $this->request($uri, 'POST', [], $headers, $cookies)->withParsedBody($data) + return $this->app->getContainer()->runScope( + new Scope(ScopeName::Http), + fn () => $this->app->getHttp()->handle( + $this->request($uri, 'POST', [], $headers, $cookies)->withParsedBody($data) + ) ); } From fcbd72df06a0f29ce02945cc8d07503c69d140cf Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 23 Jan 2024 10:29:02 +0200 Subject: [PATCH 05/84] Fix Psalm --- src/Router/src/CoreHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index aa2f3869d..dea9a084c 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -97,6 +97,7 @@ public function handle(Request $request): Response : $this->action; // run the core withing PSR-7 Request/Response scope + /** @psalm-suppress InvalidArgument */ $result = $this->scope->runScope( new Scope( name: ScopeName::HttpRequest, From 229011fa39858ccebec5680f97dd0ed8635d4543 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 24 Jan 2024 18:12:43 +0200 Subject: [PATCH 06/84] Add Http binding in the http scope --- src/AnnotatedRoutes/tests/IntegrationTest.php | 10 +- .../Bootloader/Http/HttpBootloader.php | 52 +++++++-- .../Middleware/AuthMiddlewareTest.php | 13 +-- .../Bootloader/Http/HttpBootloaderTest.php | 19 +++- tests/Framework/Http/AuthSessionTest.php | 32 ++---- tests/Framework/Http/ControllerTest.php | 21 ++-- tests/Framework/Http/CookiesTest.php | 79 ++++++------- tests/Framework/Http/FilterTest.php | 12 +- tests/Framework/Http/PaginationTest.php | 4 +- tests/Framework/Http/SessionTest.php | 104 +++++++++++------- tests/Framework/HttpTestCase.php | 63 +++++++++-- .../Interceptor/PipelineInterceptorTest.php | 18 +-- tests/Framework/Router/CoreHandlerTest.php | 4 +- 13 files changed, 257 insertions(+), 174 deletions(-) diff --git a/src/AnnotatedRoutes/tests/IntegrationTest.php b/src/AnnotatedRoutes/tests/IntegrationTest.php index 5ec1e2027..7b5bcba58 100644 --- a/src/AnnotatedRoutes/tests/IntegrationTest.php +++ b/src/AnnotatedRoutes/tests/IntegrationTest.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface; use Spiral\Core\Scope; use Spiral\Framework\ScopeName; +use Spiral\Http\Http; use Spiral\Tests\Router\App\App; final class IntegrationTest extends TestCase @@ -67,7 +68,7 @@ public function get( ): ResponseInterface { return $this->app->getContainer()->runScope( new Scope(ScopeName::Http), - fn () => $this->app->getHttp()->handle($this->request($uri, 'GET', $query, $headers, $cookies)) + fn (Http $http) => $http->handle($this->request($uri, 'GET', $query, $headers, $cookies)) ); } @@ -92,9 +93,8 @@ public function post( ): ResponseInterface { return $this->app->getContainer()->runScope( new Scope(ScopeName::Http), - fn () => $this->app->getHttp()->handle( - $this->request($uri, 'POST', [], $headers, $cookies)->withParsedBody($data) - ) + fn (Http $http) => $http + ->handle($this->request($uri, 'POST', [], $headers, $cookies)->withParsedBody($data)) ); } @@ -122,7 +122,7 @@ public function request( ->withQueryParams($query); } - public function fetchCookies(array $header) + public function fetchCookies(array $header): array { $result = []; foreach ($header as $line) { diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index d8d5e8a93..e60c14bbb 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -13,7 +13,10 @@ use Spiral\Config\Patch\Append; use Spiral\Core\Attribute\Proxy; use Spiral\Core\Attribute\Singleton; +use Spiral\Core\BinderInterface; use Spiral\Core\Container\Autowire; +use Spiral\Core\InvokerInterface; +use Spiral\Framework\ScopeName; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; @@ -26,19 +29,45 @@ #[Singleton] final class HttpBootloader extends Bootloader { - protected const DEPENDENCIES = [ - TelemetryBootloader::class, - ]; - - protected const SINGLETONS = [ - Http::class => [self::class, 'httpCore'], - ]; - public function __construct( - private readonly ConfiguratorInterface $config + private readonly ConfiguratorInterface $config, + private readonly BinderInterface $binder, ) { } + public function defineDependencies(): array + { + return [ + TelemetryBootloader::class, + ]; + } + + public function defineSingletons(): array + { + $this->binder->getBinder(ScopeName::Http)->bindSingleton( + Http::class, + fn (InvokerInterface $invoker): Http => $invoker->invoke([self::class, 'httpCore']) + ); + + /** + * @deprecated since v3.12. Will be removed in v4.0. + */ + $this->binder->bindSingleton( + Http::class, + function (InvokerInterface $invoker, #[Proxy] ContainerInterface $container): Http { + @trigger_error(\sprintf( + 'Using `%s` outside of the `%s` scope is deprecated and will be impossible in version 4.0.', + Http::class, + ScopeName::Http->value + ), \E_USER_DEPRECATED); + + return $invoker->invoke([self::class, 'httpCore'], ['container' => $container]); + } + ); + + return []; + } + public function init(): void { $this->config->setDefaults( @@ -74,12 +103,15 @@ public function addInputBag(string $bag, array $config): void $this->config->modify(HttpConfig::CONFIG, new Append('inputBags', $bag, $config)); } + /** + * @deprecated since v3.12. Will be removed in v4.0 and replaced with callback. + */ protected function httpCore( HttpConfig $config, Pipeline $pipeline, RequestHandlerInterface $handler, ResponseFactoryInterface $responseFactory, - #[Proxy] ContainerInterface $container, + ContainerInterface $container, TracerFactoryInterface $tracerFactory ): Http { $core = new Http($config, $pipeline, $responseFactory, $container, $tracerFactory); diff --git a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php index 581e810d9..3f8e0a48c 100644 --- a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php +++ b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php @@ -26,7 +26,12 @@ public function testTokenStorageInterfaceShouldBeBound(): void AuthMiddleware::class, new Autowire(AuthMiddleware::class, ['tokenStorage' => $storage]) ); - $this->setHttpHandler(function () use ($storage): void { + + $scope = $this->getContainer()->get(TokenStorageScope::class); + $ref = new \ReflectionMethod($scope, 'getTokenStorage'); + $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); + + $this->get(uri: '/', handler: function () use ($storage): void { $scope = $this->getContainer()->get(TokenStorageScope::class); $ref = new \ReflectionMethod($scope, 'getTokenStorage'); @@ -37,11 +42,5 @@ public function testTokenStorageInterfaceShouldBeBound(): void $scope = $this->getContainer()->get(TokenStorageScope::class); $ref = new \ReflectionMethod($scope, 'getTokenStorage'); $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); - - $this->getHttp()->get('/'); - - $scope = $this->getContainer()->get(TokenStorageScope::class); - $ref = new \ReflectionMethod($scope, 'getTokenStorage'); - $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); } } diff --git a/tests/Framework/Bootloader/Http/HttpBootloaderTest.php b/tests/Framework/Bootloader/Http/HttpBootloaderTest.php index 7ffd62396..367f03e9e 100644 --- a/tests/Framework/Bootloader/Http/HttpBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/HttpBootloaderTest.php @@ -12,7 +12,10 @@ use Spiral\Bootloader\Http\HttpBootloader; use Spiral\Config\ConfigManager; use Spiral\Config\LoaderInterface; +use Spiral\Core\Container; use Spiral\Core\Container\Autowire; +use Spiral\Core\Scope; +use Spiral\Framework\ScopeName; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Tests\Framework\BaseTestCase; @@ -21,7 +24,17 @@ final class HttpBootloaderTest extends BaseTestCase { public function testHttpBinding(): void { - $this->assertContainerBoundAsSingleton(Http::class, Http::class); + $this->getContainer()->runScope(new Scope(ScopeName::Http), function (Container $container): void { + $this->assertTrue($container->has(Http::class)); + + $instance1 = $container->get(Http::class); + $instance2 = $container->get(Http::class); + + $this->assertInstanceOf(Http::class, $instance1); + $this->assertInstanceOf(Http::class, $instance2); + + $this->assertSame($instance1, $instance2); + }); } public function testDefaultInputBags(): void @@ -34,7 +47,7 @@ public function testAddInputBag(): void $configs = new ConfigManager($this->createMock(LoaderInterface::class)); $configs->setDefaults(HttpConfig::CONFIG, ['inputBags' => []]); - $bootloader = new HttpBootloader($configs); + $bootloader = new HttpBootloader($configs, new Container()); $bootloader->addInputBag('test', ['class' => 'foo', 'source' => 'bar']); $this->assertSame([ @@ -48,7 +61,7 @@ public function testAddMiddleware(mixed $middleware): void $configs = new ConfigManager($this->createMock(LoaderInterface::class)); $configs->setDefaults(HttpConfig::CONFIG, ['middleware' => []]); - $bootloader = new HttpBootloader($configs); + $bootloader = new HttpBootloader($configs, new Container()); $bootloader->addMiddleware($middleware); $this->assertSame([$middleware], $configs->getConfig(HttpConfig::CONFIG)['middleware']); diff --git a/tests/Framework/Http/AuthSessionTest.php b/tests/Framework/Http/AuthSessionTest.php index abb964f4e..3d49ad1e5 100644 --- a/tests/Framework/Http/AuthSessionTest.php +++ b/tests/Framework/Http/AuthSessionTest.php @@ -14,57 +14,49 @@ final class AuthSessionTest extends HttpTestCase public function testNoToken(): void { - $this->getHttp()->get(uri: '/auth/token') - ->assertBodySame('none'); + $this->get(uri: '/auth/token')->assertBodySame('none'); } public function testLogin(): void { - $result = $this->getHttp()->get(uri: '/auth/login') + $result = $this->get(uri: '/auth/login') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->getHttp()->get(uri: '/auth/token', cookies: $result->getCookies()) - ->assertBodyNotSame('none'); + $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); } public function testLogout(): void { - $result = $this->getHttp()->get(uri: '/auth/login') + $result = $this->get(uri: '/auth/login') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->getHttp()->get(uri: '/auth/token', cookies: $result->getCookies()) - ->assertBodyNotSame('none'); - - $this->getHttp()->get(uri: '/auth/logout', cookies: $result->getCookies()) - ->assertBodySame('closed'); - - $this->getHttp()->get(uri: '/auth/token', cookies: $result->getCookies()) - ->assertBodySame('none'); + $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); + $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); + $this->get(uri: '/auth/logout', cookies: $result->getCookies())->assertBodySame('closed'); + $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodySame('none'); } public function testLoginScope(): void { - $result = $this->getHttp()->get('/auth/login2') + $result = $this->get('/auth/login2') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->getHttp()->get('/auth/token2', cookies: $result->getCookies()) - ->assertBodyNotSame('none'); + $this->get('/auth/token2', cookies: $result->getCookies())->assertBodyNotSame('none'); } public function testLoginPayload(): void { - $result = $this->getHttp()->get('/auth/login2') + $result = $this->get('/auth/login2') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->getHttp()->get('/auth/token3', cookies: $result->getCookies()) - ->assertBodySame('{"userID":1}'); + $this->get('/auth/token3', cookies: $result->getCookies())->assertBodySame('{"userID":1}'); } } diff --git a/tests/Framework/Http/ControllerTest.php b/tests/Framework/Http/ControllerTest.php index e3ba2d24c..17df5f43c 100644 --- a/tests/Framework/Http/ControllerTest.php +++ b/tests/Framework/Http/ControllerTest.php @@ -11,29 +11,25 @@ final class ControllerTest extends HttpTestCase { public function testIndexAction(): void { - $this->getHttp()->get('/index') - ->assertBodySame('Hello, Dave.'); - - $this->getHttp()->get('/index/Antony') - ->assertBodySame('Hello, Antony.'); + $this->get('/index')->assertBodySame('Hello, Dave.'); + $this->get('/index/Antony')->assertBodySame('Hello, Antony.'); } public function testRouteJson(): void { - $this->getHttp()->get('/route') - ->assertBodySame('{"action":"route","name":"Dave"}'); + $this->get('/route')->assertBodySame('{"action":"route","name":"Dave"}'); } public function test404(): void { - $this->getHttp()->get('/undefined')->assertNotFound(); + $this->get('/undefined')->assertNotFound(); } public function testPayloadAction(): void { $factory = new Psr17Factory(); - $this->getHttp()->post( + $this->post( uri: '/payload', data: $factory->createStream('{"a":"b"}'), headers: ['Content-Type' => 'application/json;charset=UTF-8;'] @@ -45,7 +41,7 @@ public function testPayloadWithCustomJsonHeader(): void { $factory = new Psr17Factory(); - $this->getHttp()->post( + $this->post( uri: '/payload', data: $factory->createStream('{"a":"b"}'), headers: ['Content-Type' => 'application/vnd.api+json;charset=UTF-8;'] @@ -57,7 +53,7 @@ public function testPayloadActionBad(): void { $factory = new Psr17Factory(); - $this->getHttp()->post( + $this->post( uri: '/payload', data: $factory->createStream('{"a":"b"'), headers: ['Content-Type' => 'application/json;charset=UTF-8;'] @@ -67,7 +63,6 @@ public function testPayloadActionBad(): void public function test500(): void { - $this->getHttp()->get('/error') - ->assertStatus(500); + $this->get('/error')->assertStatus(500); } } diff --git a/tests/Framework/Http/CookiesTest.php b/tests/Framework/Http/CookiesTest.php index 59b67a80e..20da2d978 100644 --- a/tests/Framework/Http/CookiesTest.php +++ b/tests/Framework/Http/CookiesTest.php @@ -37,46 +37,46 @@ public function testOutsideOfScopeFail(): void public function testHasCookie(): void { - $this->setHttpHandler(function () { - return (int)$this->cookies()->has('a'); - }); - - $this->getHttp()->get('/') + $this + ->get(uri: '/', handler: fn (): int => (int) $this->cookies()->has('a')) ->assertOk() ->assertBodySame('0'); } public function testHasCookie2(): void { - $this->setHttpHandler(fn(): int => (int)$this->cookies()->has('a')); - - $this->getHttp()->get('/', cookies: [ - 'a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello'), - ]) + $this + ->get( + uri: '/', + cookies: ['a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello')], + handler: fn (): int => (int)$this->cookies()->has('a') + ) ->assertOk() ->assertBodySame('1'); } public function testGetCookie2(): void { - $this->setHttpHandler(fn(): string => $this->cookies()->get('a')); - - $this->getHttp()->get('/', cookies: [ - 'a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello'), - ]) + $this + ->get( + uri: '/', + cookies: ['a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello')], + handler: fn (): string => $this->cookies()->get('a') + ) ->assertOk() ->assertBodySame('hello'); } public function testSetCookie(): void { - $this->setHttpHandler(function (): string { - $this->cookies()->set('a', 'value'); - - return 'ok'; - }); - - $result = $this->getHttp()->get('/') + $result = $this + ->get( + uri:'/', + handler: function (): string { + $this->cookies()->set('a', 'value'); + return 'ok'; + } + ) ->assertOk() ->assertBodySame('ok'); @@ -90,15 +90,17 @@ public function testSetCookie(): void public function testSetCookie2(): void { - $this->setHttpHandler(function (): string { - $this->cookies()->schedule(Cookie::create('a', 'value')); - $this->assertSame([], $this->cookies()->getAll()); - $this->assertCount(1, $this->cookies()->getScheduled()); - - return 'ok'; - }); - - $result = $this->getHttp()->get('/') + $result = $this + ->get( + uri: '/', + handler: function (): string { + $this->cookies()->schedule(Cookie::create('a', 'value')); + $this->assertSame([], $this->cookies()->getAll()); + $this->assertCount(1, $this->cookies()->getScheduled()); + + return 'ok'; + } + ) ->assertOk() ->assertBodySame('ok'); @@ -112,13 +114,14 @@ public function testSetCookie2(): void public function testDeleteCookie(): void { - $this->setHttpHandler(function (): string { - $this->cookies()->delete('cookie'); - - return 'ok'; - }); - - $this->getHttp()->get('/') + $this + ->get( + uri: '/', + handler: function (): string { + $this->cookies()->delete('cookie'); + return 'ok'; + } + ) ->assertOk() ->assertBodySame('ok') ->assertCookieSame('cookie', ''); diff --git a/tests/Framework/Http/FilterTest.php b/tests/Framework/Http/FilterTest.php index f8130d3eb..b58da6a72 100644 --- a/tests/Framework/Http/FilterTest.php +++ b/tests/Framework/Http/FilterTest.php @@ -10,27 +10,23 @@ final class FilterTest extends HttpTestCase { public function testValid(): void { - $this->getHttp() - ->post('/filter', data: ['name' => 'hello']) - ->assertBodySame('{"name":"hello","sectionValue":null}'); + $this->post('/filter', data: ['name' => 'hello'])->assertBodySame('{"name":"hello","sectionValue":null}'); } public function testDotNotation(): void { - $this->getHttp() - ->post('/filter', data: ['name' => 'hello', 'section' => ['value' => 'abc'],]) + $this->post('/filter', data: ['name' => 'hello', 'section' => ['value' => 'abc'],]) ->assertBodySame('{"name":"hello","sectionValue":"abc"}'); } public function testBadRequest(): void { - $this->getHttp()->get('/filter2')->assertStatus(500); + $this->get('/filter2')->assertStatus(500); } public function testInputTest(): void { - $this->getHttp() - ->get('/input', query: ['section' => ['value' => 'abc'],]) + $this->get('/input', query: ['section' => ['value' => 'abc'],]) ->assertBodySame('value: abc'); } } diff --git a/tests/Framework/Http/PaginationTest.php b/tests/Framework/Http/PaginationTest.php index e8172062e..22e793024 100644 --- a/tests/Framework/Http/PaginationTest.php +++ b/tests/Framework/Http/PaginationTest.php @@ -12,7 +12,7 @@ final class PaginationTest extends HttpTestCase { public function testPaginate(): void { - $this->getHttp()->get('/paginate')->assertBodySame('1'); + $this->get('/paginate')->assertBodySame('1'); } public function testPaginateError(): void @@ -24,6 +24,6 @@ public function testPaginateError(): void public function testPaginate2(): void { - $this->getHttp()->get('/paginate', query: ['page' => 2])->assertBodySame('2'); + $this->get('/paginate', query: ['page' => 2])->assertBodySame('2'); } } diff --git a/tests/Framework/Http/SessionTest.php b/tests/Framework/Http/SessionTest.php index 97cce4af2..f591f6b0f 100644 --- a/tests/Framework/Http/SessionTest.php +++ b/tests/Framework/Http/SessionTest.php @@ -14,15 +14,11 @@ public function setUp(): void parent::setUp(); $this->enableMiddlewares(); - - $this->setHttpHandler(function () { - return ++$this->session()->getSection('cli')->value; - }); } public function testSetSid(): void { - $this->getHttp()->get('/') + $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) ->assertOk() ->assertBodySame('1') ->assertCookieExists('sid'); @@ -30,40 +26,56 @@ public function testSetSid(): void public function testSessionResume(): void { - $result = $this->getHttp()->get('/') + $result = $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) ->assertOk() ->assertBodySame('1') ->assertCookieExists('sid'); - $this->getHttp()->get('/', cookies: [ - 'sid' => $result->getCookies()['sid'], - ])->assertOk()->assertBodySame('2'); - - $this->getHttp()->get('/', cookies: [ - 'sid' => $result->getCookies()['sid'], - ])->assertOk()->assertBodySame('3'); + $this + ->get( + uri: '/', + cookies: ['sid' => $result->getCookies()['sid']], + handler: fn (): int => ++$this->session()->getSection('cli')->value + ) + ->assertOk() + ->assertBodySame('2'); + + $this + ->get( + uri: '/', + cookies: ['sid' => $result->getCookies()['sid']], + handler: fn (): int => ++$this->session()->getSection('cli')->value + ) + ->assertOk() + ->assertBodySame('3'); } public function testSessionRegenerateId(): void { - $result = $this->getHttp()->get('/') + $result = $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) ->assertOk() ->assertBodySame('1') ->assertCookieExists('sid'); - $this->getHttp()->get('/', cookies: [ - 'sid' => $result->getCookies()['sid'], - ])->assertOk()->assertBodySame('2'); - - $this->setHttpHandler(function () { - $this->session()->regenerateID(false); - - return ++$this->session()->getSection('cli')->value; - }); - - $newResult = $this->getHttp()->get('/', cookies: [ - 'sid' => $result->getCookies()['sid'], - ]) + $this + ->get( + uri: '/', + cookies: ['sid' => $result->getCookies()['sid']], + handler: fn (): int => ++$this->session()->getSection('cli')->value + ) + ->assertOk() + ->assertBodySame('2'); + + $newResult = $this + ->get( + uri: '/', + cookies: ['sid' => $result->getCookies()['sid']], + handler: function () { + $this->session()->regenerateID(false); + + return ++$this->session()->getSection('cli')->value; + } + ) ->assertOk() ->assertBodySame('3') ->assertCookieExists('sid'); @@ -73,25 +85,33 @@ public function testSessionRegenerateId(): void public function testDestroySession(): void { - $result = $this->getHttp()->get('/') + $result = $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) ->assertOk() ->assertBodySame('1') ->assertCookieExists('sid'); - $this->getHttp()->get('/', cookies: [ - 'sid' => $result->getCookies()['sid'], - ])->assertOk()->assertBodySame('2'); - - $this->setHttpHandler(function () { - $this->session()->destroy(); - $this->assertFalse($this->session()->isStarted()); - - return ++$this->session()->getSection('cli')->value; - }); - - $this->getHttp()->get('/', cookies: [ - 'sid' => $result->getCookies()['sid'], - ])->assertOk()->assertBodySame('1'); + $this + ->get( + uri: '/', + cookies: ['sid' => $result->getCookies()['sid']], + handler: fn (): int => ++$this->session()->getSection('cli')->value + ) + ->assertOk() + ->assertBodySame('2'); + + $this + ->get( + uri: '/', + cookies: ['sid' => $result->getCookies()['sid']], + handler: function () { + $this->session()->destroy(); + $this->assertFalse($this->session()->isStarted()); + + return ++$this->session()->getSection('cli')->value; + } + ) + ->assertOk() + ->assertBodySame('1'); } private function session(): SessionInterface diff --git a/tests/Framework/HttpTestCase.php b/tests/Framework/HttpTestCase.php index e038e98fd..de976eb36 100644 --- a/tests/Framework/HttpTestCase.php +++ b/tests/Framework/HttpTestCase.php @@ -6,30 +6,69 @@ use Spiral\Auth\Middleware\AuthMiddleware; use Spiral\Cookies\Middleware\CookiesMiddleware; +use Spiral\Core\Container; +use Spiral\Core\Scope; use Spiral\Csrf\Middleware\CsrfMiddleware; +use Spiral\Framework\ScopeName; use Spiral\Http\Config\HttpConfig; +use Spiral\Http\Http; use Spiral\Session\Middleware\SessionMiddleware; use Spiral\Testing\Http\FakeHttp; +use Spiral\Testing\Http\TestResponse; abstract class HttpTestCase extends BaseTestCase { public const ENCRYPTER_KEY = 'def00000b325585e24ff3bd2d2cd273aa1d2274cb6851a9f2c514c2e2a83806f2661937f8b9cbe217e37943f5f9ccb6b5f91151606774869883e5557a941dfd879cbf5be'; - private FakeHttp $http; - public function setUp(): void - { - parent::setUp(); - $this->http = $this->fakeHttp(); - } + public function get( + string $uri, + array $query = [], + array $headers = [], + array $cookies = [], + mixed $handler = null + ): mixed { + return $this->getContainer()->runScope( + new Scope(ScopeName::Http), + function (Container $container, Http $http) use ($uri, $query, $headers, $cookies, $handler): TestResponse { + if ($handler !== null) { + $http->setHandler($handler); + } - public function getHttp(): FakeHttp - { - return $this->http; + $fakeHttp = new FakeHttp( + $container, + $this->getFileFactory(), + fn (\Closure $closure, array $bindings): mixed => $this->runScoped($closure, $bindings) + ); + + return $fakeHttp->get($uri, $query, $headers, $cookies); + } + ); } - public function setHttpHandler(\Closure $handler) - { - $this->http->getHttp()->setHandler($handler); + public function post( + string $uri, + mixed $data = [], + array $headers = [], + array $cookies = [], + array $files = [], + mixed $handler = null + ): mixed { + return $this->getContainer()->runScope( + new Scope(ScopeName::Http), + function (Container $container, Http $http) use ($uri, $data, $headers, $cookies, $files, $handler) { + if ($handler !== null) { + $http->setHandler($handler); + } + + $fakeHttp = new FakeHttp( + $container, + $this->getFileFactory(), + fn (\Closure $closure, array $bindings): mixed => $this->runScoped($closure, $bindings) + ); + + return $fakeHttp->post($uri, $data, $headers, $cookies, $files); + } + ); } protected function enableMiddlewares(): void diff --git a/tests/Framework/Interceptor/PipelineInterceptorTest.php b/tests/Framework/Interceptor/PipelineInterceptorTest.php index 8a95e15ea..109e7f375 100644 --- a/tests/Framework/Interceptor/PipelineInterceptorTest.php +++ b/tests/Framework/Interceptor/PipelineInterceptorTest.php @@ -10,41 +10,35 @@ final class PipelineInterceptorTest extends HttpTestCase { public function testWithoutPipeline(): void { - $this->getHttp()->get('/intercepted/without') - ->assertBodySame('["without","three","two","one"]'); + $this->get('/intercepted/without')->assertBodySame('["without","three","two","one"]'); } public function testWith(): void { - $this->getHttp()->get('/intercepted/with') - ->assertBodySame('["with","three","two","one"]'); + $this->get('/intercepted/with')->assertBodySame('["with","three","two","one"]'); } public function testMix(): void { //pipeline interceptors are injected into the middle - $this->getHttp()->get('/intercepted/mix') - ->assertBodySame('["mix","six","three","two","one","five","four"]'); + $this->get('/intercepted/mix')->assertBodySame('["mix","six","three","two","one","five","four"]'); } public function testDup(): void { //pipeline interceptors are added to the end - $this->getHttp()->get('/intercepted/dup') - ->assertBodySame('["dup","three","two","one","three","two","one"]'); + $this->get('/intercepted/dup')->assertBodySame('["dup","three","two","one","three","two","one"]'); } public function testSkipNext(): void { //interceptors after current pipeline are ignored - $this->getHttp()->get('/intercepted/skip') - ->assertBodySame('["skip","three","two","one","one"]'); + $this->get('/intercepted/skip')->assertBodySame('["skip","three","two","one","one"]'); } public function testSkipIfFirst(): void { //interceptors after current pipeline are ignored - $this->getHttp()->get('/intercepted/first') - ->assertBodySame('["first","three","two","one"]'); + $this->get('/intercepted/first')->assertBodySame('["first","three","two","one"]'); } } diff --git a/tests/Framework/Router/CoreHandlerTest.php b/tests/Framework/Router/CoreHandlerTest.php index 4fd2fcdd1..313def1e1 100644 --- a/tests/Framework/Router/CoreHandlerTest.php +++ b/tests/Framework/Router/CoreHandlerTest.php @@ -11,7 +11,7 @@ final class CoreHandlerTest extends HttpTestCase { public function testHttpRequestScope(): void { - $this->getHttp()->get('/scope/construct')->assertBodySame(ScopeName::HttpRequest->value); - $this->getHttp()->get('/scope/method')->assertBodySame(ScopeName::HttpRequest->value); + $this->get('/scope/construct')->assertBodySame(ScopeName::HttpRequest->value); + $this->get('/scope/method')->assertBodySame(ScopeName::HttpRequest->value); } } From 5f632fd50813c94e4bcc5f8d750a12c0bbe1ba0e Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 24 Jan 2024 18:24:36 +0200 Subject: [PATCH 07/84] Fix parameters in phpdoc --- src/Core/src/BinderInterface.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/src/BinderInterface.php b/src/Core/src/BinderInterface.php index e7283b33c..6dad42ded 100644 --- a/src/Core/src/BinderInterface.php +++ b/src/Core/src/BinderInterface.php @@ -5,11 +5,12 @@ namespace Spiral\Core; use Spiral\Core\Container\InjectorInterface; +use Spiral\Framework\ScopeName; /** * Manages container bindings. * - * @method BinderInterface getBinder(?string $scope = null) + * @method BinderInterface getBinder(string|ScopeName|null $scope = null) * * @psalm-type TResolver = class-string|non-empty-string|object|callable|array{class-string, non-empty-string} */ From adcbc746ad329d551931c3858ad975908372f732 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 24 Jan 2024 18:39:38 +0200 Subject: [PATCH 08/84] Simplify code --- src/Framework/Bootloader/Http/HttpBootloader.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index e60c14bbb..e776e4c59 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -44,10 +44,7 @@ public function defineDependencies(): array public function defineSingletons(): array { - $this->binder->getBinder(ScopeName::Http)->bindSingleton( - Http::class, - fn (InvokerInterface $invoker): Http => $invoker->invoke([self::class, 'httpCore']) - ); + $this->binder->getBinder(ScopeName::Http)->bindSingleton(Http::class, [self::class, 'httpCore']); /** * @deprecated since v3.12. Will be removed in v4.0. From a82dcfe94ce05cf302562ba1125b1b27320faa0b Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 25 Jan 2024 15:41:55 +0200 Subject: [PATCH 09/84] Simplify Hmvc tests --- composer.json | 2 +- src/Hmvc/composer.json | 3 +- src/Hmvc/tests/CoreTest.php | 224 ++++++++++------------- src/Hmvc/tests/InterceptableCoreTest.php | 74 +++----- 4 files changed, 130 insertions(+), 173 deletions(-) diff --git a/composer.json b/composer.json index 969596a35..332291445 100644 --- a/composer.json +++ b/composer.json @@ -135,7 +135,7 @@ "rector/rector": "0.18.1", "spiral/code-style": "^1.1", "spiral/nyholm-bridge": "^1.2", - "spiral/testing": "^2.7", + "spiral/testing": "^2.8", "spiral/validator": "^1.3", "google/protobuf": "^3.25", "symplify/monorepo-builder": "^10.2.7", diff --git a/src/Hmvc/composer.json b/src/Hmvc/composer.json index 49de385c1..b2efd0cde 100644 --- a/src/Hmvc/composer.json +++ b/src/Hmvc/composer.json @@ -33,7 +33,8 @@ }, "require-dev": { "phpunit/phpunit": "^10.1", - "vimeo/psalm": "^5.9" + "vimeo/psalm": "^5.9", + "spiral/testing": "^2.8" }, "autoload": { "psr-4": { diff --git a/src/Hmvc/tests/CoreTest.php b/src/Hmvc/tests/CoreTest.php index 43ac5a644..1b6ef4c85 100644 --- a/src/Hmvc/tests/CoreTest.php +++ b/src/Hmvc/tests/CoreTest.php @@ -4,235 +4,207 @@ namespace Spiral\Tests\Core; -use PHPUnit\Framework\TestCase; -use Spiral\Core\Container; use Spiral\Core\Exception\ControllerException; -use Spiral\Core\Scope; use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; +use Spiral\Testing\TestCase; use Spiral\Tests\Core\Fixtures\CleanController; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; final class CoreTest extends TestCase { - private Container $root; - - protected function setUp(): void - { - $this->root = new Container(); - } - + #[TestScope(ScopeName::Http)] public function testCallAction(): void { - $core = new SampleCore($this->root); - - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('Hello, Antony.', $core->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('Hello, Antony.', $core->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); } + #[TestScope(ScopeName::Http)] public function testCallActionDefaultParameter(): void { - $core = new SampleCore($this->root); - - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('Hello, Dave.', $core->callAction( - DummyController::class, - 'index' - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('Hello, Dave.', $core->callAction( + DummyController::class, + 'index' + )); } + #[TestScope(ScopeName::Http)] public function testCallActionDefaultAction(): void { - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('Hello, Dave.', $core->callAction( - DummyController::class, - 'index' - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('Hello, Dave.', $core->callAction( + DummyController::class, + 'index' + )); } + #[TestScope(ScopeName::Http)] public function testCallActionDefaultActionWithParameter(): void { - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('Hello, Antony.', $core->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('Hello, Antony.', $core->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); } + #[TestScope(ScopeName::Http)] public function testCallActionMissingParameter(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(DummyController::class, 'required'); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(DummyController::class, 'required'); } + #[TestScope(ScopeName::Http)] public function testCallActionInvalidParameter(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(DummyController::class, 'required', ['id' => null]); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(DummyController::class, 'required', ['id' => null]); } + #[TestScope(ScopeName::Http)] public function testCallWrongController(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(BadController::class, 'index', ['name' => 'Antony']); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(BadController::class, 'index', ['name' => 'Antony']); } + #[TestScope(ScopeName::Http)] public function testCallBadAction(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(DummyController::class, 'missing', [ - 'name' => 'Antony', - ]); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(DummyController::class, 'missing', [ + 'name' => 'Antony', + ]); } + #[TestScope(ScopeName::Http)] public function testStaticAction(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(DummyController::class, 'inner'); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(DummyController::class, 'inner'); } + #[TestScope(ScopeName::Http)] public function testInheritedAction(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(DummyController::class, 'execute'); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(DummyController::class, 'execute'); } + #[TestScope(ScopeName::Http)] public function testInheritedActionCall(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(DummyController::class, 'call'); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(DummyController::class, 'call'); } + #[TestScope(ScopeName::Http)] public function testCallNotController(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $core->callAction(SampleCore::class, 'index', [ - 'name' => 'Antony', - ]); - }); + $core = new SampleCore($this->getContainer()); + $core->callAction(SampleCore::class, 'index', [ + 'name' => 'Antony', + ]); } + #[TestScope(ScopeName::Http)] public function testCleanController(): void { - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('900', $core->callAction( - CleanController::class, - 'test', - ['id' => '900'] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('900', $core->callAction( + CleanController::class, + 'test', + ['id' => '900'] + )); } + #[TestScope(ScopeName::Http)] public function testCleanControllerError(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('900', $core->callAction( - CleanController::class, - 'test', - ['id' => null] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('900', $core->callAction( + CleanController::class, + 'test', + ['id' => null] + )); } + #[TestScope(ScopeName::Http)] public function testCleanControllerError2(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('900', $core->callAction( - CleanController::class, - 'test', - [] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('900', $core->callAction( + CleanController::class, + 'test', + [] + )); } + #[TestScope(ScopeName::Http)] public function testCleanControllerError3(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('900', $core->callAction( - CleanController::class, - 'invalid', - [] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('900', $core->callAction( + CleanController::class, + 'invalid', + [] + )); } + #[TestScope(ScopeName::Http)] public function testCleanControllerError4(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('900', $core->callAction( - CleanController::class, - 'another', - [] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('900', $core->callAction( + CleanController::class, + 'another', + [] + )); } + #[TestScope(ScopeName::Http)] public function testMissingDependency(): void { $this->expectException(ControllerException::class); - $core = new SampleCore($this->root); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($core): void { - $this->assertSame('900', $core->callAction( - CleanController::class, - 'missing', - [] - )); - }); + $core = new SampleCore($this->getContainer()); + $this->assertSame('900', $core->callAction( + CleanController::class, + 'missing', + [] + )); } } diff --git a/src/Hmvc/tests/InterceptableCoreTest.php b/src/Hmvc/tests/InterceptableCoreTest.php index 5d84ed883..aa89557fb 100644 --- a/src/Hmvc/tests/InterceptableCoreTest.php +++ b/src/Hmvc/tests/InterceptableCoreTest.php @@ -4,82 +4,66 @@ namespace Spiral\Tests\Core; -use PHPUnit\Framework\TestCase; -use Spiral\Core\Container; use Spiral\Core\Exception\InterceptorException; use Spiral\Core\InterceptableCore; use Spiral\Core\InterceptorPipeline; -use Spiral\Core\Scope; use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; +use Spiral\Testing\TestCase; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; final class InterceptableCoreTest extends TestCase { - private Container $root; - - protected function setUp(): void - { - $this->root = new Container(); - } - + #[TestScope(ScopeName::Http)] public function testNoInterceptors(): void { - $core = new SampleCore($this->root); - $int = new InterceptableCore($core); + $int = new InterceptableCore(new SampleCore($this->getContainer())); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($int): void { - $this->assertSame('Hello, Antony.', $int->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); - }); + $this->assertSame('Hello, Antony.', $int->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); } + #[TestScope(ScopeName::Http)] public function testNoInterceptors2(): void { - $core = new SampleCore($this->root); - $int = new InterceptableCore($core); + $int = new InterceptableCore(new SampleCore($this->getContainer())); $int->addInterceptor(new DemoInterceptor()); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($int): void { - $this->assertSame('?Hello, Antony.!', $int->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); - }); + $this->assertSame('?Hello, Antony.!', $int->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); } + #[TestScope(ScopeName::Http)] public function testNoInterceptors22(): void { - $core = new SampleCore($this->root); - $int = new InterceptableCore($core); + $int = new InterceptableCore(new SampleCore($this->getContainer())); $int->addInterceptor(new DemoInterceptor()); $int->addInterceptor(new DemoInterceptor()); - $this->root->runScope(new Scope(ScopeName::Http), function () use ($int): void { - $this->assertSame('??Hello, Antony.!!', $int->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - )); - }); + $this->assertSame('??Hello, Antony.!!', $int->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + )); } + #[TestScope(ScopeName::Http)] public function testInvalidPipeline(): void { $this->expectException(InterceptorException::class); $pipeline = new InterceptorPipeline(); - - $this->root->runScope(new Scope(ScopeName::Http), function () use ($pipeline): void { - $pipeline->callAction( - DummyController::class, - 'index', - ['name' => 'Antony'] - ); - }); + $pipeline->callAction( + DummyController::class, + 'index', + ['name' => 'Antony'] + ); } } From 66514157fd5181d74c0fa8b6be60bcd7a356f07e Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 25 Jan 2024 19:08:48 +0200 Subject: [PATCH 10/84] Simplify http tests --- src/Hmvc/tests/CoreTest.php | 19 +--- src/Hmvc/tests/InterceptableCoreTest.php | 5 +- .../Middleware/AuthMiddlewareTest.php | 15 ++- tests/Framework/BaseTestCase.php | 7 +- tests/Framework/Http/AuthSessionTest.php | 27 +++--- tests/Framework/Http/ControllerTest.php | 19 ++-- tests/Framework/Http/CookiesTest.php | 75 +++++++-------- tests/Framework/Http/FilterTest.php | 20 +++- tests/Framework/Http/PaginationTest.php | 7 +- tests/Framework/Http/SessionTest.php | 92 ++++++++----------- tests/Framework/HttpTestCase.php | 57 +----------- .../Interceptor/PipelineInterceptorTest.php | 15 +-- tests/Framework/Router/CoreHandlerTest.php | 6 +- 13 files changed, 153 insertions(+), 211 deletions(-) diff --git a/src/Hmvc/tests/CoreTest.php b/src/Hmvc/tests/CoreTest.php index 1b6ef4c85..75dea0e47 100644 --- a/src/Hmvc/tests/CoreTest.php +++ b/src/Hmvc/tests/CoreTest.php @@ -12,9 +12,9 @@ use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; +#[TestScope(ScopeName::Http)] final class CoreTest extends TestCase { - #[TestScope(ScopeName::Http)] public function testCallAction(): void { $core = new SampleCore($this->getContainer()); @@ -25,7 +25,6 @@ public function testCallAction(): void )); } - #[TestScope(ScopeName::Http)] public function testCallActionDefaultParameter(): void { $core = new SampleCore($this->getContainer()); @@ -35,7 +34,6 @@ public function testCallActionDefaultParameter(): void )); } - #[TestScope(ScopeName::Http)] public function testCallActionDefaultAction(): void { $core = new SampleCore($this->getContainer()); @@ -45,7 +43,6 @@ public function testCallActionDefaultAction(): void )); } - #[TestScope(ScopeName::Http)] public function testCallActionDefaultActionWithParameter(): void { $core = new SampleCore($this->getContainer()); @@ -56,7 +53,6 @@ public function testCallActionDefaultActionWithParameter(): void )); } - #[TestScope(ScopeName::Http)] public function testCallActionMissingParameter(): void { $this->expectException(ControllerException::class); @@ -65,7 +61,6 @@ public function testCallActionMissingParameter(): void $core->callAction(DummyController::class, 'required'); } - #[TestScope(ScopeName::Http)] public function testCallActionInvalidParameter(): void { $this->expectException(ControllerException::class); @@ -74,7 +69,6 @@ public function testCallActionInvalidParameter(): void $core->callAction(DummyController::class, 'required', ['id' => null]); } - #[TestScope(ScopeName::Http)] public function testCallWrongController(): void { $this->expectException(ControllerException::class); @@ -83,7 +77,6 @@ public function testCallWrongController(): void $core->callAction(BadController::class, 'index', ['name' => 'Antony']); } - #[TestScope(ScopeName::Http)] public function testCallBadAction(): void { $this->expectException(ControllerException::class); @@ -94,7 +87,6 @@ public function testCallBadAction(): void ]); } - #[TestScope(ScopeName::Http)] public function testStaticAction(): void { $this->expectException(ControllerException::class); @@ -103,7 +95,6 @@ public function testStaticAction(): void $core->callAction(DummyController::class, 'inner'); } - #[TestScope(ScopeName::Http)] public function testInheritedAction(): void { $this->expectException(ControllerException::class); @@ -112,7 +103,6 @@ public function testInheritedAction(): void $core->callAction(DummyController::class, 'execute'); } - #[TestScope(ScopeName::Http)] public function testInheritedActionCall(): void { $this->expectException(ControllerException::class); @@ -121,7 +111,6 @@ public function testInheritedActionCall(): void $core->callAction(DummyController::class, 'call'); } - #[TestScope(ScopeName::Http)] public function testCallNotController(): void { $this->expectException(ControllerException::class); @@ -132,7 +121,6 @@ public function testCallNotController(): void ]); } - #[TestScope(ScopeName::Http)] public function testCleanController(): void { $core = new SampleCore($this->getContainer()); @@ -143,7 +131,6 @@ public function testCleanController(): void )); } - #[TestScope(ScopeName::Http)] public function testCleanControllerError(): void { $this->expectException(ControllerException::class); @@ -156,7 +143,6 @@ public function testCleanControllerError(): void )); } - #[TestScope(ScopeName::Http)] public function testCleanControllerError2(): void { $this->expectException(ControllerException::class); @@ -169,7 +155,6 @@ public function testCleanControllerError2(): void )); } - #[TestScope(ScopeName::Http)] public function testCleanControllerError3(): void { $this->expectException(ControllerException::class); @@ -182,7 +167,6 @@ public function testCleanControllerError3(): void )); } - #[TestScope(ScopeName::Http)] public function testCleanControllerError4(): void { $this->expectException(ControllerException::class); @@ -195,7 +179,6 @@ public function testCleanControllerError4(): void )); } - #[TestScope(ScopeName::Http)] public function testMissingDependency(): void { $this->expectException(ControllerException::class); diff --git a/src/Hmvc/tests/InterceptableCoreTest.php b/src/Hmvc/tests/InterceptableCoreTest.php index aa89557fb..047344d37 100644 --- a/src/Hmvc/tests/InterceptableCoreTest.php +++ b/src/Hmvc/tests/InterceptableCoreTest.php @@ -13,9 +13,9 @@ use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; +#[TestScope(ScopeName::Http)] final class InterceptableCoreTest extends TestCase { - #[TestScope(ScopeName::Http)] public function testNoInterceptors(): void { $int = new InterceptableCore(new SampleCore($this->getContainer())); @@ -27,7 +27,6 @@ public function testNoInterceptors(): void )); } - #[TestScope(ScopeName::Http)] public function testNoInterceptors2(): void { $int = new InterceptableCore(new SampleCore($this->getContainer())); @@ -40,7 +39,6 @@ public function testNoInterceptors2(): void )); } - #[TestScope(ScopeName::Http)] public function testNoInterceptors22(): void { $int = new InterceptableCore(new SampleCore($this->getContainer())); @@ -54,7 +52,6 @@ public function testNoInterceptors22(): void )); } - #[TestScope(ScopeName::Http)] public function testInvalidPipeline(): void { $this->expectException(InterceptorException::class); diff --git a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php index 3f8e0a48c..d60e37b4c 100644 --- a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php +++ b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php @@ -8,6 +8,8 @@ use Spiral\Auth\TokenStorageInterface; use Spiral\Auth\TokenStorageScope; use Spiral\Core\Container\Autowire; +use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; final class AuthMiddlewareTest extends HttpTestCase @@ -19,6 +21,7 @@ public function setUp(): void $this->enableMiddlewares(); } + #[TestScope(ScopeName::Http)] public function testTokenStorageInterfaceShouldBeBound(): void { $storage = $this->createMock(TokenStorageInterface::class); @@ -27,11 +30,7 @@ public function testTokenStorageInterfaceShouldBeBound(): void new Autowire(AuthMiddleware::class, ['tokenStorage' => $storage]) ); - $scope = $this->getContainer()->get(TokenStorageScope::class); - $ref = new \ReflectionMethod($scope, 'getTokenStorage'); - $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); - - $this->get(uri: '/', handler: function () use ($storage): void { + $this->setHttpHandler(function () use ($storage): void { $scope = $this->getContainer()->get(TokenStorageScope::class); $ref = new \ReflectionMethod($scope, 'getTokenStorage'); @@ -42,5 +41,11 @@ public function testTokenStorageInterfaceShouldBeBound(): void $scope = $this->getContainer()->get(TokenStorageScope::class); $ref = new \ReflectionMethod($scope, 'getTokenStorage'); $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); + + $this->fakeHttp()->get('/'); + + $scope = $this->getContainer()->get(TokenStorageScope::class); + $ref = new \ReflectionMethod($scope, 'getTokenStorage'); + $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); } } diff --git a/tests/Framework/BaseTestCase.php b/tests/Framework/BaseTestCase.php index 7f878d3a5..95dfb5255 100644 --- a/tests/Framework/BaseTestCase.php +++ b/tests/Framework/BaseTestCase.php @@ -22,12 +22,13 @@ public function rootDirectory(): string public function createAppInstance(Container $container = new Container()): TestApp { return TestApp::create( - $this->defineDirectories($this->rootDirectory()), - false + directories: $this->defineDirectories($this->rootDirectory()), + handleErrors: false, + container: $container, )->disableBootloader(...$this->disabledBootloaders); } - public function withDisabledBootloaders(string ... $bootloader): self + public function withDisabledBootloaders(string ...$bootloader): self { $this->disabledBootloaders = $bootloader; diff --git a/tests/Framework/Http/AuthSessionTest.php b/tests/Framework/Http/AuthSessionTest.php index 3d49ad1e5..134fe3215 100644 --- a/tests/Framework/Http/AuthSessionTest.php +++ b/tests/Framework/Http/AuthSessionTest.php @@ -4,8 +4,11 @@ namespace Spiral\Tests\Framework\Http; +use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class AuthSessionTest extends HttpTestCase { public const ENV = [ @@ -14,49 +17,49 @@ final class AuthSessionTest extends HttpTestCase public function testNoToken(): void { - $this->get(uri: '/auth/token')->assertBodySame('none'); + $this->fakeHttp()->get(uri: '/auth/token')->assertBodySame('none'); } public function testLogin(): void { - $result = $this->get(uri: '/auth/login') + $result = $this->fakeHttp()->get(uri: '/auth/login') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); + $this->fakeHttp()->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); } public function testLogout(): void { - $result = $this->get(uri: '/auth/login') + $result = $this->fakeHttp()->get(uri: '/auth/login') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); - $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); - $this->get(uri: '/auth/logout', cookies: $result->getCookies())->assertBodySame('closed'); - $this->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodySame('none'); + $this->fakeHttp()->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); + $this->fakeHttp()->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodyNotSame('none'); + $this->fakeHttp()->get(uri: '/auth/logout', cookies: $result->getCookies())->assertBodySame('closed'); + $this->fakeHttp()->get(uri: '/auth/token', cookies: $result->getCookies())->assertBodySame('none'); } public function testLoginScope(): void { - $result = $this->get('/auth/login2') + $result = $this->fakeHttp()->get('/auth/login2') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->get('/auth/token2', cookies: $result->getCookies())->assertBodyNotSame('none'); + $this->fakeHttp()->get('/auth/token2', cookies: $result->getCookies())->assertBodyNotSame('none'); } public function testLoginPayload(): void { - $result = $this->get('/auth/login2') + $result = $this->fakeHttp()->get('/auth/login2') ->assertBodySame('OK') ->assertCookieExists('token') ->assertCookieExists('sid'); - $this->get('/auth/token3', cookies: $result->getCookies())->assertBodySame('{"userID":1}'); + $this->fakeHttp()->get('/auth/token3', cookies: $result->getCookies())->assertBodySame('{"userID":1}'); } } diff --git a/tests/Framework/Http/ControllerTest.php b/tests/Framework/Http/ControllerTest.php index 17df5f43c..d43fa4a73 100644 --- a/tests/Framework/Http/ControllerTest.php +++ b/tests/Framework/Http/ControllerTest.php @@ -5,31 +5,34 @@ namespace Spiral\Tests\Framework\Http; use Nyholm\Psr7\Factory\Psr17Factory; +use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class ControllerTest extends HttpTestCase { public function testIndexAction(): void { - $this->get('/index')->assertBodySame('Hello, Dave.'); - $this->get('/index/Antony')->assertBodySame('Hello, Antony.'); + $this->fakeHttp()->get('/index')->assertBodySame('Hello, Dave.'); + $this->fakeHttp()->get('/index/Antony')->assertBodySame('Hello, Antony.'); } public function testRouteJson(): void { - $this->get('/route')->assertBodySame('{"action":"route","name":"Dave"}'); + $this->fakeHttp()->get('/route')->assertBodySame('{"action":"route","name":"Dave"}'); } public function test404(): void { - $this->get('/undefined')->assertNotFound(); + $this->fakeHttp()->get('/undefined')->assertNotFound(); } public function testPayloadAction(): void { $factory = new Psr17Factory(); - $this->post( + $this->fakeHttp()->post( uri: '/payload', data: $factory->createStream('{"a":"b"}'), headers: ['Content-Type' => 'application/json;charset=UTF-8;'] @@ -41,7 +44,7 @@ public function testPayloadWithCustomJsonHeader(): void { $factory = new Psr17Factory(); - $this->post( + $this->fakeHttp()->post( uri: '/payload', data: $factory->createStream('{"a":"b"}'), headers: ['Content-Type' => 'application/vnd.api+json;charset=UTF-8;'] @@ -53,7 +56,7 @@ public function testPayloadActionBad(): void { $factory = new Psr17Factory(); - $this->post( + $this->fakeHttp()->post( uri: '/payload', data: $factory->createStream('{"a":"b"'), headers: ['Content-Type' => 'application/json;charset=UTF-8;'] @@ -63,6 +66,6 @@ public function testPayloadActionBad(): void public function test500(): void { - $this->get('/error')->assertStatus(500); + $this->fakeHttp()->get('/error')->assertStatus(500); } } diff --git a/tests/Framework/Http/CookiesTest.php b/tests/Framework/Http/CookiesTest.php index 20da2d978..8bce34991 100644 --- a/tests/Framework/Http/CookiesTest.php +++ b/tests/Framework/Http/CookiesTest.php @@ -8,8 +8,11 @@ use Spiral\Cookies\CookieManager; use Spiral\Core\Exception\ScopeException; use Spiral\Encrypter\EncrypterInterface; +use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class CookiesTest extends HttpTestCase { public const ENV = [ @@ -37,19 +40,20 @@ public function testOutsideOfScopeFail(): void public function testHasCookie(): void { - $this - ->get(uri: '/', handler: fn (): int => (int) $this->cookies()->has('a')) - ->assertOk() - ->assertBodySame('0'); + $this->setHttpHandler(fn (): int => (int)$this->cookies()->has('a')); + + $this->fakeHttp()->get('/')->assertOk()->assertBodySame('0'); } public function testHasCookie2(): void { + $this->setHttpHandler(fn (): int => (int)$this->cookies()->has('a')); + $this + ->fakeHttp() ->get( uri: '/', - cookies: ['a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello')], - handler: fn (): int => (int)$this->cookies()->has('a') + cookies: ['a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello')] ) ->assertOk() ->assertBodySame('1'); @@ -57,11 +61,13 @@ public function testHasCookie2(): void public function testGetCookie2(): void { + $this->setHttpHandler(fn (): string => $this->cookies()->get('a')); + $this + ->fakeHttp() ->get( uri: '/', - cookies: ['a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello')], - handler: fn (): string => $this->cookies()->get('a') + cookies: ['a' => $this->getContainer()->get(EncrypterInterface::class)->encrypt('hello')] ) ->assertOk() ->assertBodySame('hello'); @@ -69,16 +75,12 @@ public function testGetCookie2(): void public function testSetCookie(): void { - $result = $this - ->get( - uri:'/', - handler: function (): string { - $this->cookies()->set('a', 'value'); - return 'ok'; - } - ) - ->assertOk() - ->assertBodySame('ok'); + $this->setHttpHandler(function (): string { + $this->cookies()->set('a', 'value'); + return 'ok'; + }); + + $result = $this->fakeHttp()->get('/')->assertOk()->assertBodySame('ok'); $cookies = $result->getCookies(); @@ -90,19 +92,15 @@ public function testSetCookie(): void public function testSetCookie2(): void { - $result = $this - ->get( - uri: '/', - handler: function (): string { - $this->cookies()->schedule(Cookie::create('a', 'value')); - $this->assertSame([], $this->cookies()->getAll()); - $this->assertCount(1, $this->cookies()->getScheduled()); + $this->setHttpHandler(function (): string { + $this->cookies()->schedule(Cookie::create('a', 'value')); + $this->assertSame([], $this->cookies()->getAll()); + $this->assertCount(1, $this->cookies()->getScheduled()); - return 'ok'; - } - ) - ->assertOk() - ->assertBodySame('ok'); + return 'ok'; + }); + + $result = $this->fakeHttp()->get('/')->assertOk()->assertBodySame('ok'); $cookies = $result->getCookies(); @@ -114,17 +112,12 @@ public function testSetCookie2(): void public function testDeleteCookie(): void { - $this - ->get( - uri: '/', - handler: function (): string { - $this->cookies()->delete('cookie'); - return 'ok'; - } - ) - ->assertOk() - ->assertBodySame('ok') - ->assertCookieSame('cookie', ''); + $this->setHttpHandler(function (): string { + $this->cookies()->delete('cookie'); + return 'ok'; + }); + + $this->fakeHttp()->get('/')->assertOk()->assertBodySame('ok')->assertCookieSame('cookie', ''); } private function cookies(): CookieManager diff --git a/tests/Framework/Http/FilterTest.php b/tests/Framework/Http/FilterTest.php index b58da6a72..a2c5e2121 100644 --- a/tests/Framework/Http/FilterTest.php +++ b/tests/Framework/Http/FilterTest.php @@ -4,29 +4,41 @@ namespace Spiral\Tests\Framework\Http; +use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class FilterTest extends HttpTestCase { public function testValid(): void { - $this->post('/filter', data: ['name' => 'hello'])->assertBodySame('{"name":"hello","sectionValue":null}'); + $this + ->fakeHttp() + ->post('/filter', data: ['name' => 'hello']) + ->assertBodySame('{"name":"hello","sectionValue":null}'); } public function testDotNotation(): void { - $this->post('/filter', data: ['name' => 'hello', 'section' => ['value' => 'abc'],]) + $this + ->fakeHttp() + ->post('/filter', data: ['name' => 'hello', 'section' => ['value' => 'abc'],]) ->assertBodySame('{"name":"hello","sectionValue":"abc"}'); } public function testBadRequest(): void { - $this->get('/filter2')->assertStatus(500); + $this->fakeHttp() + ->get('/filter2') + ->assertStatus(500); } public function testInputTest(): void { - $this->get('/input', query: ['section' => ['value' => 'abc'],]) + $this + ->fakeHttp() + ->get('/input', query: ['section' => ['value' => 'abc'],]) ->assertBodySame('value: abc'); } } diff --git a/tests/Framework/Http/PaginationTest.php b/tests/Framework/Http/PaginationTest.php index 22e793024..6b090d121 100644 --- a/tests/Framework/Http/PaginationTest.php +++ b/tests/Framework/Http/PaginationTest.php @@ -5,14 +5,17 @@ namespace Spiral\Tests\Framework\Http; use Spiral\Core\Exception\ScopeException; +use Spiral\Framework\ScopeName; use Spiral\Http\PaginationFactory; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class PaginationTest extends HttpTestCase { public function testPaginate(): void { - $this->get('/paginate')->assertBodySame('1'); + $this->fakeHttp()->get('/paginate')->assertBodySame('1'); } public function testPaginateError(): void @@ -24,6 +27,6 @@ public function testPaginateError(): void public function testPaginate2(): void { - $this->get('/paginate', query: ['page' => 2])->assertBodySame('2'); + $this->fakeHttp()->get('/paginate', query: ['page' => 2])->assertBodySame('2'); } } diff --git a/tests/Framework/Http/SessionTest.php b/tests/Framework/Http/SessionTest.php index f591f6b0f..5a362a432 100644 --- a/tests/Framework/Http/SessionTest.php +++ b/tests/Framework/Http/SessionTest.php @@ -4,9 +4,12 @@ namespace Spiral\Tests\Framework\Http; +use Spiral\Framework\ScopeName; use Spiral\Session\SessionInterface; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class SessionTest extends HttpTestCase { public function setUp(): void @@ -18,64 +21,51 @@ public function setUp(): void public function testSetSid(): void { - $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) - ->assertOk() - ->assertBodySame('1') - ->assertCookieExists('sid'); + $this->setHttpHandler(fn (): int => ++$this->session()->getSection('cli')->value); + + $this->fakeHttp()->get('/')->assertOk()->assertBodySame('1')->assertCookieExists('sid'); } public function testSessionResume(): void { - $result = $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) - ->assertOk() - ->assertBodySame('1') - ->assertCookieExists('sid'); + $this->setHttpHandler(fn (): int => ++$this->session()->getSection('cli')->value); + + $result = $this->fakeHttp()->get('/')->assertOk()->assertBodySame('1')->assertCookieExists('sid'); $this - ->get( - uri: '/', - cookies: ['sid' => $result->getCookies()['sid']], - handler: fn (): int => ++$this->session()->getSection('cli')->value - ) + ->fakeHttp() + ->get(uri: '/', cookies: ['sid' => $result->getCookies()['sid']]) ->assertOk() ->assertBodySame('2'); $this - ->get( - uri: '/', - cookies: ['sid' => $result->getCookies()['sid']], - handler: fn (): int => ++$this->session()->getSection('cli')->value - ) + ->fakeHttp() + ->get(uri: '/', cookies: ['sid' => $result->getCookies()['sid']]) ->assertOk() ->assertBodySame('3'); } public function testSessionRegenerateId(): void { - $result = $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) - ->assertOk() - ->assertBodySame('1') - ->assertCookieExists('sid'); + $this->setHttpHandler(fn (): int => ++$this->session()->getSection('cli')->value); + + $result = $this->fakeHttp()->get('/')->assertOk()->assertBodySame('1')->assertCookieExists('sid'); $this - ->get( - uri: '/', - cookies: ['sid' => $result->getCookies()['sid']], - handler: fn (): int => ++$this->session()->getSection('cli')->value - ) + ->fakeHttp() + ->get(uri: '/', cookies: ['sid' => $result->getCookies()['sid']]) ->assertOk() ->assertBodySame('2'); - $newResult = $this - ->get( - uri: '/', - cookies: ['sid' => $result->getCookies()['sid']], - handler: function () { - $this->session()->regenerateID(false); + $this->setHttpHandler(function (): int { + $this->session()->regenerateID(false); - return ++$this->session()->getSection('cli')->value; - } - ) + return ++$this->session()->getSection('cli')->value; + }); + + $newResult = $this + ->fakeHttp() + ->get(uri: '/', cookies: ['sid' => $result->getCookies()['sid']]) ->assertOk() ->assertBodySame('3') ->assertCookieExists('sid'); @@ -85,31 +75,29 @@ public function testSessionRegenerateId(): void public function testDestroySession(): void { - $result = $this->get(uri: '/', handler: fn (): int => ++$this->session()->getSection('cli')->value) - ->assertOk() - ->assertBodySame('1') - ->assertCookieExists('sid'); + $this->setHttpHandler(fn (): int => ++$this->session()->getSection('cli')->value); + + $result = $this->fakeHttp()->get('/')->assertOk()->assertBodySame('1')->assertCookieExists('sid'); $this + ->fakeHttp() ->get( uri: '/', - cookies: ['sid' => $result->getCookies()['sid']], - handler: fn (): int => ++$this->session()->getSection('cli')->value + cookies: ['sid' => $result->getCookies()['sid']] ) ->assertOk() ->assertBodySame('2'); - $this - ->get( - uri: '/', - cookies: ['sid' => $result->getCookies()['sid']], - handler: function () { - $this->session()->destroy(); - $this->assertFalse($this->session()->isStarted()); + $this->setHttpHandler(function () { + $this->session()->destroy(); + $this->assertFalse($this->session()->isStarted()); - return ++$this->session()->getSection('cli')->value; - } - ) + return ++$this->session()->getSection('cli')->value; + }); + + $this + ->fakeHttp() + ->get(uri: '/', cookies: ['sid' => $result->getCookies()['sid']]) ->assertOk() ->assertBodySame('1'); } diff --git a/tests/Framework/HttpTestCase.php b/tests/Framework/HttpTestCase.php index de976eb36..9381f2e5f 100644 --- a/tests/Framework/HttpTestCase.php +++ b/tests/Framework/HttpTestCase.php @@ -6,69 +6,18 @@ use Spiral\Auth\Middleware\AuthMiddleware; use Spiral\Cookies\Middleware\CookiesMiddleware; -use Spiral\Core\Container; -use Spiral\Core\Scope; use Spiral\Csrf\Middleware\CsrfMiddleware; -use Spiral\Framework\ScopeName; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Session\Middleware\SessionMiddleware; -use Spiral\Testing\Http\FakeHttp; -use Spiral\Testing\Http\TestResponse; abstract class HttpTestCase extends BaseTestCase { public const ENCRYPTER_KEY = 'def00000b325585e24ff3bd2d2cd273aa1d2274cb6851a9f2c514c2e2a83806f2661937f8b9cbe217e37943f5f9ccb6b5f91151606774869883e5557a941dfd879cbf5be'; - public function get( - string $uri, - array $query = [], - array $headers = [], - array $cookies = [], - mixed $handler = null - ): mixed { - return $this->getContainer()->runScope( - new Scope(ScopeName::Http), - function (Container $container, Http $http) use ($uri, $query, $headers, $cookies, $handler): TestResponse { - if ($handler !== null) { - $http->setHandler($handler); - } - - $fakeHttp = new FakeHttp( - $container, - $this->getFileFactory(), - fn (\Closure $closure, array $bindings): mixed => $this->runScoped($closure, $bindings) - ); - - return $fakeHttp->get($uri, $query, $headers, $cookies); - } - ); - } - - public function post( - string $uri, - mixed $data = [], - array $headers = [], - array $cookies = [], - array $files = [], - mixed $handler = null - ): mixed { - return $this->getContainer()->runScope( - new Scope(ScopeName::Http), - function (Container $container, Http $http) use ($uri, $data, $headers, $cookies, $files, $handler) { - if ($handler !== null) { - $http->setHandler($handler); - } - - $fakeHttp = new FakeHttp( - $container, - $this->getFileFactory(), - fn (\Closure $closure, array $bindings): mixed => $this->runScoped($closure, $bindings) - ); - - return $fakeHttp->post($uri, $data, $headers, $cookies, $files); - } - ); + public function setHttpHandler(\Closure $handler): void + { + $this->getContainer()->get(Http::class)->setHandler($handler); } protected function enableMiddlewares(): void diff --git a/tests/Framework/Interceptor/PipelineInterceptorTest.php b/tests/Framework/Interceptor/PipelineInterceptorTest.php index 109e7f375..e06e625cc 100644 --- a/tests/Framework/Interceptor/PipelineInterceptorTest.php +++ b/tests/Framework/Interceptor/PipelineInterceptorTest.php @@ -4,41 +4,44 @@ namespace Spiral\Tests\Framework\Interceptor; +use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; +#[TestScope(ScopeName::Http)] final class PipelineInterceptorTest extends HttpTestCase { public function testWithoutPipeline(): void { - $this->get('/intercepted/without')->assertBodySame('["without","three","two","one"]'); + $this->fakeHttp()->get('/intercepted/without')->assertBodySame('["without","three","two","one"]'); } public function testWith(): void { - $this->get('/intercepted/with')->assertBodySame('["with","three","two","one"]'); + $this->fakeHttp()->get('/intercepted/with')->assertBodySame('["with","three","two","one"]'); } public function testMix(): void { //pipeline interceptors are injected into the middle - $this->get('/intercepted/mix')->assertBodySame('["mix","six","three","two","one","five","four"]'); + $this->fakeHttp()->get('/intercepted/mix')->assertBodySame('["mix","six","three","two","one","five","four"]'); } public function testDup(): void { //pipeline interceptors are added to the end - $this->get('/intercepted/dup')->assertBodySame('["dup","three","two","one","three","two","one"]'); + $this->fakeHttp()->get('/intercepted/dup')->assertBodySame('["dup","three","two","one","three","two","one"]'); } public function testSkipNext(): void { //interceptors after current pipeline are ignored - $this->get('/intercepted/skip')->assertBodySame('["skip","three","two","one","one"]'); + $this->fakeHttp()->get('/intercepted/skip')->assertBodySame('["skip","three","two","one","one"]'); } public function testSkipIfFirst(): void { //interceptors after current pipeline are ignored - $this->get('/intercepted/first')->assertBodySame('["first","three","two","one"]'); + $this->fakeHttp()->get('/intercepted/first')->assertBodySame('["first","three","two","one"]'); } } diff --git a/tests/Framework/Router/CoreHandlerTest.php b/tests/Framework/Router/CoreHandlerTest.php index 313def1e1..df19dd5a6 100644 --- a/tests/Framework/Router/CoreHandlerTest.php +++ b/tests/Framework/Router/CoreHandlerTest.php @@ -5,13 +5,15 @@ namespace Spiral\Tests\Framework\Router; use Spiral\Framework\ScopeName; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; final class CoreHandlerTest extends HttpTestCase { + #[TestScope(ScopeName::Http)] public function testHttpRequestScope(): void { - $this->get('/scope/construct')->assertBodySame(ScopeName::HttpRequest->value); - $this->get('/scope/method')->assertBodySame(ScopeName::HttpRequest->value); + $this->fakeHttp()->get('/scope/construct')->assertBodySame(ScopeName::HttpRequest->value); + $this->fakeHttp()->get('/scope/method')->assertBodySame(ScopeName::HttpRequest->value); } } From d1eb6535180392565c5c36467119f1eb1aab9fc1 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 25 Jan 2024 19:20:06 +0200 Subject: [PATCH 11/84] Add dev version of spiral/testing --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 332291445..dce13a547 100644 --- a/composer.json +++ b/composer.json @@ -135,7 +135,7 @@ "rector/rector": "0.18.1", "spiral/code-style": "^1.1", "spiral/nyholm-bridge": "^1.2", - "spiral/testing": "^2.8", + "spiral/testing": "dev-feature/scopes as 2.8.0", "spiral/validator": "^1.3", "google/protobuf": "^3.25", "symplify/monorepo-builder": "^10.2.7", From 7e3c73777ff63779e5a72c58f086ac9c01d08629 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 25 Jan 2024 22:40:52 +0200 Subject: [PATCH 12/84] Simplify spiral/annotated-routes tests --- src/AnnotatedRoutes/composer.json | 1 + src/AnnotatedRoutes/tests/App/App.php | 35 ------ .../tests/App/runtime/.gitignore | 2 - src/AnnotatedRoutes/tests/IntegrationTest.php | 114 ++---------------- .../tests/RouteLocatorListenerTest.php | 1 + src/AnnotatedRoutes/tests/TestCase.php | 34 ++++-- 6 files changed, 34 insertions(+), 153 deletions(-) delete mode 100644 src/AnnotatedRoutes/tests/App/App.php delete mode 100644 src/AnnotatedRoutes/tests/App/runtime/.gitignore diff --git a/src/AnnotatedRoutes/composer.json b/src/AnnotatedRoutes/composer.json index 87054a66b..fe9dfc9d0 100644 --- a/src/AnnotatedRoutes/composer.json +++ b/src/AnnotatedRoutes/composer.json @@ -40,6 +40,7 @@ "mockery/mockery": "^1.5", "phpunit/phpunit": "^10.1", "spiral/framework": "^3.1", + "spiral/testing": "^2.8", "spiral/nyholm-bridge": "^1.2", "vimeo/psalm": "^5.9" }, diff --git a/src/AnnotatedRoutes/tests/App/App.php b/src/AnnotatedRoutes/tests/App/App.php deleted file mode 100644 index d582ac009..000000000 --- a/src/AnnotatedRoutes/tests/App/App.php +++ /dev/null @@ -1,35 +0,0 @@ -container->get(Http::class); - } - - public function getConsole(): Console - { - return $this->container->get(Console::class); - } - - public function getContainer(): Container - { - return $this->container; - } -} diff --git a/src/AnnotatedRoutes/tests/App/runtime/.gitignore b/src/AnnotatedRoutes/tests/App/runtime/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/src/AnnotatedRoutes/tests/App/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/AnnotatedRoutes/tests/IntegrationTest.php b/src/AnnotatedRoutes/tests/IntegrationTest.php index 7b5bcba58..33f057bcb 100644 --- a/src/AnnotatedRoutes/tests/IntegrationTest.php +++ b/src/AnnotatedRoutes/tests/IntegrationTest.php @@ -4,132 +4,36 @@ namespace Spiral\Tests\Router; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestFactoryInterface; -use Psr\Http\Message\ServerRequestInterface; -use Spiral\Core\Scope; use Spiral\Framework\ScopeName; -use Spiral\Http\Http; -use Spiral\Tests\Router\App\App; +use Spiral\Testing\Attribute\TestScope; +#[TestScope(ScopeName::Http)] final class IntegrationTest extends TestCase { - private App $app; - - public function setUp(): void - { - parent::setUp(); - $this->app = $this->makeApp(['DEBUG' => true]); - } - public function testRoute(): void { - $r = $this->get('/'); - $this->assertStringContainsString('index', $r->getBody()->__toString()); + $this->fakeHttp()->get('/')->assertBodySame('index'); } public function testRoute2(): void { - $r = $this->post('/'); - $this->assertStringContainsString('method', $r->getBody()->__toString()); + $this->fakeHttp()->post('/')->assertBodySame('method'); } public function testRoute3(): void { - $r = $this->get('/page/test'); - - $this->assertSame('page-test', $r->getBody()->__toString()); + $this->fakeHttp()->get('/page/test')->assertBodySame('page-test'); } public function testRoute4(): void { - $r = $this->get('/page/about'); - - $this->assertSame('about', $r->getBody()->__toString()); + $this->fakeHttp()->get('/page/about')->assertBodySame('about'); } public function testRoutesWithoutNames(): void { - $r = $this->get('/nameless'); - $this->assertSame('index', $r->getBody()->__toString()); - - $r = $this->post('/nameless'); - $this->assertSame('method', $r->getBody()->__toString()); - - $r = $this->get('/nameless/route'); - $this->assertSame('route', $r->getBody()->__toString()); - } - - public function get( - $uri, - array $query = [], - array $headers = [], - array $cookies = [] - ): ResponseInterface { - return $this->app->getContainer()->runScope( - new Scope(ScopeName::Http), - fn (Http $http) => $http->handle($this->request($uri, 'GET', $query, $headers, $cookies)) - ); - } - - public function getWithAttributes( - $uri, - array $attributes, - array $headers = [] - ): ResponseInterface { - $r = $this->request($uri, 'GET', [], $headers, []); - foreach ($attributes as $k => $v) { - $r = $r->withAttribute($k, $v); - } - - return $this->app->getHttp()->handle($r); - } - - public function post( - $uri, - array $data = [], - array $headers = [], - array $cookies = [] - ): ResponseInterface { - return $this->app->getContainer()->runScope( - new Scope(ScopeName::Http), - fn (Http $http) => $http - ->handle($this->request($uri, 'POST', [], $headers, $cookies)->withParsedBody($data)) - ); - } - - public function request( - $uri, - string $method, - array $query = [], - array $headers = [], - array $cookies = [] - ): ServerRequestInterface { - $headers = array_merge([ - 'accept-language' => 'en' - ], $headers); - - /** @var ServerRequestFactoryInterface $factory */ - $factory = $this->app->getContainer()->get(ServerRequestFactoryInterface::class); - $request = $factory->createServerRequest($method, $uri); - - foreach ($headers as $name => $value) { - $request = $request->withAddedHeader($name, $value); - } - - return $request - ->withCookieParams($cookies) - ->withQueryParams($query); - } - - public function fetchCookies(array $header): array - { - $result = []; - foreach ($header as $line) { - $cookie = explode('=', $line); - $result[$cookie[0]] = rawurldecode(substr($cookie[1], 0, strpos($cookie[1], ';'))); - } - - return $result; + $this->fakeHttp()->get('/nameless')->assertBodySame('index'); + $this->fakeHttp()->post('/nameless')->assertBodySame('method'); + $this->fakeHttp()->get('/nameless/route')->assertBodySame('route'); } } diff --git a/src/AnnotatedRoutes/tests/RouteLocatorListenerTest.php b/src/AnnotatedRoutes/tests/RouteLocatorListenerTest.php index ce6751b2a..e79aa0990 100644 --- a/src/AnnotatedRoutes/tests/RouteLocatorListenerTest.php +++ b/src/AnnotatedRoutes/tests/RouteLocatorListenerTest.php @@ -4,6 +4,7 @@ namespace Spiral\Tests\Router; +use PHPUnit\Framework\TestCase; use Nyholm\Psr7\Factory\Psr17Factory; use Psr\Http\Message\UriFactoryInterface; use Spiral\Attributes\Factory; diff --git a/src/AnnotatedRoutes/tests/TestCase.php b/src/AnnotatedRoutes/tests/TestCase.php index 68face2db..5540a9c85 100644 --- a/src/AnnotatedRoutes/tests/TestCase.php +++ b/src/AnnotatedRoutes/tests/TestCase.php @@ -4,25 +4,37 @@ namespace Spiral\Tests\Router; -use PHPUnit\Framework\TestCase as BaseTestCase; -use Spiral\Boot\Environment; -use Spiral\Tests\Router\App\App; +use Spiral\Nyholm\Bootloader\NyholmBootloader; +use Spiral\Router\Bootloader\AnnotatedRoutesBootloader; +use Spiral\Testing\TestCase as BaseTestCase; /** * @requires function \Spiral\Framework\Kernel::init */ abstract class TestCase extends BaseTestCase { - /** - * @throws \Throwable - */ - protected function makeApp(array $env): App + public function defineBootloaders(): array { - $config = [ - 'root' => __DIR__ . '/App', - 'app' => __DIR__ . '/App', + return [ + NyholmBootloader::class, + AnnotatedRoutesBootloader::class, ]; + } + + public function rootDirectory(): string + { + return __DIR__; + } + + public function defineDirectories(string $root): array + { + return parent::defineDirectories($root) + ['app' => $root . '/App']; + } + + protected function tearDown(): void + { + parent::tearDown(); - return (App::create($config, false))->run(new Environment($env)); + $this->cleanUpRuntimeDirectory(); } } From 6ebb71721bdfa81d9917a5544711fe184125e248 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 25 Jan 2024 22:45:08 +0200 Subject: [PATCH 13/84] Simplify testHttpBinding test --- .../Bootloader/Http/HttpBootloaderTest.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/Framework/Bootloader/Http/HttpBootloaderTest.php b/tests/Framework/Bootloader/Http/HttpBootloaderTest.php index 367f03e9e..2f18464f4 100644 --- a/tests/Framework/Bootloader/Http/HttpBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/HttpBootloaderTest.php @@ -18,23 +18,15 @@ use Spiral\Framework\ScopeName; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class HttpBootloaderTest extends BaseTestCase { + #[TestScope(ScopeName::Http)] public function testHttpBinding(): void { - $this->getContainer()->runScope(new Scope(ScopeName::Http), function (Container $container): void { - $this->assertTrue($container->has(Http::class)); - - $instance1 = $container->get(Http::class); - $instance2 = $container->get(Http::class); - - $this->assertInstanceOf(Http::class, $instance1); - $this->assertInstanceOf(Http::class, $instance2); - - $this->assertSame($instance1, $instance2); - }); + $this->assertContainerBoundAsSingleton(Http::class, Http::class); } public function testDefaultInputBags(): void From 98385bee839390ae7f49161c14fcf27888c27f09 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 25 Jan 2024 22:52:00 +0200 Subject: [PATCH 14/84] Fix test app directory --- src/AnnotatedRoutes/tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AnnotatedRoutes/tests/TestCase.php b/src/AnnotatedRoutes/tests/TestCase.php index 5540a9c85..b3561f3fd 100644 --- a/src/AnnotatedRoutes/tests/TestCase.php +++ b/src/AnnotatedRoutes/tests/TestCase.php @@ -28,7 +28,7 @@ public function rootDirectory(): string public function defineDirectories(string $root): array { - return parent::defineDirectories($root) + ['app' => $root . '/App']; + return \array_merge(parent::defineDirectories($root), ['app' => $root . '/App']); } protected function tearDown(): void From a337cb47c2efbe7bcee736016ded8d970acf4334 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 29 Jan 2024 16:22:23 +0200 Subject: [PATCH 15/84] Add todo --- src/Http/src/Pipeline.php | 2 ++ src/Router/src/CoreHandler.php | 5 ++++- src/Telemetry/src/AbstractTracer.php | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Http/src/Pipeline.php b/src/Http/src/Pipeline.php index 8d25c190e..773afb0ea 100644 --- a/src/Http/src/Pipeline.php +++ b/src/Http/src/Pipeline.php @@ -93,6 +93,8 @@ public function handle(Request $request): Response } $handler = $this->handler; + + // TODO: Can we remove this scope? return $this->scope->runScope( [Request::class => $request], static fn (): Response => $handler->handle($request) diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index dea9a084c..e1a4699f7 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -97,7 +97,10 @@ public function handle(Request $request): Response : $this->action; // run the core withing PSR-7 Request/Response scope - /** @psalm-suppress InvalidArgument */ + /** + * @psalm-suppress InvalidArgument + * TODO: Can we bind all controller classes at the bootstrap stage? + */ $result = $this->scope->runScope( new Scope( name: ScopeName::HttpRequest, diff --git a/src/Telemetry/src/AbstractTracer.php b/src/Telemetry/src/AbstractTracer.php index 5596742c4..547e3ddab 100644 --- a/src/Telemetry/src/AbstractTracer.php +++ b/src/Telemetry/src/AbstractTracer.php @@ -26,6 +26,7 @@ public function __construct( */ final protected function runScope(Span $span, callable $callback): mixed { + // TODO: Can we remove this scope? return $this->scope->runScope([ SpanInterface::class => $span, TracerInterface::class => $this, From a3465ccb81f27154325a1365e63a307185c11ebb Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Fri, 16 Feb 2024 10:29:21 +0200 Subject: [PATCH 16/84] Change enum name from ScopeName to Spiral --- src/AnnotatedRoutes/tests/IntegrationTest.php | 3 +-- src/Core/src/BinderInterface.php | 3 +-- src/Core/tests/Attribute/ScopeTest.php | 2 -- src/Core/tests/Scope/UseCaseTest.php | 14 +++----------- src/Framework/Bootloader/Http/HttpBootloader.php | 6 +++--- src/Framework/Console/ConsoleDispatcher.php | 4 ++-- .../Framework/{ScopeName.php => Spiral.php} | 2 +- src/Hmvc/tests/CoreTest.php | 3 +-- src/Hmvc/tests/InterceptableCoreTest.php | 3 +-- src/Router/src/CoreHandler.php | 3 +-- tests/Framework/Attribute/DispatcherScopeTest.php | 4 ++-- .../AuthHttp/Middleware/AuthMiddlewareTest.php | 4 ++-- .../Bootloader/Http/HttpBootloaderTest.php | 5 ++--- tests/Framework/Http/AuthSessionTest.php | 4 ++-- tests/Framework/Http/ControllerTest.php | 4 ++-- tests/Framework/Http/CookiesTest.php | 4 ++-- tests/Framework/Http/FilterTest.php | 4 ++-- tests/Framework/Http/PaginationTest.php | 4 ++-- tests/Framework/Http/SessionTest.php | 4 ++-- .../Interceptor/PipelineInterceptorTest.php | 4 ++-- tests/Framework/KernelTest.php | 4 ++-- tests/Framework/Router/CoreHandlerTest.php | 8 ++++---- .../app/src/Dispatcher/DispatcherWithScopeName.php | 4 ++-- 23 files changed, 42 insertions(+), 58 deletions(-) rename src/Framework/Framework/{ScopeName.php => Spiral.php} (96%) diff --git a/src/AnnotatedRoutes/tests/IntegrationTest.php b/src/AnnotatedRoutes/tests/IntegrationTest.php index 33f057bcb..9424f463a 100644 --- a/src/AnnotatedRoutes/tests/IntegrationTest.php +++ b/src/AnnotatedRoutes/tests/IntegrationTest.php @@ -4,10 +4,9 @@ namespace Spiral\Tests\Router; -use Spiral\Framework\ScopeName; use Spiral\Testing\Attribute\TestScope; -#[TestScope(ScopeName::Http)] +#[TestScope('http')] final class IntegrationTest extends TestCase { public function testRoute(): void diff --git a/src/Core/src/BinderInterface.php b/src/Core/src/BinderInterface.php index 6dad42ded..41a0926c3 100644 --- a/src/Core/src/BinderInterface.php +++ b/src/Core/src/BinderInterface.php @@ -5,12 +5,11 @@ namespace Spiral\Core; use Spiral\Core\Container\InjectorInterface; -use Spiral\Framework\ScopeName; /** * Manages container bindings. * - * @method BinderInterface getBinder(string|ScopeName|null $scope = null) + * @method BinderInterface getBinder(string|\BackedEnum|null $scope = null) * * @psalm-type TResolver = class-string|non-empty-string|object|callable|array{class-string, non-empty-string} */ diff --git a/src/Core/tests/Attribute/ScopeTest.php b/src/Core/tests/Attribute/ScopeTest.php index 81c443b48..d4f6dbcd4 100644 --- a/src/Core/tests/Attribute/ScopeTest.php +++ b/src/Core/tests/Attribute/ScopeTest.php @@ -7,7 +7,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Spiral\Core\Attribute\Scope; -use Spiral\Framework\ScopeName; use Spiral\Tests\Core\Fixtures\ScopeEnum; final class ScopeTest extends TestCase @@ -23,7 +22,6 @@ public function testScope(string|\BackedEnum $name, string $expected): void public static function scopeNameDataProvider(): \Traversable { yield ['foo', 'foo']; - yield [ScopeName::HttpRequest, 'http.request']; yield [ScopeEnum::A, 'a']; } } diff --git a/src/Core/tests/Scope/UseCaseTest.php b/src/Core/tests/Scope/UseCaseTest.php index 88ea5bf13..3f442d769 100644 --- a/src/Core/tests/Scope/UseCaseTest.php +++ b/src/Core/tests/Scope/UseCaseTest.php @@ -11,7 +11,6 @@ use Spiral\Core\Config\Shared; use Spiral\Core\Container; use Spiral\Core\Scope; -use Spiral\Framework\ScopeName; use Spiral\Tests\Core\Fixtures\Bucket; use Spiral\Tests\Core\Fixtures\Factory; use Spiral\Tests\Core\Fixtures\SampleClass; @@ -328,13 +327,12 @@ function (ContainerInterface $c) { ); } - #[DataProvider('scopeEnumDataProvider')] - public function testScopeWithEnum(\BackedEnum $scope): void + public function testScopeWithEnum(): void { $root = new Container(); - $root->getBinder($scope)->bindSingleton('foo', SampleClass::class); + $root->getBinder(ScopeEnum::A)->bindSingleton('foo', SampleClass::class); - $root->runScope(new Scope($scope), function (Container $container) { + $root->runScope(new Scope(ScopeEnum::A), function (Container $container) { $this->assertTrue($container->has('foo')); $this->assertInstanceOf(SampleClass::class, $container->get('foo')); }); @@ -403,10 +401,4 @@ public function testHasInParentScopeWithScopeAttribute(): void }); }); } - - public static function scopeEnumDataProvider(): \Traversable - { - yield [ScopeName::HttpRequest, 'http.request']; - yield [ScopeEnum::A, 'a']; - } } diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index e776e4c59..5d33ca54e 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -16,7 +16,7 @@ use Spiral\Core\BinderInterface; use Spiral\Core\Container\Autowire; use Spiral\Core\InvokerInterface; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; @@ -44,7 +44,7 @@ public function defineDependencies(): array public function defineSingletons(): array { - $this->binder->getBinder(ScopeName::Http)->bindSingleton(Http::class, [self::class, 'httpCore']); + $this->binder->getBinder(Spiral::Http)->bindSingleton(Http::class, [self::class, 'httpCore']); /** * @deprecated since v3.12. Will be removed in v4.0. @@ -55,7 +55,7 @@ function (InvokerInterface $invoker, #[Proxy] ContainerInterface $container): Ht @trigger_error(\sprintf( 'Using `%s` outside of the `%s` scope is deprecated and will be impossible in version 4.0.', Http::class, - ScopeName::Http->value + Spiral::Http->value ), \E_USER_DEPRECATED); return $invoker->invoke([self::class, 'httpCore'], ['container' => $container]); diff --git a/src/Framework/Console/ConsoleDispatcher.php b/src/Framework/Console/ConsoleDispatcher.php index 34f6b6803..e8b60b36b 100644 --- a/src/Framework/Console/ConsoleDispatcher.php +++ b/src/Framework/Console/ConsoleDispatcher.php @@ -12,7 +12,7 @@ use Spiral\Console\Logger\DebugListener; use Spiral\Exceptions\ExceptionHandlerInterface; use Spiral\Exceptions\Verbosity; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutput; @@ -22,7 +22,7 @@ /** * Manages Console commands and exception. Lazy loads console service. */ -#[DispatcherScope(scope: ScopeName::Console)] +#[DispatcherScope(scope: Spiral::Console)] final class ConsoleDispatcher implements DispatcherInterface { public function __construct( diff --git a/src/Framework/Framework/ScopeName.php b/src/Framework/Framework/Spiral.php similarity index 96% rename from src/Framework/Framework/ScopeName.php rename to src/Framework/Framework/Spiral.php index 564af59c8..53497cf93 100644 --- a/src/Framework/Framework/ScopeName.php +++ b/src/Framework/Framework/Spiral.php @@ -4,7 +4,7 @@ namespace Spiral\Framework; -enum ScopeName: string +enum Spiral: string { case Http = 'http'; case HttpRequest = 'http.request'; diff --git a/src/Hmvc/tests/CoreTest.php b/src/Hmvc/tests/CoreTest.php index 75dea0e47..6cf9ec85b 100644 --- a/src/Hmvc/tests/CoreTest.php +++ b/src/Hmvc/tests/CoreTest.php @@ -5,14 +5,13 @@ namespace Spiral\Tests\Core; use Spiral\Core\Exception\ControllerException; -use Spiral\Framework\ScopeName; use Spiral\Testing\Attribute\TestScope; use Spiral\Testing\TestCase; use Spiral\Tests\Core\Fixtures\CleanController; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; -#[TestScope(ScopeName::Http)] +#[TestScope('http')] final class CoreTest extends TestCase { public function testCallAction(): void diff --git a/src/Hmvc/tests/InterceptableCoreTest.php b/src/Hmvc/tests/InterceptableCoreTest.php index 047344d37..371661645 100644 --- a/src/Hmvc/tests/InterceptableCoreTest.php +++ b/src/Hmvc/tests/InterceptableCoreTest.php @@ -7,13 +7,12 @@ use Spiral\Core\Exception\InterceptorException; use Spiral\Core\InterceptableCore; use Spiral\Core\InterceptorPipeline; -use Spiral\Framework\ScopeName; use Spiral\Testing\Attribute\TestScope; use Spiral\Testing\TestCase; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; -#[TestScope(ScopeName::Http)] +#[TestScope('http')] final class InterceptableCoreTest extends TestCase { public function testNoInterceptors(): void diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index e1a4699f7..5c918fe56 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -12,7 +12,6 @@ use Spiral\Core\Exception\ControllerException; use Spiral\Core\Scope; use Spiral\Core\ScopeInterface; -use Spiral\Framework\ScopeName; use Spiral\Http\Exception\ClientException; use Spiral\Http\Exception\ClientException\BadRequestException; use Spiral\Http\Exception\ClientException\ForbiddenException; @@ -103,7 +102,7 @@ public function handle(Request $request): Response */ $result = $this->scope->runScope( new Scope( - name: ScopeName::HttpRequest, + name: 'http.request', bindings: [Request::class => $request, Response::class => $response, $controller => $controller], ), fn (): mixed => $this->tracer->trace( diff --git a/tests/Framework/Attribute/DispatcherScopeTest.php b/tests/Framework/Attribute/DispatcherScopeTest.php index 8d9a5f733..addf705a1 100644 --- a/tests/Framework/Attribute/DispatcherScopeTest.php +++ b/tests/Framework/Attribute/DispatcherScopeTest.php @@ -11,7 +11,7 @@ use Spiral\App\Dispatcher\Scope; use Spiral\Boot\AbstractKernel; use Spiral\Core\Container; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Tests\Framework\BaseTestCase; final class DispatcherScopeTest extends BaseTestCase @@ -33,7 +33,7 @@ public function testDispatcherScope(string $dispatcher, string|\BackedEnum $scop public static function dispatchersDataProvider(): \Traversable { - yield [DispatcherWithScopeName::class, ScopeName::Console]; + yield [DispatcherWithScopeName::class, Spiral::Console]; yield [DispatcherWithCustomEnum::class, Scope::Custom]; yield [DispatcherWithStringScope::class, 'test']; } diff --git a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php index d60e37b4c..7b9e87647 100644 --- a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php +++ b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php @@ -8,7 +8,7 @@ use Spiral\Auth\TokenStorageInterface; use Spiral\Auth\TokenStorageScope; use Spiral\Core\Container\Autowire; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; @@ -21,7 +21,7 @@ public function setUp(): void $this->enableMiddlewares(); } - #[TestScope(ScopeName::Http)] + #[TestScope(Spiral::Http)] public function testTokenStorageInterfaceShouldBeBound(): void { $storage = $this->createMock(TokenStorageInterface::class); diff --git a/tests/Framework/Bootloader/Http/HttpBootloaderTest.php b/tests/Framework/Bootloader/Http/HttpBootloaderTest.php index 2f18464f4..7b258e802 100644 --- a/tests/Framework/Bootloader/Http/HttpBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/HttpBootloaderTest.php @@ -14,8 +14,7 @@ use Spiral\Config\LoaderInterface; use Spiral\Core\Container; use Spiral\Core\Container\Autowire; -use Spiral\Core\Scope; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Testing\Attribute\TestScope; @@ -23,7 +22,7 @@ final class HttpBootloaderTest extends BaseTestCase { - #[TestScope(ScopeName::Http)] + #[TestScope(Spiral::Http)] public function testHttpBinding(): void { $this->assertContainerBoundAsSingleton(Http::class, Http::class); diff --git a/tests/Framework/Http/AuthSessionTest.php b/tests/Framework/Http/AuthSessionTest.php index 134fe3215..9ad68bcb6 100644 --- a/tests/Framework/Http/AuthSessionTest.php +++ b/tests/Framework/Http/AuthSessionTest.php @@ -4,11 +4,11 @@ namespace Spiral\Tests\Framework\Http; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class AuthSessionTest extends HttpTestCase { public const ENV = [ diff --git a/tests/Framework/Http/ControllerTest.php b/tests/Framework/Http/ControllerTest.php index d43fa4a73..a9d78c2fb 100644 --- a/tests/Framework/Http/ControllerTest.php +++ b/tests/Framework/Http/ControllerTest.php @@ -5,11 +5,11 @@ namespace Spiral\Tests\Framework\Http; use Nyholm\Psr7\Factory\Psr17Factory; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class ControllerTest extends HttpTestCase { public function testIndexAction(): void diff --git a/tests/Framework/Http/CookiesTest.php b/tests/Framework/Http/CookiesTest.php index 8bce34991..a5af8e9d4 100644 --- a/tests/Framework/Http/CookiesTest.php +++ b/tests/Framework/Http/CookiesTest.php @@ -8,11 +8,11 @@ use Spiral\Cookies\CookieManager; use Spiral\Core\Exception\ScopeException; use Spiral\Encrypter\EncrypterInterface; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class CookiesTest extends HttpTestCase { public const ENV = [ diff --git a/tests/Framework/Http/FilterTest.php b/tests/Framework/Http/FilterTest.php index a2c5e2121..0c287b768 100644 --- a/tests/Framework/Http/FilterTest.php +++ b/tests/Framework/Http/FilterTest.php @@ -4,11 +4,11 @@ namespace Spiral\Tests\Framework\Http; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class FilterTest extends HttpTestCase { public function testValid(): void diff --git a/tests/Framework/Http/PaginationTest.php b/tests/Framework/Http/PaginationTest.php index 6b090d121..024eefd42 100644 --- a/tests/Framework/Http/PaginationTest.php +++ b/tests/Framework/Http/PaginationTest.php @@ -5,12 +5,12 @@ namespace Spiral\Tests\Framework\Http; use Spiral\Core\Exception\ScopeException; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Http\PaginationFactory; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class PaginationTest extends HttpTestCase { public function testPaginate(): void diff --git a/tests/Framework/Http/SessionTest.php b/tests/Framework/Http/SessionTest.php index 5a362a432..367e3a12c 100644 --- a/tests/Framework/Http/SessionTest.php +++ b/tests/Framework/Http/SessionTest.php @@ -4,12 +4,12 @@ namespace Spiral\Tests\Framework\Http; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Session\SessionInterface; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class SessionTest extends HttpTestCase { public function setUp(): void diff --git a/tests/Framework/Interceptor/PipelineInterceptorTest.php b/tests/Framework/Interceptor/PipelineInterceptorTest.php index e06e625cc..68eadd3ae 100644 --- a/tests/Framework/Interceptor/PipelineInterceptorTest.php +++ b/tests/Framework/Interceptor/PipelineInterceptorTest.php @@ -4,11 +4,11 @@ namespace Spiral\Tests\Framework\Interceptor; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(ScopeName::Http)] +#[TestScope(Spiral::Http)] final class PipelineInterceptorTest extends HttpTestCase { public function testWithoutPipeline(): void diff --git a/tests/Framework/KernelTest.php b/tests/Framework/KernelTest.php index 669f224d4..efe16d99d 100644 --- a/tests/Framework/KernelTest.php +++ b/tests/Framework/KernelTest.php @@ -16,7 +16,7 @@ use Spiral\Boot\Exception\BootException; use Spiral\App\TestApp; use Spiral\Core\Container; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use stdClass; class KernelTest extends BaseTestCase @@ -136,7 +136,7 @@ public function testDispatchersShouldBeBoundInCorrectScope(string $dispatcher, s public static function dispatchersDataProvider(): \Traversable { - yield [DispatcherWithScopeName::class, ScopeName::Console->value]; + yield [DispatcherWithScopeName::class, Spiral::Console->value]; yield [DispatcherWithCustomEnum::class, Scope::Custom->value]; yield [DispatcherWithStringScope::class, 'test']; } diff --git a/tests/Framework/Router/CoreHandlerTest.php b/tests/Framework/Router/CoreHandlerTest.php index df19dd5a6..385f3f933 100644 --- a/tests/Framework/Router/CoreHandlerTest.php +++ b/tests/Framework/Router/CoreHandlerTest.php @@ -4,16 +4,16 @@ namespace Spiral\Tests\Framework\Router; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; final class CoreHandlerTest extends HttpTestCase { - #[TestScope(ScopeName::Http)] + #[TestScope(Spiral::Http)] public function testHttpRequestScope(): void { - $this->fakeHttp()->get('/scope/construct')->assertBodySame(ScopeName::HttpRequest->value); - $this->fakeHttp()->get('/scope/method')->assertBodySame(ScopeName::HttpRequest->value); + $this->fakeHttp()->get('/scope/construct')->assertBodySame(Spiral::HttpRequest->value); + $this->fakeHttp()->get('/scope/method')->assertBodySame(Spiral::HttpRequest->value); } } diff --git a/tests/app/src/Dispatcher/DispatcherWithScopeName.php b/tests/app/src/Dispatcher/DispatcherWithScopeName.php index 47abf6326..e3cbd30d7 100644 --- a/tests/app/src/Dispatcher/DispatcherWithScopeName.php +++ b/tests/app/src/Dispatcher/DispatcherWithScopeName.php @@ -5,9 +5,9 @@ namespace Spiral\App\Dispatcher; use Spiral\Attribute\DispatcherScope; -use Spiral\Framework\ScopeName; +use Spiral\Framework\Spiral; -#[DispatcherScope(scope: ScopeName::Console)] +#[DispatcherScope(scope: Spiral::Console)] final class DispatcherWithScopeName extends AbstractDispatcher { } From 0e87fd460949f0f386bdc3012278f81ba644cb97 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Fri, 16 Feb 2024 13:52:26 +0200 Subject: [PATCH 17/84] Add PaginationProviderInterface binding in scope --- src/Core/src/Config/DeprecationProxy.php | 2 +- src/Core/tests/Internal/Proxy/ProxyTest.php | 30 +++++++++++++++++ .../Bootloader/Http/PaginationBootloader.php | 33 +++++++++++++++---- src/Framework/Http/PaginationFactory.php | 3 ++ tests/app/src/Controller/TestController.php | 4 +-- 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/Core/src/Config/DeprecationProxy.php b/src/Core/src/Config/DeprecationProxy.php index fa63fd8db..8d6bba84c 100644 --- a/src/Core/src/Config/DeprecationProxy.php +++ b/src/Core/src/Config/DeprecationProxy.php @@ -34,7 +34,7 @@ public function getInterface(): string $message = $this->message ?? \sprintf( 'Using `%s` outside of the `%s` scope is deprecated and will be impossible in version %s.', $this->interface, - $this->scope, + $this->scope instanceof \BackedEnum ? $this->scope->value : $this->scope, $this->version ); diff --git a/src/Core/tests/Internal/Proxy/ProxyTest.php b/src/Core/tests/Internal/Proxy/ProxyTest.php index 7e6ae64ed..33888bad4 100644 --- a/src/Core/tests/Internal/Proxy/ProxyTest.php +++ b/src/Core/tests/Internal/Proxy/ProxyTest.php @@ -12,6 +12,7 @@ use Spiral\Core\Container; use Spiral\Core\Exception\Container\ContainerException; use Spiral\Core\Scope; +use Spiral\Tests\Core\Fixtures\ScopeEnum; use Spiral\Tests\Core\Internal\Proxy\Stub\EmptyInterface; use Spiral\Tests\Core\Internal\Proxy\Stub\MockInterface; use Spiral\Tests\Core\Internal\Proxy\Stub\MockInterfaceImpl; @@ -281,6 +282,35 @@ interface: $interface, \restore_error_handler(); } + #[DataProvider('interfacesProvider')] + #[WithoutErrorHandler] + public function testDeprecationProxyConfigWithEnumScope(string $interface): void + { + \set_error_handler(static function (int $errno, string $error) use ($interface): void { + self::assertSame( + \sprintf('Using `%s` outside of the `a` scope is deprecated and will be ' . + 'impossible in version 4.0.', $interface), + $error + ); + }); + + $root = new Container(); + $root->getBinder('foo')->bindSingleton($interface, Stub\MockInterfaceImpl::class); + $root->bindSingleton($interface, new Config\DeprecationProxy($interface, true, ScopeEnum::A, '4.0')); + + $proxy = $root->get($interface); + $this->assertInstanceOf($interface, $proxy); + + $root->runScope(new Scope('foo'), static function () use ($proxy) { + $proxy->bar(name: 'foo'); // Possible to run + self::assertSame('foo', $proxy->baz('foo', 42)); + self::assertSame(123, $proxy->qux(age: 123)); + self::assertSame(69, $proxy->space(test age: 69)); + }); + + \restore_error_handler(); + } + #[DataProvider('interfacesProvider')] #[WithoutErrorHandler] public function testDeprecationProxyConfigDontThrowIfNotConstructed(string $interface): void diff --git a/src/Framework/Bootloader/Http/PaginationBootloader.php b/src/Framework/Bootloader/Http/PaginationBootloader.php index d74573ce2..6f392d029 100644 --- a/src/Framework/Bootloader/Http/PaginationBootloader.php +++ b/src/Framework/Bootloader/Http/PaginationBootloader.php @@ -5,16 +5,37 @@ namespace Spiral\Bootloader\Http; use Spiral\Boot\Bootloader\Bootloader; +use Spiral\Core\BinderInterface; +use Spiral\Core\Config\DeprecationProxy; +use Spiral\Framework\Spiral; use Spiral\Http\PaginationFactory; use Spiral\Pagination\PaginationProviderInterface; final class PaginationBootloader extends Bootloader { - protected const DEPENDENCIES = [ - HttpBootloader::class, - ]; + public function __construct( + private readonly BinderInterface $binder, + ) { + } - protected const SINGLETONS = [ - PaginationProviderInterface::class => PaginationFactory::class, - ]; + public function defineDependencies(): array + { + return [ + HttpBootloader::class, + ]; + } + + public function defineSingletons(): array + { + $this->binder + ->getBinder(Spiral::Http) + ->bindSingleton(PaginationProviderInterface::class, PaginationFactory::class); + + $this->binder->bind( + PaginationProviderInterface::class, + new DeprecationProxy(PaginationProviderInterface::class, true, Spiral::Http, '4.0') + ); + + return []; + } } diff --git a/src/Framework/Http/PaginationFactory.php b/src/Framework/Http/PaginationFactory.php index 65fc98bc6..d103cda0b 100644 --- a/src/Framework/Http/PaginationFactory.php +++ b/src/Framework/Http/PaginationFactory.php @@ -6,8 +6,10 @@ use Psr\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; +use Spiral\Core\Attribute\Scope; use Spiral\Core\Exception\ScopeException; use Spiral\Core\FactoryInterface; +use Spiral\Framework\Spiral; use Spiral\Pagination\PaginationProviderInterface; use Spiral\Pagination\Paginator; use Spiral\Pagination\PaginatorInterface; @@ -15,6 +17,7 @@ /** * Paginators factory binded to active request scope in order to select page number. */ +#[Scope(Spiral::Http)] final class PaginationFactory implements PaginationProviderInterface { public function __construct( diff --git a/tests/app/src/Controller/TestController.php b/tests/app/src/Controller/TestController.php index 3e2afb5bb..27af8e6dd 100644 --- a/tests/app/src/Controller/TestController.php +++ b/tests/app/src/Controller/TestController.php @@ -8,7 +8,7 @@ use Spiral\App\Request\BadRequest; use Spiral\App\Request\TestRequest; use Spiral\Filter\InputScope; -use Spiral\Http\PaginationFactory; +use Spiral\Pagination\PaginationProviderInterface; use Spiral\Pagination\Paginator; use Spiral\Router\RouteInterface; use Spiral\Translator\Traits\TranslatorTrait; @@ -22,7 +22,7 @@ public function index(string $name = 'Dave') return "Hello, {$name}."; } - public function paginate(PaginationFactory $paginationFactory) + public function paginate(PaginationProviderInterface $paginationFactory): int { /** @var Paginator $p */ $p = $paginationFactory->createPaginator('page'); From 1e65f861a1688c347f1605391a3e0355da2f5781 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Fri, 16 Feb 2024 14:33:08 +0200 Subject: [PATCH 18/84] Add unit test --- .../Http/PaginationBootloaderTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php b/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php index a3b51f2d2..bb7f8fab9 100644 --- a/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php @@ -4,14 +4,33 @@ namespace Framework\Bootloader\Http; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use Spiral\Framework\Spiral; use Spiral\Http\PaginationFactory; use Spiral\Pagination\PaginationProviderInterface; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class PaginationBootloaderTest extends BaseTestCase { + #[TestScope(Spiral::Http)] public function testPaginationProviderInterfaceBinding(): void { $this->assertContainerBoundAsSingleton(PaginationProviderInterface::class, PaginationFactory::class); } + + #[WithoutErrorHandler] + public function testPaginationProviderInterfaceBindingInRootScope(): void + { + \set_error_handler(static function (int $errno, string $error): void { + self::assertSame(\sprintf( + 'Using `%s` outside of the `http` scope is deprecated and will be impossible in version 4.0.', + PaginationProviderInterface::class + ), $error); + }); + + $this->assertContainerBoundAsSingleton(PaginationProviderInterface::class, PaginationProviderInterface::class); + + \restore_error_handler(); + } } From 3a2f6290c1c657756c23a7b2d12aa79fb665962a Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Fri, 16 Feb 2024 16:49:44 +0200 Subject: [PATCH 19/84] Fix scope --- src/Framework/Bootloader/Http/PaginationBootloader.php | 4 ++-- src/Framework/Http/PaginationFactory.php | 2 +- tests/Framework/Bootloader/Http/PaginationBootloaderTest.php | 4 ++-- tests/Framework/Http/PaginationTest.php | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Framework/Bootloader/Http/PaginationBootloader.php b/src/Framework/Bootloader/Http/PaginationBootloader.php index 6f392d029..0d4a0c28b 100644 --- a/src/Framework/Bootloader/Http/PaginationBootloader.php +++ b/src/Framework/Bootloader/Http/PaginationBootloader.php @@ -28,12 +28,12 @@ public function defineDependencies(): array public function defineSingletons(): array { $this->binder - ->getBinder(Spiral::Http) + ->getBinder(Spiral::HttpRequest) ->bindSingleton(PaginationProviderInterface::class, PaginationFactory::class); $this->binder->bind( PaginationProviderInterface::class, - new DeprecationProxy(PaginationProviderInterface::class, true, Spiral::Http, '4.0') + new DeprecationProxy(PaginationProviderInterface::class, true, Spiral::HttpRequest, '4.0') ); return []; diff --git a/src/Framework/Http/PaginationFactory.php b/src/Framework/Http/PaginationFactory.php index d103cda0b..cf644e4b9 100644 --- a/src/Framework/Http/PaginationFactory.php +++ b/src/Framework/Http/PaginationFactory.php @@ -17,7 +17,7 @@ /** * Paginators factory binded to active request scope in order to select page number. */ -#[Scope(Spiral::Http)] +#[Scope(Spiral::HttpRequest)] final class PaginationFactory implements PaginationProviderInterface { public function __construct( diff --git a/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php b/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php index bb7f8fab9..5147677a8 100644 --- a/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/PaginationBootloaderTest.php @@ -13,7 +13,7 @@ final class PaginationBootloaderTest extends BaseTestCase { - #[TestScope(Spiral::Http)] + #[TestScope(Spiral::HttpRequest)] public function testPaginationProviderInterfaceBinding(): void { $this->assertContainerBoundAsSingleton(PaginationProviderInterface::class, PaginationFactory::class); @@ -24,7 +24,7 @@ public function testPaginationProviderInterfaceBindingInRootScope(): void { \set_error_handler(static function (int $errno, string $error): void { self::assertSame(\sprintf( - 'Using `%s` outside of the `http` scope is deprecated and will be impossible in version 4.0.', + 'Using `%s` outside of the `http.request` scope is deprecated and will be impossible in version 4.0.', PaginationProviderInterface::class ), $error); }); diff --git a/tests/Framework/Http/PaginationTest.php b/tests/Framework/Http/PaginationTest.php index 024eefd42..23eb63143 100644 --- a/tests/Framework/Http/PaginationTest.php +++ b/tests/Framework/Http/PaginationTest.php @@ -18,6 +18,7 @@ public function testPaginate(): void $this->fakeHttp()->get('/paginate')->assertBodySame('1'); } + #[TestScope(Spiral::HttpRequest)] public function testPaginateError(): void { $this->expectException(ScopeException::class); From 5147e08469d84560198aabfb11d1dd2e407c6ac8 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Sat, 17 Feb 2024 23:15:37 +0200 Subject: [PATCH 20/84] Using HTTP scopes --- .../src/Middleware/AuthMiddleware.php | 20 ++-- .../Middleware/AuthTransportMiddleware.php | 3 + .../AuthTransportWithStorageMiddleware.php | 3 + ...AuthTransportWithStorageMiddlewareTest.php | 69 +++++++----- src/AuthHttp/tests/CookieTransportTest.php | 4 +- src/AuthHttp/tests/HeaderTransportTest.php | 16 +-- .../tests/Middleware/AuthMiddlewareTest.php | 6 +- .../Firewall/BaseFirewallTestCase.php | 6 +- src/AuthHttp/tests/ScopedTestCase.php | 19 ++++ src/Cookies/src/CookieQueue.php | 3 + src/Cookies/tests/CookiesTest.php | 54 ++++----- src/Csrf/tests/CsrfTest.php | 14 ++- src/Filters/tests/InputScopeTest.php | 10 ++ src/Framework/Auth/AuthScope.php | 4 +- .../Auth/Exception/InvalidAuthContext.php | 19 ++++ src/Framework/Auth/TokenStorageScope.php | 6 +- .../Bootloader/Auth/HttpAuthBootloader.php | 68 ++++++++--- .../Bootloader/Http/CookiesBootloader.php | 21 ++-- .../Bootloader/Http/HttpBootloader.php | 2 + .../Bootloader/Http/RouterBootloader.php | 48 +++++--- .../Bootloader/Http/SessionBootloader.php | 35 +++++- .../Bootloader/Security/FiltersBootloader.php | 26 ++++- src/Framework/Cookies/CookieManager.php | 3 +- .../Exception/InvalidSessionContext.php | 19 ++++ .../Session/Middleware/SessionMiddleware.php | 8 +- src/Framework/Session/SectionScope.php | 2 +- src/Framework/Session/SessionScope.php | 4 +- src/Http/src/CurrentRequest.php | 28 +++++ src/Http/src/Http.php | 4 +- src/Http/src/Pipeline.php | 16 +-- src/Http/src/Request/InputManager.php | 3 +- src/Http/tests/HttpTest.php | 35 +----- src/Http/tests/PipelineTest.php | 19 +--- src/Http/tests/ScopedTestCase.php | 30 +++++ src/Router/tests/BaseTestCase.php | 16 ++- src/Router/tests/PipelineFactoryTest.php | 21 ++-- .../Middleware/AuthMiddlewareTest.php | 8 -- .../Bootloader/Http/CookiesBootloaderTest.php | 6 +- .../Http/HttpAuthBootloaderTest.php | 4 +- .../Router/RouterBootloaderTest.php | 4 + .../Security/FiltersBootloaderTest.php | 3 + tests/Framework/Http/CookiesTest.php | 32 ++++-- tests/Framework/Http/CurrentRequestTest.php | 106 ++++++++++++++++++ 43 files changed, 591 insertions(+), 236 deletions(-) create mode 100644 src/AuthHttp/tests/ScopedTestCase.php create mode 100644 src/Framework/Auth/Exception/InvalidAuthContext.php create mode 100644 src/Framework/Session/Exception/InvalidSessionContext.php create mode 100644 src/Http/src/CurrentRequest.php create mode 100644 src/Http/tests/ScopedTestCase.php create mode 100644 tests/Framework/Http/CurrentRequestTest.php diff --git a/src/AuthHttp/src/Middleware/AuthMiddleware.php b/src/AuthHttp/src/Middleware/AuthMiddleware.php index 73c5898bb..aed474282 100644 --- a/src/AuthHttp/src/Middleware/AuthMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthMiddleware.php @@ -22,13 +22,17 @@ final class AuthMiddleware implements MiddlewareInterface { public const ATTRIBUTE = 'authContext'; + public const TOKEN_STORAGE_ATTRIBUTE = 'tokenStorage'; + /** + * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + */ public function __construct( private readonly ScopeInterface $scope, private readonly ActorProviderInterface $actorProvider, private readonly TokenStorageInterface $tokenStorage, private readonly TransportRegistry $transportRegistry, - private readonly ?EventDispatcherInterface $eventDispatcher = null + private readonly ?EventDispatcherInterface $eventDispatcher = null, ) { } @@ -39,12 +43,10 @@ public function process(Request $request, RequestHandlerInterface $handler): Res { $authContext = $this->initContext($request, new AuthContext($this->actorProvider, $this->eventDispatcher)); - $response = $this->scope->runScope( - [ - AuthContextInterface::class => $authContext, - TokenStorageInterface::class => $this->tokenStorage, - ], - static fn () => $handler->handle($request->withAttribute(self::ATTRIBUTE, $authContext)) + $response = $handler->handle( + $request + ->withAttribute(self::ATTRIBUTE, $authContext) + ->withAttribute(self::TOKEN_STORAGE_ATTRIBUTE, $this->tokenStorage), ); return $this->closeContext($request, $response, $authContext); @@ -85,7 +87,7 @@ private function closeContext(Request $request, Response $response, AuthContextI return $transport->removeToken( $request, $response, - $authContext->getToken()->getID() + $authContext->getToken()->getID(), ); } @@ -93,7 +95,7 @@ private function closeContext(Request $request, Response $response, AuthContextI $request, $response, $authContext->getToken()->getID(), - $authContext->getToken()->getExpiresAt() + $authContext->getToken()->getExpiresAt(), ); } } diff --git a/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php b/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php index c64576732..883128516 100644 --- a/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php @@ -21,6 +21,9 @@ final class AuthTransportMiddleware implements MiddlewareInterface { private readonly AuthMiddleware $authMiddleware; + /** + * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + */ public function __construct( string $transportName, ScopeInterface $scope, diff --git a/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php b/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php index 92281a010..4470a16fc 100644 --- a/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php @@ -21,6 +21,9 @@ final class AuthTransportWithStorageMiddleware implements MiddlewareInterface { private readonly MiddlewareInterface $authMiddleware; + /** + * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + */ public function __construct( string $transportName, ScopeInterface $scope, diff --git a/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php b/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php index 30f5a41c3..5e80da6bb 100644 --- a/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php +++ b/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php @@ -4,8 +4,6 @@ namespace Spiral\Tests\Auth; -use Mockery as m; -use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -19,54 +17,63 @@ use Spiral\Auth\TransportRegistry; use Spiral\Core\ScopeInterface; -final class AuthTransportWithStorageMiddlewareTest extends TestCase +final class AuthTransportWithStorageMiddlewareTest extends ScopedTestCase { public function testProcessMiddlewareWithTokenStorageProvider(): void { - $serverRequest = m::mock(ServerRequestInterface::class); - $storageProvider = m::mock(TokenStorageProviderInterface::class); - $scope = m::mock(ScopeInterface::class); - $response = m::mock(ResponseInterface::class); + $storageProvider = $this->createMock(TokenStorageProviderInterface::class); + $storageProvider + ->expects($this->once()) + ->method('getStorage') + ->with('session') + ->willReturn($tokenStorage = $this->createMock(TokenStorageInterface::class)); - $storageProvider->shouldReceive('getStorage')->once()->with('session')->andReturn( - $tokenStorage = m::mock(TokenStorageInterface::class) - ); + $matcher = $this->exactly(2); + $request = $this->createMock(ServerRequestInterface::class); + $request + ->expects($this->exactly(2)) + ->method('withAttribute') + ->willReturnCallback(function (string $key, string $value) use ($matcher, $tokenStorage) { + match ($matcher->numberOfInvocations()) { + 1 => $this->assertInstanceOf(AuthContextInterface::class, $value), + 2 => $this->assertSame($tokenStorage, $value), + }; + }) + ->willReturnSelf(); + + $response = $this->createMock(ResponseInterface::class); $registry = new TransportRegistry(); - $registry->setTransport('header', $transport = m::mock(HttpTransportInterface::class)); + $registry->setTransport('header', $transport = $this->createMock(HttpTransportInterface::class)); - $transport->shouldReceive('fetchToken')->once()->with($serverRequest)->andReturn('fooToken'); - $transport->shouldReceive('commitToken')->once()->with($serverRequest, $response, '123', null) - ->andReturn($response); + $transport->expects($this->once())->method('fetchToken')->with($request)->willReturn('fooToken'); + $transport->expects($this->once())->method('commitToken')->with($request, $response, '123', null) + ->willReturn($response); - $tokenStorage->shouldReceive('load')->once()->with('fooToken')->andReturn( - $token = m::mock(TokenInterface::class) - ); + $tokenStorage + ->expects($this->once()) + ->method('load') + ->with('fooToken') + ->willReturn($token = $this->createMock(TokenInterface::class)); - $scope - ->shouldReceive('runScope') - ->once() - ->withArgs( - fn(array $bindings, callable $callback) => $bindings[AuthContextInterface::class] - ->getToken() instanceof $token - ) - ->andReturn($response); - - $token->shouldReceive('getID')->once()->andReturn('123'); - $token->shouldReceive('getExpiresAt')->once()->andReturnNull(); + $token->expects($this->once())->method('getID')->willReturn('123'); + $token->expects($this->once())->method('getExpiresAt')->willReturn(null); $middleware = new AuthTransportWithStorageMiddleware( 'header', - $scope, - m::mock(ActorProviderInterface::class), + $this->createMock(ScopeInterface::class), + $this->createMock(ActorProviderInterface::class), $storageProvider, $registry, storage: 'session' ); + $handler = $this->createMock(RequestHandlerInterface::class); + $handler->expects($this->once())->method('handle')->with($request)->willReturn($response); + $this->assertSame( $response, - $middleware->process($serverRequest, m::mock(RequestHandlerInterface::class)) + $middleware->process($request, $handler) ); } } diff --git a/src/AuthHttp/tests/CookieTransportTest.php b/src/AuthHttp/tests/CookieTransportTest.php index 86fcc88d9..a0923146d 100644 --- a/src/AuthHttp/tests/CookieTransportTest.php +++ b/src/AuthHttp/tests/CookieTransportTest.php @@ -20,7 +20,7 @@ use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; use Spiral\Tests\Auth\Stub\TestAuthHttpToken; -class CookieTransportTest extends BaseTestCase +final class CookieTransportTest extends ScopedTestCase { public function testCookieToken(): void { @@ -175,7 +175,7 @@ protected function getCore(HttpTransportInterface $transport): Http $http = new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/HeaderTransportTest.php b/src/AuthHttp/tests/HeaderTransportTest.php index e12702206..6b587dbc3 100644 --- a/src/AuthHttp/tests/HeaderTransportTest.php +++ b/src/AuthHttp/tests/HeaderTransportTest.php @@ -4,35 +4,23 @@ namespace Spiral\Tests\Auth; -use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Spiral\Auth\HttpTransportInterface; use Spiral\Auth\Middleware\AuthMiddleware; use Spiral\Auth\Transport\HeaderTransport; use Spiral\Auth\TransportRegistry; -use Spiral\Core\Container; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; -use Spiral\Telemetry\NullTracer; -use Spiral\Telemetry\TracerInterface; use Spiral\Tests\Auth\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; use Spiral\Tests\Auth\Stub\TestAuthHttpProvider; use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; use Spiral\Tests\Auth\Stub\TestAuthHttpToken; -class HeaderTransportTest extends TestCase +final class HeaderTransportTest extends ScopedTestCase { - private Container $container; - - public function setUp(): void - { - $this->container = new Container(); - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); - } - public function testHeaderToken(): void { $http = $this->getCore(new HeaderTransport()); @@ -168,7 +156,7 @@ protected function getCore(HttpTransportInterface $transport): Http $http = new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php b/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php index 8031bbf1f..90ec2ba87 100644 --- a/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php +++ b/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php @@ -12,13 +12,13 @@ use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; -use Spiral\Tests\Auth\BaseTestCase; use Spiral\Tests\Auth\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; +use Spiral\Tests\Auth\ScopedTestCase; use Spiral\Tests\Auth\Stub\TestAuthHttpProvider; use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; -class AuthMiddlewareTest extends BaseTestCase +final class AuthMiddlewareTest extends ScopedTestCase { public function testAttributeRead(): void { @@ -84,7 +84,7 @@ protected function getCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php b/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php index c6feb52e6..fdf70bfd7 100644 --- a/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php +++ b/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php @@ -11,12 +11,12 @@ use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; -use Spiral\Tests\Auth\BaseTestCase; use Spiral\Tests\Auth\Diactoros\ResponseFactory; +use Spiral\Tests\Auth\ScopedTestCase; use Spiral\Tests\Auth\Stub\TestAuthHttpProvider; use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; -abstract class BaseFirewallTestCase extends BaseTestCase +abstract class BaseFirewallTestCase extends ScopedTestCase { protected function getCore(AbstractFirewall $firewall, HttpTransportInterface $transport): Http { @@ -30,7 +30,7 @@ protected function getCore(AbstractFirewall $firewall, HttpTransportInterface $t $http = new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/ScopedTestCase.php b/src/AuthHttp/tests/ScopedTestCase.php new file mode 100644 index 000000000..573f3db1a --- /dev/null +++ b/src/AuthHttp/tests/ScopedTestCase.php @@ -0,0 +1,19 @@ +container->runScope(new Scope('http'), function (Container $container): mixed { + $this->container = $container; + return parent::runTest(); + }); + } +} diff --git a/src/Cookies/src/CookieQueue.php b/src/Cookies/src/CookieQueue.php index 7b83fab91..699528f61 100644 --- a/src/Cookies/src/CookieQueue.php +++ b/src/Cookies/src/CookieQueue.php @@ -4,6 +4,9 @@ namespace Spiral\Cookies; +use Spiral\Core\Attribute\Scope; + +#[Scope('http.request')] final class CookieQueue { public const ATTRIBUTE = 'cookieQueue'; diff --git a/src/Cookies/tests/CookiesTest.php b/src/Cookies/tests/CookiesTest.php index 4834e330b..748e4ba91 100644 --- a/src/Cookies/tests/CookiesTest.php +++ b/src/Cookies/tests/CookiesTest.php @@ -7,12 +7,11 @@ use Defuse\Crypto\Key; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; use Spiral\Cookies\Config\CookiesConfig; use Spiral\Cookies\CookieQueue; use Spiral\Cookies\Middleware\CookiesMiddleware; use Spiral\Core\Container; -use Spiral\Core\ContainerScope; +use Spiral\Core\Scope; use Spiral\Encrypter\Config\EncrypterConfig; use Spiral\Encrypter\Encrypter; use Spiral\Encrypter\EncrypterFactory; @@ -25,14 +24,13 @@ use Spiral\Telemetry\NullTracer; use Spiral\Telemetry\TracerInterface; -class CookiesTest extends TestCase +final class CookiesTest extends TestCase { - private $container; + private Container $container; public function setUp(): void { $this->container = new Container(); - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); $this->container->bind(CookiesConfig::class, new CookiesConfig([ 'domain' => '.%s', 'method' => CookiesConfig::COOKIE_ENCRYPT, @@ -54,18 +52,7 @@ public function testScope(): void { $core = $this->httpCore([CookiesMiddleware::class]); $core->setHandler(function ($r) { - $this->assertInstanceOf( - CookieQueue::class, - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE) - ); - - $this->assertSame( - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE), - $r->getAttribute(CookieQueue::ATTRIBUTE) - ); - + $this->assertInstanceOf(CookieQueue::class, $r->getAttribute(CookieQueue::ATTRIBUTE)); return 'all good'; }); @@ -78,8 +65,7 @@ public function testSetEncryptedCookie(): void { $core = $this->httpCore([CookiesMiddleware::class]); $core->setHandler(function ($r) { - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); + $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); return 'all good'; }); @@ -100,8 +86,7 @@ public function testSetNotProtectedCookie(): void { $core = $this->httpCore([CookiesMiddleware::class]); $core->setHandler(function ($r) { - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE)->set('PHPSESSID', 'value'); + $r->getAttribute(CookieQueue::ATTRIBUTE)->set('PHPSESSID', 'value'); return 'all good'; }); @@ -173,11 +158,9 @@ public function testDelete(): void { $core = $this->httpCore([CookiesMiddleware::class]); $core->setHandler(function ($r) { - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); + $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE)->delete('name'); + $r->getAttribute(CookieQueue::ATTRIBUTE)->delete('name'); return 'all good'; }); @@ -193,7 +176,7 @@ public function testDelete(): void public function testUnprotected(): void { - $this->container->bind(CookiesConfig::class, new CookiesConfig([ + $this->container->getBinder('root')->bind(CookiesConfig::class, new CookiesConfig([ 'domain' => '.%s', 'method' => CookiesConfig::COOKIE_UNPROTECTED, 'excluded' => ['PHPSESSID', 'csrf-token'] @@ -201,8 +184,7 @@ public function testUnprotected(): void $core = $this->httpCore([CookiesMiddleware::class]); $core->setHandler(function ($r) { - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); + $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); return 'all good'; }); @@ -218,7 +200,7 @@ public function testUnprotected(): void public function testGetUnprotected(): void { - $this->container->bind(CookiesConfig::class, new CookiesConfig([ + $this->container->getBinder('root')->bind(CookiesConfig::class, new CookiesConfig([ 'domain' => '.%s', 'method' => CookiesConfig::COOKIE_UNPROTECTED, 'excluded' => ['PHPSESSID', 'csrf-token'] @@ -250,8 +232,7 @@ public function testHMAC(): void $core = $this->httpCore([CookiesMiddleware::class]); $core->setHandler(function ($r) { - ContainerScope::getContainer()->get(ServerRequestInterface::class) - ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); + $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); return 'all good'; }); @@ -284,7 +265,7 @@ protected function httpCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new TestResponseFactory($config), $this->container ); @@ -329,4 +310,13 @@ protected function fetchCookies(ResponseInterface $response) return $result; } + + protected function runTest(): mixed + { + return $this->container->runScope(new Scope('http'), function (Container $container): mixed { + $this->container = $container; + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); + return parent::runTest(); + }); + } } diff --git a/src/Csrf/tests/CsrfTest.php b/src/Csrf/tests/CsrfTest.php index 862b37374..7e6c37c81 100644 --- a/src/Csrf/tests/CsrfTest.php +++ b/src/Csrf/tests/CsrfTest.php @@ -8,6 +8,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Spiral\Core\Container; +use Spiral\Core\Scope; use Spiral\Csrf\Config\CsrfConfig; use Spiral\Csrf\Middleware\CsrfFirewall; use Spiral\Csrf\Middleware\CsrfMiddleware; @@ -69,7 +70,7 @@ static function ($r) { public function testLengthException(): void { $this->expectException(\RuntimeException::class); - $this->container->bind( + $this->container->getBinder('root')->bind( CsrfConfig::class, new CsrfConfig( [ @@ -228,7 +229,7 @@ protected function httpCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new TestResponseFactory($config), $this->container ); @@ -286,4 +287,13 @@ protected function fetchCookies(ResponseInterface $response): array return $result; } + + protected function runTest(): mixed + { + return $this->container->runScope(new Scope('http'), function (Container $container): mixed { + $this->container = $container; + + return parent::runTest(); + }); + } } diff --git a/src/Filters/tests/InputScopeTest.php b/src/Filters/tests/InputScopeTest.php index c7e8797cd..11b78baaf 100644 --- a/src/Filters/tests/InputScopeTest.php +++ b/src/Filters/tests/InputScopeTest.php @@ -6,6 +6,8 @@ use Nyholm\Psr7\ServerRequest; use Psr\Http\Message\ServerRequestInterface; +use Spiral\Core\Container; +use Spiral\Core\Scope; use Spiral\Filter\InputScope; use Spiral\Filters\InputInterface; @@ -66,4 +68,12 @@ private function getInput(): InputInterface { return $this->container->get(InputInterface::class); } + + protected function runTest(): mixed + { + return $this->container->runScope(new Scope('http.request'), function (Container $container): mixed { + $this->container = $container; + return parent::runTest(); + }); + } } diff --git a/src/Framework/Auth/AuthScope.php b/src/Framework/Auth/AuthScope.php index 4c6b4edcd..c9af68b61 100644 --- a/src/Framework/Auth/AuthScope.php +++ b/src/Framework/Auth/AuthScope.php @@ -6,15 +6,17 @@ use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Exception\ScopeException; /** * Provides global access to temporary authentication scope. + * @deprecated Use {@see AuthContextInterface} instead. Will be removed in v4.0. */ final class AuthScope implements AuthContextInterface { public function __construct( - private readonly ContainerInterface $container + #[Proxy] private readonly ContainerInterface $container ) { } diff --git a/src/Framework/Auth/Exception/InvalidAuthContext.php b/src/Framework/Auth/Exception/InvalidAuthContext.php new file mode 100644 index 000000000..d65410e4a --- /dev/null +++ b/src/Framework/Auth/Exception/InvalidAuthContext.php @@ -0,0 +1,19 @@ + [self::class, 'transportRegistry'], - TokenStorageInterface::class => [self::class, 'getDefaultTokenStorage'], - TokenStorageProviderInterface::class => TokenStorageProvider::class, - ]; - public function __construct( - private readonly ConfiguratorInterface $config + private readonly ConfiguratorInterface $config, + private readonly BinderInterface $binder, ) { } + public function defineDependencies(): array + { + return [ + AuthBootloader::class, + HttpBootloader::class, + ]; + } + + public function defineBindings(): array + { + $this->binder + ->getBinder(Spiral::Http) + ->bind( + AuthContextInterface::class, + static fn(CurrentRequest $request): AuthContextInterface => + $request->get()->getAttribute(AuthMiddleware::ATTRIBUTE) ?? throw new InvalidAuthContext() + ); + + return []; + } + + public function defineSingletons(): array + { + // Default token storage outside of HTTP scope + $this->binder->bindSingleton( + TokenStorageInterface::class, + static fn (TokenStorageProviderInterface $provider): TokenStorageInterface => $provider->getStorage(), + ); + + // Token storage from request attribute in HTTP scope + $this->binder + ->getBinder(Spiral::Http) + ->bindSingleton(TokenStorageInterface::class, [self::class, 'getTokenStorage']); + + return [ + TransportRegistry::class => [self::class, 'transportRegistry'], + TokenStorageProviderInterface::class => TokenStorageProvider::class, + ]; + } + public function init(AbstractKernel $kernel, EnvironmentInterface $env): void { $this->config->setDefaults( @@ -119,8 +155,10 @@ private function transportRegistry(AuthConfig $config, FactoryInterface $factory /** * Get default token storage from provider */ - private function getDefaultTokenStorage(TokenStorageProviderInterface $provider): TokenStorageInterface - { - return $provider->getStorage(); + private function getTokenStorage( + TokenStorageProviderInterface $provider, + CurrentRequest $request + ): TokenStorageInterface { + return $request->get()->getAttribute(AuthMiddleware::TOKEN_STORAGE_ATTRIBUTE) ?? $provider->getStorage(); } } diff --git a/src/Framework/Bootloader/Http/CookiesBootloader.php b/src/Framework/Bootloader/Http/CookiesBootloader.php index df83d98e8..2bd1af5a1 100644 --- a/src/Framework/Bootloader/Http/CookiesBootloader.php +++ b/src/Framework/Bootloader/Http/CookiesBootloader.php @@ -11,21 +11,27 @@ use Spiral\Cookies\Config\CookiesConfig; use Spiral\Cookies\CookieQueue; use Spiral\Core\Attribute\Singleton; +use Spiral\Core\BinderInterface; use Spiral\Core\Exception\ScopeException; +use Spiral\Framework\Spiral; #[Singleton] final class CookiesBootloader extends Bootloader { - protected const BINDINGS = [ - CookieQueue::class => [self::class, 'cookieQueue'], - ]; - public function __construct( - private readonly ConfiguratorInterface $config + private readonly ConfiguratorInterface $config, + private readonly BinderInterface $binder, ) { } - public function init(HttpBootloader $http): void + public function defineBindings(): array + { + $this->binder->getBinder(Spiral::HttpRequest)->bind(CookieQueue::class, [self::class, 'cookieQueue']); + + return []; + } + + public function init(): void { $this->config->setDefaults( CookiesConfig::CONFIG, @@ -45,9 +51,6 @@ public function whitelistCookie(string $cookie): void $this->config->modify(CookiesConfig::CONFIG, new Append('excluded', null, $cookie)); } - /** - * @noRector RemoveUnusedPrivateMethodRector - */ private function cookieQueue(ServerRequestInterface $request): CookieQueue { $cookieQueue = $request->getAttribute(CookieQueue::ATTRIBUTE, null); diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index 5d33ca54e..a6e54e635 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -18,6 +18,7 @@ use Spiral\Core\InvokerInterface; use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; +use Spiral\Http\CurrentRequest; use Spiral\Http\Http; use Spiral\Http\Pipeline; use Spiral\Telemetry\Bootloader\TelemetryBootloader; @@ -45,6 +46,7 @@ public function defineDependencies(): array public function defineSingletons(): array { $this->binder->getBinder(Spiral::Http)->bindSingleton(Http::class, [self::class, 'httpCore']); + $this->binder->getBinder(Spiral::Http)->bindSingleton(CurrentRequest::class, CurrentRequest::class); /** * @deprecated since v3.12. Will be removed in v4.0. diff --git a/src/Framework/Bootloader/Http/RouterBootloader.php b/src/Framework/Bootloader/Http/RouterBootloader.php index a6c86baa1..6d2f315fc 100644 --- a/src/Framework/Bootloader/Http/RouterBootloader.php +++ b/src/Framework/Bootloader/Http/RouterBootloader.php @@ -12,10 +12,12 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Config\ConfiguratorInterface; use Spiral\Core\Attribute\Proxy; +use Spiral\Core\BinderInterface; use Spiral\Core\Core; use Spiral\Core\CoreInterface; use Spiral\Core\Exception\ScopeException; use Spiral\Framework\Kernel; +use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; use Spiral\Router\GroupRegistry; use Spiral\Router\Loader\Configurator\RoutingConfigurator; @@ -35,28 +37,38 @@ final class RouterBootloader extends Bootloader { - protected const DEPENDENCIES = [ - HttpBootloader::class, - TelemetryBootloader::class, - ]; - - protected const SINGLETONS = [ - CoreInterface::class => Core::class, - RouterInterface::class => [self::class, 'router'], - RouteInterface::class => [self::class, 'route'], - RequestHandlerInterface::class => RouterInterface::class, - LoaderInterface::class => DelegatingLoader::class, - LoaderRegistryInterface::class => [self::class, 'initRegistry'], - GroupRegistry::class => GroupRegistry::class, - RoutingConfigurator::class => RoutingConfigurator::class, - RoutePatternRegistryInterface::class => DefaultPatternRegistry::class, - ]; - public function __construct( - private readonly ConfiguratorInterface $config + private readonly ConfiguratorInterface $config, + private readonly BinderInterface $binder, ) { } + public function defineDependencies(): array + { + return [ + HttpBootloader::class, + TelemetryBootloader::class, + ]; + } + + public function defineSingletons(): array + { + $this->binder + ->getBinder(Spiral::HttpRequest) + ->bindSingleton(RouteInterface::class, [self::class, 'route']); + + return [ + CoreInterface::class => Core::class, + RouterInterface::class => [self::class, 'router'], + RequestHandlerInterface::class => RouterInterface::class, + LoaderInterface::class => DelegatingLoader::class, + LoaderRegistryInterface::class => [self::class, 'initRegistry'], + GroupRegistry::class => GroupRegistry::class, + RoutingConfigurator::class => RoutingConfigurator::class, + RoutePatternRegistryInterface::class => DefaultPatternRegistry::class, + ]; + } + public function boot(AbstractKernel $kernel): void { $configuratorCallback = static function (RouterInterface $router, RoutingConfigurator $routes): void { diff --git a/src/Framework/Bootloader/Http/SessionBootloader.php b/src/Framework/Bootloader/Http/SessionBootloader.php index 11d6e8b3f..b07f139a2 100644 --- a/src/Framework/Bootloader/Http/SessionBootloader.php +++ b/src/Framework/Bootloader/Http/SessionBootloader.php @@ -7,17 +7,46 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Boot\DirectoriesInterface; use Spiral\Config\ConfiguratorInterface; +use Spiral\Core\BinderInterface; +use Spiral\Core\Config\Proxy; use Spiral\Core\Container\Autowire; +use Spiral\Framework\Spiral; +use Spiral\Http\CurrentRequest; use Spiral\Session\Config\SessionConfig; +use Spiral\Session\Exception\InvalidSessionContext; use Spiral\Session\Handler\FileHandler; +use Spiral\Session\Middleware\SessionMiddleware; use Spiral\Session\SessionFactory; use Spiral\Session\SessionFactoryInterface; +use Spiral\Session\SessionInterface; final class SessionBootloader extends Bootloader { - protected const SINGLETONS = [ - SessionFactoryInterface::class => SessionFactory::class, - ]; + public function __construct( + private readonly BinderInterface $binder, + ) { + } + + public function defineBindings(): array + { + $this->binder + ->getBinder(Spiral::Http) + ->bind(SessionInterface::class, static function (CurrentRequest $request): SessionInterface { + return $request->get() + ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new InvalidSessionContext(); + } + ); + $this->binder->bind(SessionInterface::class, new Proxy(SessionInterface::class, false)); + + return []; + } + + public function defineSingletons(): array + { + return [ + SessionFactoryInterface::class => SessionFactory::class, + ]; + } /** * Automatically registers session starter middleware and excludes session cookie from diff --git a/src/Framework/Bootloader/Security/FiltersBootloader.php b/src/Framework/Bootloader/Security/FiltersBootloader.php index 915b2556c..cb470be3e 100644 --- a/src/Framework/Bootloader/Security/FiltersBootloader.php +++ b/src/Framework/Bootloader/Security/FiltersBootloader.php @@ -11,6 +11,7 @@ use Spiral\Config\Patch\Append; use Spiral\Core\Attribute\Singleton; use Spiral\Core\BinderInterface; +use Spiral\Core\Config\Proxy; use Spiral\Core\Container; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\InterceptableCore; @@ -28,6 +29,8 @@ use Spiral\Filters\Model\Mapper\CasterRegistry; use Spiral\Filters\Model\Mapper\CasterRegistryInterface; use Spiral\Filters\Model\Mapper\UuidCaster; +use Spiral\Framework\Spiral; +use Spiral\Http\Request\InputManager; /** * @implements Container\InjectorInterface @@ -35,12 +38,6 @@ #[Singleton] final class FiltersBootloader extends Bootloader implements Container\InjectorInterface { - protected const SINGLETONS = [ - FilterProviderInterface::class => [self::class, 'initFilterProvider'], - InputInterface::class => InputScope::class, - CasterRegistryInterface::class => [self::class, 'initCasterRegistry'], - ]; - public function __construct( private readonly ContainerInterface $container, private readonly BinderInterface $binder, @@ -48,6 +45,23 @@ public function __construct( ) { } + public function defineSingletons(): array + { + $this->binder + ->getBinder(Spiral::HttpRequest) + ->bindSingleton( + InputInterface::class, + static fn (ContainerInterface $container): InputScope => new InputScope(new InputManager($container)) + ); + + $this->binder->bind(InputInterface::class, new Proxy(InputInterface::class, true)); + + return [ + FilterProviderInterface::class => [self::class, 'initFilterProvider'], + CasterRegistryInterface::class => [self::class, 'initCasterRegistry'], + ]; + } + /** * Declare Filter injection. */ diff --git a/src/Framework/Cookies/CookieManager.php b/src/Framework/Cookies/CookieManager.php index 01bedb234..43815017b 100644 --- a/src/Framework/Cookies/CookieManager.php +++ b/src/Framework/Cookies/CookieManager.php @@ -7,6 +7,7 @@ use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ServerRequestInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Attribute\Singleton; use Spiral\Core\Exception\ScopeException; @@ -17,7 +18,7 @@ final class CookieManager { public function __construct( - private readonly ContainerInterface $container + #[Proxy] private readonly ContainerInterface $container, ) { } diff --git a/src/Framework/Session/Exception/InvalidSessionContext.php b/src/Framework/Session/Exception/InvalidSessionContext.php new file mode 100644 index 000000000..41c647c48 --- /dev/null +++ b/src/Framework/Session/Exception/InvalidSessionContext.php @@ -0,0 +1,19 @@ +scope->runScope( - [SessionInterface::class => $session], - static fn () => $handler->handle($request->withAttribute(self::ATTRIBUTE, $session)) - ); + $response = $handler->handle($request->withAttribute(self::ATTRIBUTE, $session)); } catch (\Throwable $e) { $session->abort(); throw $e; diff --git a/src/Framework/Session/SectionScope.php b/src/Framework/Session/SectionScope.php index 3990b9ddb..41b98670e 100644 --- a/src/Framework/Session/SectionScope.php +++ b/src/Framework/Session/SectionScope.php @@ -7,7 +7,7 @@ final class SectionScope implements SessionSectionInterface { public function __construct( - private readonly SessionScope $session, + private readonly SessionInterface $session, private readonly string $name ) { } diff --git a/src/Framework/Session/SessionScope.php b/src/Framework/Session/SessionScope.php index 3a974ae46..9ac2508be 100644 --- a/src/Framework/Session/SessionScope.php +++ b/src/Framework/Session/SessionScope.php @@ -6,11 +6,13 @@ use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Attribute\Singleton; use Spiral\Core\Exception\ScopeException; /** * Provides access to the currently active session scope. + * @deprecated Use {@see SessionInterface} instead. Will be removed in v4.0. */ #[Singleton] final class SessionScope implements SessionInterface @@ -19,7 +21,7 @@ final class SessionScope implements SessionInterface private const DEFAULT_SECTION = '_DEFAULT'; public function __construct( - private readonly ContainerInterface $container + #[Proxy] private readonly ContainerInterface $container ) { } diff --git a/src/Http/src/CurrentRequest.php b/src/Http/src/CurrentRequest.php new file mode 100644 index 000000000..54a344c02 --- /dev/null +++ b/src/Http/src/CurrentRequest.php @@ -0,0 +1,28 @@ +request = $request; + } + + public function get(): ServerRequestInterface + { + return $this->request ?? throw new HttpException('Unable to resolve current request.'); + } +} diff --git a/src/Http/src/Http.php b/src/Http/src/Http.php index 9fe1bafe4..3dd124551 100644 --- a/src/Http/src/Http.php +++ b/src/Http/src/Http.php @@ -60,7 +60,9 @@ public function setHandler(callable|RequestHandlerInterface $handler): self */ public function handle(ServerRequestInterface $request): ResponseInterface { - $callback = function (SpanInterface $span) use ($request): ResponseInterface { + $callback = function (SpanInterface $span, CurrentRequest $currentRequest) use ($request): ResponseInterface { + $currentRequest->set($request); + $dispatcher = $this->container->has(EventDispatcherInterface::class) ? $this->container->get(EventDispatcherInterface::class) : null; diff --git a/src/Http/src/Pipeline.php b/src/Http/src/Pipeline.php index 773afb0ea..c5987c386 100644 --- a/src/Http/src/Pipeline.php +++ b/src/Http/src/Pipeline.php @@ -4,6 +4,7 @@ namespace Spiral\Http; +use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -31,6 +32,7 @@ final class Pipeline implements RequestHandlerInterface, MiddlewareInterface public function __construct( #[Proxy] private readonly ScopeInterface $scope, + #[Proxy] private readonly ContainerInterface $container, private readonly ?EventDispatcherInterface $dispatcher = null, ?TracerInterface $tracer = null ) { @@ -69,7 +71,11 @@ public function handle(Request $request): Response return $this->tracer->trace( name: \sprintf('Middleware processing [%s]', $middleware::class), - callback: function (SpanInterface $span) use ($request, $middleware): Response { + callback: function ( + SpanInterface $span, + CurrentRequest $current + ) use ($request, $middleware): Response { + $current->set($request); $response = $middleware->process($request, $this); $span @@ -92,12 +98,8 @@ public function handle(Request $request): Response ); } - $handler = $this->handler; + $this->container->get(CurrentRequest::class)->set($request); - // TODO: Can we remove this scope? - return $this->scope->runScope( - [Request::class => $request], - static fn (): Response => $handler->handle($request) - ); + return $this->handler->handle($request); } } diff --git a/src/Http/src/Request/InputManager.php b/src/Http/src/Request/InputManager.php index 6e3629934..1d0fcbc2f 100644 --- a/src/Http/src/Request/InputManager.php +++ b/src/Http/src/Request/InputManager.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Attribute\Singleton; use Spiral\Core\Exception\ScopeException; use Spiral\Http\Config\HttpConfig; @@ -115,7 +116,7 @@ final class InputManager public function __construct( /** @invisible */ - private readonly ContainerInterface $container, + #[Proxy] private readonly ContainerInterface $container, /** @invisible */ HttpConfig $config = new HttpConfig() ) { diff --git a/src/Http/tests/HttpTest.php b/src/Http/tests/HttpTest.php index 6fd322a11..a61602efe 100644 --- a/src/Http/tests/HttpTest.php +++ b/src/Http/tests/HttpTest.php @@ -5,11 +5,7 @@ namespace Spiral\Tests\Http; use Mockery as m; -use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; -use Psr\Http\Message\ServerRequestInterface; -use Spiral\Core\Container; -use Spiral\Core\ContainerScope; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Event\RequestHandled; @@ -19,22 +15,13 @@ use Spiral\Http\Pipeline; use Spiral\Telemetry\NullTracer; use Spiral\Telemetry\TracerFactoryInterface; -use Spiral\Telemetry\TracerInterface; use Spiral\Tests\Http\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; -class HttpTest extends TestCase +final class HttpTest extends ScopedTestCase { use m\Adapter\Phpunit\MockeryPHPUnitIntegration; - private Container $container; - - public function setUp(): void - { - $this->container = new Container(); - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); - } - public function testGetPipeline(): void { $core = $this->getCore(); @@ -223,20 +210,6 @@ public function testMiddlewareTraitReversed(): void $this->assertSame('hello?', (string)$response->getBody()); } - public function testScope(): void - { - $core = $this->getCore(); - - $core->setHandler(function () { - $this->assertTrue(ContainerScope::getContainer()->has(ServerRequestInterface::class)); - - return 'OK'; - }); - - $response = $core->handle(new ServerRequest('GET', '')); - $this->assertSame('OK', (string)$response->getBody()); - } - public function testPassException(): void { $this->expectException(\RuntimeException::class); @@ -280,7 +253,7 @@ public function testPassingTracerIntoScope(): void $http = new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new ResponseFactory($config), $this->container, $tracerFactory = m::mock(TracerFactoryInterface::class), @@ -293,7 +266,7 @@ public function testPassingTracerIntoScope(): void $tracerFactory->shouldReceive('make') ->once() ->with(['foo' => ['bar']]) - ->andReturn($tracer = new NullTracer()); + ->andReturn(new NullTracer($this->container)); $response = $http->handle($request); $this->assertSame('hello world', (string)$response->getBody()); @@ -305,7 +278,7 @@ protected function getCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container), + new Pipeline($this->container, $this->container), new ResponseFactory($config), $this->container ); diff --git a/src/Http/tests/PipelineTest.php b/src/Http/tests/PipelineTest.php index 1dc350ae4..950c7ef23 100644 --- a/src/Http/tests/PipelineTest.php +++ b/src/Http/tests/PipelineTest.php @@ -4,28 +4,25 @@ namespace Spiral\Tests\Http; -use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Spiral\Core\Container; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Event\MiddlewareProcessing; use Spiral\Http\Exception\PipelineException; use Spiral\Http\Pipeline; use Spiral\Telemetry\NullTracer; -use Spiral\Telemetry\NullTracerFactory; use Spiral\Tests\Http\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; -class PipelineTest extends TestCase +final class PipelineTest extends ScopedTestCase { public function testTarget(): void { - $pipeline = new Pipeline(new Container()); + $pipeline = new Pipeline($this->container, $this->container); $handler = new CallableHandler(function () { return 'response'; @@ -40,7 +37,7 @@ public function testTarget(): void public function testHandle(): void { - $pipeline = new Pipeline(new Container()); + $pipeline = new Pipeline($this->container, $this->container); $handler = new CallableHandler(function () { return 'response'; @@ -57,7 +54,7 @@ public function testHandleException(): void { $this->expectException(PipelineException::class); - $pipeline = new Pipeline(new Container()); + $pipeline = new Pipeline($this->container, $this->container); $pipeline->handle(new ServerRequest('GET', '')); } @@ -80,13 +77,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->method('dispatch') ->with(new MiddlewareProcessing($request, $middleware)); - $container = new Container(); - - $pipeline = new Pipeline( - $container, - $dispatcher, - new NullTracer($container) - ); + $pipeline = new Pipeline($this->container, $this->container, $dispatcher, new NullTracer($this->container)); $pipeline->pushMiddleware($middleware); diff --git a/src/Http/tests/ScopedTestCase.php b/src/Http/tests/ScopedTestCase.php new file mode 100644 index 000000000..4f3f97182 --- /dev/null +++ b/src/Http/tests/ScopedTestCase.php @@ -0,0 +1,30 @@ +container = new Container(); + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); + } + + protected function runTest(): mixed + { + return $this->container->runScope(new Scope('http'), function (Container $container): mixed { + $this->container = $container; + return parent::runTest(); + }); + } +} diff --git a/src/Router/tests/BaseTestCase.php b/src/Router/tests/BaseTestCase.php index 2c8ced74e..921ffed2c 100644 --- a/src/Router/tests/BaseTestCase.php +++ b/src/Router/tests/BaseTestCase.php @@ -12,7 +12,9 @@ use Spiral\Core\Container; use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterface; +use Spiral\Core\Scope; use Spiral\Http\Config\HttpConfig; +use Spiral\Http\CurrentRequest; use Spiral\Router\GroupRegistry; use Spiral\Router\Loader\Configurator\RoutingConfigurator; use Spiral\Router\Loader\DelegatingLoader; @@ -74,7 +76,6 @@ public static function middlewaresDataProvider(): \Traversable private function initContainer(): void { $this->container = new Container(); - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); $this->container->bind(ResponseFactoryInterface::class, new ResponseFactory(new HttpConfig(['headers' => []]))); $this->container->bind(UriFactoryInterface::class, new UriFactory()); $this->container->bind( @@ -90,6 +91,9 @@ private function initContainer(): void $this->container->bind(CoreInterface::class, Core::class); $this->container->bindSingleton(GroupRegistry::class, GroupRegistry::class); $this->container->bindSingleton(RoutingConfigurator::class, RoutingConfigurator::class); + $this->container + ->getBinder('http') + ->bindSingleton(CurrentRequest::class, CurrentRequest::class); } private function initRouter(): void @@ -97,4 +101,14 @@ private function initRouter(): void $this->router = $this->makeRouter(); $this->container->bindSingleton(RouterInterface::class, $this->router); } + + protected function runTest(): mixed + { + return $this->container->runScope(new Scope('http'), function (Container $container): mixed { + $this->container = $container; + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); + + return parent::runTest(); + }); + } } diff --git a/src/Router/tests/PipelineFactoryTest.php b/src/Router/tests/PipelineFactoryTest.php index a4125ba02..3aefaba71 100644 --- a/src/Router/tests/PipelineFactoryTest.php +++ b/src/Router/tests/PipelineFactoryTest.php @@ -15,6 +15,7 @@ use Spiral\Core\Container\Autowire; use Spiral\Core\FactoryInterface; use Spiral\Core\ScopeInterface; +use Spiral\Http\CurrentRequest; use Spiral\Http\Pipeline; use Spiral\Router\Exception\RouteException; use Spiral\Router\PipelineFactory; @@ -41,7 +42,8 @@ protected function setUp(): void public function testCreatesFromArrayWithPipeline(): void { $newPipeline = new Pipeline( - scope: m::mock(ScopeInterface::class) + scope: m::mock(ScopeInterface::class), + container: $this->createMock(ContainerInterface::class), ); $this->assertSame( @@ -53,6 +55,7 @@ public function testCreatesFromArrayWithPipeline(): void public function testCreates(): void { $container = new Container(); + $container->bindSingleton(CurrentRequest::class, new CurrentRequest()); $this->factory ->shouldReceive('make') @@ -60,6 +63,7 @@ public function testCreates(): void ->with(Pipeline::class) ->andReturn($p = new Pipeline( $scope = m::mock(ScopeInterface::class), + $container, tracer: new NullTracer($container) )); @@ -90,16 +94,19 @@ public function testCreates(): void $middleware4->shouldReceive('process')->once()->andReturnUsing($handle); $middleware5->shouldReceive('process')->once()->andReturnUsing($handle); - $scope->shouldReceive('runScope') - ->once() - ->andReturn($response = m::mock(ResponseInterface::class)); - + $response = m::mock(ResponseInterface::class); $response ->shouldReceive('getHeaderLine')->with('Content-Length')->andReturn('test') ->shouldReceive('getStatusCode')->andReturn(200); + $requestHandler = $this->createMock(RequestHandlerInterface::class); + $requestHandler + ->expects($this->once()) + ->method('handle') + ->willReturn($response); + $p - ->withHandler(m::mock(RequestHandlerInterface::class)) + ->withHandler($requestHandler) ->handle(m::mock(ServerRequestInterface::class)); } @@ -110,7 +117,7 @@ public function testInvalidTypeShouldThrowAnException(mixed $value, string $type ->shouldReceive('make') ->once() ->with(Pipeline::class) - ->andReturn(new Pipeline(m::mock(ScopeInterface::class))); + ->andReturn(new Pipeline(m::mock(ScopeInterface::class), $this->container)); $this->expectException(RouteException::class); $this->expectExceptionMessage(\sprintf('Invalid middleware `%s`', $type)); diff --git a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php index 7b9e87647..3ec7144ed 100644 --- a/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php +++ b/tests/Framework/AuthHttp/Middleware/AuthMiddlewareTest.php @@ -38,14 +38,6 @@ public function testTokenStorageInterfaceShouldBeBound(): void $this->assertSame($storage, $ref->invoke($scope)); }); - $scope = $this->getContainer()->get(TokenStorageScope::class); - $ref = new \ReflectionMethod($scope, 'getTokenStorage'); - $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); - $this->fakeHttp()->get('/'); - - $scope = $this->getContainer()->get(TokenStorageScope::class); - $ref = new \ReflectionMethod($scope, 'getTokenStorage'); - $this->assertNotInstanceOf($storage::class, $ref->invoke($scope)); } } diff --git a/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php b/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php index 0754a2142..beab7be2d 100644 --- a/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php @@ -10,10 +10,13 @@ use Spiral\Config\LoaderInterface; use Spiral\Cookies\Config\CookiesConfig; use Spiral\Cookies\CookieQueue; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class CookiesBootloaderTest extends BaseTestCase { + #[TestScope(Spiral::HttpRequest)] public function testCookieQueueBinding(): void { $request = $this->mockContainer(ServerRequestInterface::class); @@ -25,6 +28,7 @@ public function testCookieQueueBinding(): void $this->assertContainerBound(CookieQueue::class); } + #[TestScope(Spiral::HttpRequest)] public function testCookieQueueBindingShouldThrowAndExceptionWhenAttributeIsEmpty(): void { $this->expectExceptionMessage('Unable to resolve CookieQueue, invalid request scope'); @@ -51,7 +55,7 @@ public function testWhitelistCookie(): void $configs = new ConfigManager($this->createMock(LoaderInterface::class)); $configs->setDefaults(CookiesConfig::CONFIG, ['excluded' => []]); - $bootloader = new CookiesBootloader($configs); + $bootloader = new CookiesBootloader($configs, $this->getContainer()); $bootloader->whitelistCookie('foo'); $this->assertSame(['foo'], $configs->getConfig(CookiesConfig::CONFIG)['excluded']); diff --git a/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php b/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php index cbd1e86b0..ed3f81bec 100644 --- a/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php @@ -43,7 +43,7 @@ public function testAddTokenStorage(): void $configs = new ConfigManager($this->createMock(LoaderInterface::class)); $configs->setDefaults(AuthConfig::CONFIG, ['storages' => []]); - $bootloader = new HttpAuthBootloader($configs); + $bootloader = new HttpAuthBootloader($configs, $this->getContainer()); $bootloader->addTokenStorage('foo', 'bar'); $this->assertSame(['foo' => 'bar'], $configs->getConfig(AuthConfig::CONFIG)['storages']); @@ -54,7 +54,7 @@ public function testAddTransport(): void $configs = new ConfigManager($this->createMock(LoaderInterface::class)); $configs->setDefaults(AuthConfig::CONFIG, ['transports' => []]); - $bootloader = new HttpAuthBootloader($configs); + $bootloader = new HttpAuthBootloader($configs, $this->getContainer()); $bootloader->addTransport('foo', 'bar'); $this->assertSame(['foo' => 'bar'], $configs->getConfig(AuthConfig::CONFIG)['transports']); diff --git a/tests/Framework/Bootloader/Router/RouterBootloaderTest.php b/tests/Framework/Bootloader/Router/RouterBootloaderTest.php index 2d31be257..bf70b1e54 100644 --- a/tests/Framework/Bootloader/Router/RouterBootloaderTest.php +++ b/tests/Framework/Bootloader/Router/RouterBootloaderTest.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Spiral\Core\CoreInterface; +use Spiral\Framework\Spiral; use Spiral\Router\GroupRegistry; use Spiral\Router\Loader\Configurator\RoutingConfigurator; use Spiral\Router\Loader\DelegatingLoader; @@ -18,6 +19,7 @@ use Spiral\Router\RouteInterface; use Spiral\Router\Router; use Spiral\Router\RouterInterface; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class RouterBootloaderTest extends BaseTestCase @@ -62,6 +64,7 @@ public function testRoutePatternRegistryBinding(): void $this->assertContainerBoundAsSingleton(RoutePatternRegistryInterface::class, DefaultPatternRegistry::class); } + #[TestScope(Spiral::HttpRequest)] public function testRouteInterfaceBinding(): void { $request = $this->mockContainer(ServerRequestInterface::class); @@ -73,6 +76,7 @@ public function testRouteInterfaceBinding(): void $this->assertContainerBoundAsSingleton(RouteInterface::class, RouteInterface::class); } + #[TestScope(Spiral::HttpRequest)] public function testRouteInterfaceShouldThrowAnExceptionWhenRequestDoesNotContainIt(): void { $this->expectExceptionMessage('Unable to resolve Route, invalid request scope'); diff --git a/tests/Framework/Bootloader/Security/FiltersBootloaderTest.php b/tests/Framework/Bootloader/Security/FiltersBootloaderTest.php index fb9d1f3d7..a9daf92cb 100644 --- a/tests/Framework/Bootloader/Security/FiltersBootloaderTest.php +++ b/tests/Framework/Bootloader/Security/FiltersBootloaderTest.php @@ -15,6 +15,8 @@ use Spiral\Filters\Model\Interceptor\PopulateDataFromEntityInterceptor; use Spiral\Filters\Model\Interceptor\ValidateFilterInterceptor; use Spiral\Filters\InputInterface; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class FiltersBootloaderTest extends BaseTestCase @@ -24,6 +26,7 @@ public function testFilterProviderInterfaceBinding(): void $this->assertContainerBoundAsSingleton(FilterProviderInterface::class, FilterProvider::class); } + #[TestScope(Spiral::HttpRequest)] public function testInputInterfaceBinding(): void { $this->assertContainerBoundAsSingleton(InputInterface::class, InputScope::class); diff --git a/tests/Framework/Http/CookiesTest.php b/tests/Framework/Http/CookiesTest.php index a5af8e9d4..5227d2282 100644 --- a/tests/Framework/Http/CookiesTest.php +++ b/tests/Framework/Http/CookiesTest.php @@ -4,6 +4,7 @@ namespace Spiral\Tests\Framework\Http; +use Psr\Http\Message\ServerRequestInterface; use Spiral\Cookies\Cookie; use Spiral\Cookies\CookieManager; use Spiral\Core\Exception\ScopeException; @@ -12,7 +13,7 @@ use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope(Spiral::Http)] +#[TestScope([Spiral::Http, Spiral::HttpRequest])] final class CookiesTest extends HttpTestCase { public const ENV = [ @@ -40,14 +41,22 @@ public function testOutsideOfScopeFail(): void public function testHasCookie(): void { - $this->setHttpHandler(fn (): int => (int)$this->cookies()->has('a')); + $this->setHttpHandler(function (ServerRequestInterface $request) { + $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); + + return (int)$this->cookies()->has('a'); + }); $this->fakeHttp()->get('/')->assertOk()->assertBodySame('0'); } public function testHasCookie2(): void { - $this->setHttpHandler(fn (): int => (int)$this->cookies()->has('a')); + $this->setHttpHandler(function (ServerRequestInterface $request) { + $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); + + return (int)$this->cookies()->has('a'); + }); $this ->fakeHttp() @@ -61,7 +70,11 @@ public function testHasCookie2(): void public function testGetCookie2(): void { - $this->setHttpHandler(fn (): string => $this->cookies()->get('a')); + $this->setHttpHandler(function (ServerRequestInterface $request) { + $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); + + return $this->cookies()->get('a'); + }); $this ->fakeHttp() @@ -75,7 +88,9 @@ public function testGetCookie2(): void public function testSetCookie(): void { - $this->setHttpHandler(function (): string { + $this->setHttpHandler(function (ServerRequestInterface $request) { + $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); + $this->cookies()->set('a', 'value'); return 'ok'; }); @@ -92,7 +107,9 @@ public function testSetCookie(): void public function testSetCookie2(): void { - $this->setHttpHandler(function (): string { + $this->setHttpHandler(function (ServerRequestInterface $request): string { + $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); + $this->cookies()->schedule(Cookie::create('a', 'value')); $this->assertSame([], $this->cookies()->getAll()); $this->assertCount(1, $this->cookies()->getScheduled()); @@ -112,7 +129,8 @@ public function testSetCookie2(): void public function testDeleteCookie(): void { - $this->setHttpHandler(function (): string { + $this->setHttpHandler(function (ServerRequestInterface $request): string { + $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); $this->cookies()->delete('cookie'); return 'ok'; }); diff --git a/tests/Framework/Http/CurrentRequestTest.php b/tests/Framework/Http/CurrentRequestTest.php new file mode 100644 index 000000000..49d760a9e --- /dev/null +++ b/tests/Framework/Http/CurrentRequestTest.php @@ -0,0 +1,106 @@ +setHttpHandler(function (ServerRequestInterface $request): void { + $this->assertSame($request, $this->getContainer()->get(CurrentRequest::class)->get()); + }); + + $this->fakeHttp()->get('/')->assertOk(); + } + + public function testAttributesAddedInMiddlewareAreAccessibleInHandler(): void + { + $middleware1 = $this->createMiddleware('first', 'first-value'); + $middleware2 = $this->createMiddleware('second', 'second-value'); + + $this->getContainer()->bind(HttpConfig::class, new HttpConfig([ + 'middleware' => [ + $middleware1::class, + $middleware2::class, + ], + 'basePath' => '/', + 'headers' => [] + ])); + + $this->setHttpHandler(function (ServerRequestInterface $request): void { + $this->assertSame($request, $this->getContainer()->get(CurrentRequest::class)->get()); + }); + + $this->fakeHttp()->get('/')->assertOk(); + } + + public function testAttributesAddedInMiddlewareAreAccessibleInNextMiddleware(): void + { + $middleware1 = $this->createMiddleware('first', 5); + $middleware2 = new class($this->getContainer()) implements MiddlewareInterface { + public function __construct( + private readonly ContainerInterface $container, + ) { + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + $initial = $this->container->get(CurrentRequest::class)->get(); + + return $handler->handle($request->withAttribute('second', $initial->getAttribute('first') + 5)); + } + }; + $this->getContainer()->bind($middleware2::class, $middleware2); + + $this->getContainer()->bind(HttpConfig::class, new HttpConfig([ + 'middleware' => [ + $middleware1::class, + $middleware2::class, + ], + 'basePath' => '/', + 'headers' => [] + ])); + + $this->setHttpHandler(function (ServerRequestInterface $request): void { + $current = $this->getContainer()->get(CurrentRequest::class)->get(); + + $this->assertSame($request, $current); + $this->assertSame(5, $current->getAttribute('first')); + $this->assertSame(10, $current->getAttribute('second')); + }); + + $this->fakeHttp()->get('/')->assertOk(); + } + + private function createMiddleware(string $attribute, mixed $value): MiddlewareInterface + { + $middleware = new class($attribute, $value) implements MiddlewareInterface { + public function __construct( + private readonly string $attribute, + private readonly mixed $value, + ) { + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + return $handler->handle($request->withAttribute($this->attribute, $this->value)); + } + }; + + $this->getContainer()->bind($middleware::class, $middleware); + + return $middleware; + } +} From e5c84ec00666264450119e37dcecd0d04715ba8a Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 17 Feb 2024 21:16:03 +0000 Subject: [PATCH 21/84] Apply fixes from StyleCI [ci skip] [skip ci] --- src/Framework/Bootloader/Auth/HttpAuthBootloader.php | 2 +- src/Framework/Bootloader/Http/SessionBootloader.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Framework/Bootloader/Auth/HttpAuthBootloader.php b/src/Framework/Bootloader/Auth/HttpAuthBootloader.php index 48a862cc4..044f73fbb 100644 --- a/src/Framework/Bootloader/Auth/HttpAuthBootloader.php +++ b/src/Framework/Bootloader/Auth/HttpAuthBootloader.php @@ -56,7 +56,7 @@ public function defineBindings(): array ->getBinder(Spiral::Http) ->bind( AuthContextInterface::class, - static fn(CurrentRequest $request): AuthContextInterface => + static fn (CurrentRequest $request): AuthContextInterface => $request->get()->getAttribute(AuthMiddleware::ATTRIBUTE) ?? throw new InvalidAuthContext() ); diff --git a/src/Framework/Bootloader/Http/SessionBootloader.php b/src/Framework/Bootloader/Http/SessionBootloader.php index b07f139a2..ff8fb1230 100644 --- a/src/Framework/Bootloader/Http/SessionBootloader.php +++ b/src/Framework/Bootloader/Http/SessionBootloader.php @@ -31,11 +31,13 @@ public function defineBindings(): array { $this->binder ->getBinder(Spiral::Http) - ->bind(SessionInterface::class, static function (CurrentRequest $request): SessionInterface { + ->bind( + SessionInterface::class, + static function (CurrentRequest $request): SessionInterface { return $request->get() ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new InvalidSessionContext(); } - ); + ); $this->binder->bind(SessionInterface::class, new Proxy(SessionInterface::class, false)); return []; From 0dba8cb20b51a0b13eeae04f7861fa629cf813c2 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Sun, 18 Feb 2024 00:00:08 +0200 Subject: [PATCH 22/84] Improve test --- src/Router/tests/PipelineFactoryTest.php | 41 ++++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Router/tests/PipelineFactoryTest.php b/src/Router/tests/PipelineFactoryTest.php index 3aefaba71..2a0c4d9a9 100644 --- a/src/Router/tests/PipelineFactoryTest.php +++ b/src/Router/tests/PipelineFactoryTest.php @@ -23,17 +23,15 @@ final class PipelineFactoryTest extends \PHPUnit\Framework\TestCase { - use m\Adapter\Phpunit\MockeryPHPUnitIntegration; - - private ContainerInterface&m\MockInterface $container; - private FactoryInterface&m\MockInterface $factory; + private ContainerInterface $container; + private FactoryInterface $factory; private PipelineFactory $pipeline; protected function setUp(): void { parent::setUp(); - $this->container = m::mock(ContainerInterface::class); + $this->container = $this->createMock(ContainerInterface::class); $this->factory = m::mock(FactoryInterface::class); $this->pipeline = new PipelineFactory($this->container, $this->factory); @@ -42,7 +40,7 @@ protected function setUp(): void public function testCreatesFromArrayWithPipeline(): void { $newPipeline = new Pipeline( - scope: m::mock(ScopeInterface::class), + scope: $this->createMock(ScopeInterface::class), container: $this->createMock(ContainerInterface::class), ); @@ -62,7 +60,7 @@ public function testCreates(): void ->once() ->with(Pipeline::class) ->andReturn($p = new Pipeline( - $scope = m::mock(ScopeInterface::class), + $this->createMock(ScopeInterface::class), $container, tracer: new NullTracer($container) )); @@ -71,17 +69,18 @@ public function testCreates(): void ->shouldReceive('make') ->once() ->with('bar', []) - ->andReturn($middleware5 = m::mock(MiddlewareInterface::class)); + ->andReturn($middleware5 = $this->createMock(MiddlewareInterface::class)); - $this->container->shouldReceive('get') - ->once() + $this->container + ->expects($this->once()) + ->method('get') ->with('foo') - ->andReturn($middleware4 = m::mock(MiddlewareInterface::class)); + ->willReturn($middleware4 = $this->createMock(MiddlewareInterface::class)); $this->assertSame($p, $this->pipeline->createWithMiddleware([ 'foo', - $middleware1 = m::mock(MiddlewareInterface::class), - $middleware2 = m::mock(MiddlewareInterface::class), + $middleware1 = $this->createMock(MiddlewareInterface::class), + $middleware2 = $this->createMock(MiddlewareInterface::class), new Autowire('bar'), ])); @@ -89,15 +88,15 @@ public function testCreates(): void return $handler->handle($request); }; - $middleware1->shouldReceive('process')->once()->andReturnUsing($handle); - $middleware2->shouldReceive('process')->once()->andReturnUsing($handle); - $middleware4->shouldReceive('process')->once()->andReturnUsing($handle); - $middleware5->shouldReceive('process')->once()->andReturnUsing($handle); + $middleware1->expects($this->once())->method('process')->willReturnCallback($handle); + $middleware2->expects($this->once())->method('process')->willReturnCallback($handle); + $middleware4->expects($this->once())->method('process')->willReturnCallback($handle); + $middleware5->expects($this->once())->method('process')->willReturnCallback($handle); - $response = m::mock(ResponseInterface::class); + $response = $this->createMock(ResponseInterface::class); $response - ->shouldReceive('getHeaderLine')->with('Content-Length')->andReturn('test') - ->shouldReceive('getStatusCode')->andReturn(200); + ->expects($this->exactly(4))->method('getHeaderLine')->with('Content-Length')->willReturn('test'); + $response->expects($this->exactly(8))->method('getStatusCode')->willReturn(200); $requestHandler = $this->createMock(RequestHandlerInterface::class); $requestHandler @@ -107,7 +106,7 @@ public function testCreates(): void $p ->withHandler($requestHandler) - ->handle(m::mock(ServerRequestInterface::class)); + ->handle($this->createMock(ServerRequestInterface::class)); } #[DataProvider('invalidTypeDataProvider')] From dae18705098d5ef53c5caa25cc1898b71846015a Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Sun, 18 Feb 2024 00:01:47 +0200 Subject: [PATCH 23/84] Fix CS --- src/Framework/Bootloader/Http/SessionBootloader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Framework/Bootloader/Http/SessionBootloader.php b/src/Framework/Bootloader/Http/SessionBootloader.php index ff8fb1230..111d15c76 100644 --- a/src/Framework/Bootloader/Http/SessionBootloader.php +++ b/src/Framework/Bootloader/Http/SessionBootloader.php @@ -34,8 +34,8 @@ public function defineBindings(): array ->bind( SessionInterface::class, static function (CurrentRequest $request): SessionInterface { - return $request->get() - ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new InvalidSessionContext(); + return $request->get() + ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new InvalidSessionContext(); } ); $this->binder->bind(SessionInterface::class, new Proxy(SessionInterface::class, false)); From 1752d6d442161c529945f2630a6e5caabb3cb2fc Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 17 Feb 2024 22:01:58 +0000 Subject: [PATCH 24/84] Apply fixes from StyleCI [ci skip] [skip ci] --- src/Framework/Bootloader/Http/SessionBootloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/Bootloader/Http/SessionBootloader.php b/src/Framework/Bootloader/Http/SessionBootloader.php index 111d15c76..0576db19c 100644 --- a/src/Framework/Bootloader/Http/SessionBootloader.php +++ b/src/Framework/Bootloader/Http/SessionBootloader.php @@ -36,7 +36,7 @@ public function defineBindings(): array static function (CurrentRequest $request): SessionInterface { return $request->get() ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new InvalidSessionContext(); - } + } ); $this->binder->bind(SessionInterface::class, new Proxy(SessionInterface::class, false)); From 6eb179295dbcee21ad48b1dc5adbc9c5a9ee7444 Mon Sep 17 00:00:00 2001 From: butschster Date: Tue, 20 Feb 2024 12:57:32 +0400 Subject: [PATCH 25/84] Uses named scopes in console component fixes #1074 --- src/Console/src/Command.php | 52 ++++++++++--------- src/Console/src/CommandCore.php | 8 +-- src/Console/src/CommandCoreFactory.php | 36 +++++++++++++ src/Console/src/Console.php | 39 ++++++++------ .../Confirmation/ApplicationInProduction.php | 2 + .../src/Command/AbstractCommand.php | 3 +- .../ApplicationInProductionCommandTest.php | 29 +++++++++++ .../ApplicationInProductionCommand.php | 25 +++++++++ 8 files changed, 147 insertions(+), 47 deletions(-) create mode 100644 src/Console/src/CommandCoreFactory.php create mode 100644 tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php create mode 100644 tests/app/src/Command/ApplicationInProductionCommand.php diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 110ad53d9..51f3e3f55 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -14,12 +14,11 @@ use Spiral\Console\Configurator\SignatureBasedConfigurator; use Spiral\Console\Event\CommandFinished; use Spiral\Console\Event\CommandStarting; -use Spiral\Console\Interceptor\AttributeInterceptor; use Spiral\Console\Traits\HelpersTrait; use Spiral\Core\CoreInterceptorInterface; -use Spiral\Core\CoreInterface; use Spiral\Core\Exception\ScopeException; -use Spiral\Core\InterceptableCore; +use Spiral\Core\Scope; +use Spiral\Core\ScopeInterface; use Spiral\Events\EventDispatcherAwareInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Input\InputInterface; @@ -29,6 +28,7 @@ /** * Provides automatic command configuration and access to global container scope. */ +#[\Spiral\Core\Attribute\Scope('console')] abstract class Command extends SymfonyCommand implements EventDispatcherAwareInterface { use HelpersTrait; @@ -73,7 +73,7 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v /** * Pass execution to "perform" method using container to resolve method dependencies. */ - protected function execute(InputInterface $input, OutputInterface $output): int + final protected function execute(InputInterface $input, OutputInterface $output): int { if ($this->container === null) { throw new ScopeException('Container is not set'); @@ -81,19 +81,35 @@ protected function execute(InputInterface $input, OutputInterface $output): int $method = method_exists($this, 'perform') ? 'perform' : '__invoke'; - $core = $this->buildCore(); - try { [$this->input, $this->output] = [$this->prepareInput($input), $this->prepareOutput($input, $output)]; $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, $this->output)); // Executing perform method with method injection - $code = (int)$core->callAction(static::class, $method, [ - 'input' => $this->input, - 'output' => $this->output, - 'command' => $this, - ]); + $code = $this->container->get(ScopeInterface::class) + ->runScope( + new Scope( + name: 'console.command', + bindings: [ + InputInterface::class => $input, + OutputInterface::class => $output, + ], + autowire: true, + ), + fn(CommandCoreFactory $factory) => $factory->make( + $this->interceptors, + $this->eventDispatcher, + )->callAction( + static::class, + $method, + [ + 'input' => $this->input, + 'output' => $this->output, + 'command' => $this, + ], + ), + ); $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, $this->output)); @@ -103,20 +119,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - protected function buildCore(): CoreInterface - { - $core = $this->container->get(CommandCore::class); - - $interceptableCore = new InterceptableCore($core, $this->eventDispatcher); - - foreach ($this->interceptors as $interceptor) { - $interceptableCore->addInterceptor($this->container->get($interceptor)); - } - $interceptableCore->addInterceptor($this->container->get(AttributeInterceptor::class)); - - return $interceptableCore; - } - protected function prepareInput(InputInterface $input): InputInterface { return $input; diff --git a/src/Console/src/CommandCore.php b/src/Console/src/CommandCore.php index 436043af3..895139bb2 100644 --- a/src/Console/src/CommandCore.php +++ b/src/Console/src/CommandCore.php @@ -4,15 +4,17 @@ namespace Spiral\Console; +use Spiral\Core\Attribute\Scope as ScopeAttribute; use Spiral\Core\CoreInterface; use Spiral\Core\InvokerInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +#[ScopeAttribute('console.command')] final class CommandCore implements CoreInterface { public function __construct( - private readonly InvokerInterface $invoker + private readonly InvokerInterface $invoker, ) { } @@ -21,10 +23,8 @@ public function __construct( */ public function callAction(string $controller, string $action, array $parameters = []): int { - $input = $parameters['input']; - $output = $parameters['output']; $command = $parameters['command']; - return (int)$this->invoker->invoke([$command, $action], \compact('input', 'output')); + return (int)$this->invoker->invoke([$command, $action]); } } diff --git a/src/Console/src/CommandCoreFactory.php b/src/Console/src/CommandCoreFactory.php new file mode 100644 index 000000000..b07686307 --- /dev/null +++ b/src/Console/src/CommandCoreFactory.php @@ -0,0 +1,36 @@ +container->get(CommandCore::class); + + $interceptableCore = new InterceptableCore($core, $eventDispatcher); + + foreach ($interceptors as $interceptor) { + $interceptableCore->addInterceptor($this->container->get($interceptor)); + } + $interceptableCore->addInterceptor($this->container->get(AttributeInterceptor::class)); + + return $interceptableCore; + } +} diff --git a/src/Console/src/Console.php b/src/Console/src/Console.php index e7cd5a64b..cdba03289 100644 --- a/src/Console/src/Console.php +++ b/src/Console/src/Console.php @@ -8,7 +8,9 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Console\Config\ConsoleConfig; use Spiral\Console\Exception\LocatorException; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\Container; +use Spiral\Core\Scope; use Spiral\Core\ScopeInterface; use Spiral\Events\EventDispatcherAwareInterface; use Symfony\Component\Console\Application; @@ -22,6 +24,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface; +#[\Spiral\Core\Attribute\Scope('console')] final class Console { // Undefined response code for command (errors). See below. @@ -32,9 +35,9 @@ final class Console public function __construct( private readonly ConsoleConfig $config, private readonly ?LocatorInterface $locator = null, - private readonly ContainerInterface $container = new Container(), + #[Proxy] private readonly ContainerInterface $container = new Container(), private readonly ScopeInterface $scope = new Container(), - private readonly ?EventDispatcherInterface $dispatcher = null + private readonly ?EventDispatcherInterface $dispatcher = null, ) { } @@ -45,14 +48,11 @@ public function __construct( */ public function start(InputInterface $input = new ArgvInput(), OutputInterface $output = new ConsoleOutput()): int { - return $this->scope->runScope( - [], - fn () => $this->run( - $input->getFirstArgument() ?? 'list', - $input, - $output - )->getCode() - ); + return $this->run( + $input->getFirstArgument() ?? 'list', + $input, + $output, + )->getCode(); } /** @@ -64,7 +64,7 @@ public function start(InputInterface $input = new ArgvInput(), OutputInterface $ public function run( ?string $command, array|InputInterface $input = [], - OutputInterface $output = new BufferedOutput() + OutputInterface $output = new BufferedOutput(), ): CommandOutput { $input = \is_array($input) ? new ArrayInput($input) : $input; @@ -74,10 +74,15 @@ public function run( $input = new InputProxy($input, ['firstArgument' => $command]); } - $code = $this->scope->runScope([ - InputInterface::class => $input, - OutputInterface::class => $output, - ], fn () => $this->getApplication()->doRun($input, $output)); + $code = $this->scope->runScope( + new Scope( + bindings: [ + InputInterface::class => $input, + OutputInterface::class => $output, + ], + ), + fn() => $this->getApplication()->doRun($input, $output), + ); return new CommandOutput($code ?? self::CODE_NONE, $output); } @@ -108,7 +113,7 @@ public function getApplication(): Application $static = new StaticLocator( $this->config->getCommands(), $this->config->getInterceptors(), - $this->container + $this->container, ); $this->addCommands($static->locateCommands()); @@ -161,7 +166,7 @@ private function configureIO(InputInterface $input, OutputInterface $output): vo } } - match ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { + match ($shellVerbosity = (int)getenv('SHELL_VERBOSITY')) { -1 => $output->setVerbosity(OutputInterface::VERBOSITY_QUIET), 1 => $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE), 2 => $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE), diff --git a/src/Framework/Console/Confirmation/ApplicationInProduction.php b/src/Framework/Console/Confirmation/ApplicationInProduction.php index e6227f5ef..5b702373e 100644 --- a/src/Framework/Console/Confirmation/ApplicationInProduction.php +++ b/src/Framework/Console/Confirmation/ApplicationInProduction.php @@ -6,9 +6,11 @@ use Spiral\Boot\Environment\AppEnvironment; use Spiral\Console\Traits\HelpersTrait; +use Spiral\Core\Attribute\Scope; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +#[Scope('console.command')] final class ApplicationInProduction { use HelpersTrait; diff --git a/src/Scaffolder/src/Command/AbstractCommand.php b/src/Scaffolder/src/Command/AbstractCommand.php index e1447507c..bf28071ef 100644 --- a/src/Scaffolder/src/Command/AbstractCommand.php +++ b/src/Scaffolder/src/Command/AbstractCommand.php @@ -7,6 +7,7 @@ use Psr\Container\ContainerInterface; use Spiral\Boot\DirectoriesInterface; use Spiral\Console\Command; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\FactoryInterface; use Spiral\Files\FilesInterface; use Spiral\Reactor\Writer; @@ -19,7 +20,7 @@ abstract class AbstractCommand extends Command public function __construct( protected ScaffolderConfig $config, protected FilesInterface $files, - ContainerInterface $container, + #[Proxy] ContainerInterface $container, private readonly FactoryInterface $factory, private readonly DirectoriesInterface $dirs, ) { diff --git a/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php b/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php new file mode 100644 index 000000000..0a06a7b1e --- /dev/null +++ b/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php @@ -0,0 +1,29 @@ +assertConsoleCommandOutputContainsStrings('app-in-production', [], [ + 'Application is in production.', + ]); + } + + #[Env('APP_ENV', 'testing')] + public function testRunCommandInTestingEnv(): void + { + $this->assertConsoleCommandOutputContainsStrings('app-in-production', [], [ + 'Application is in testing.', + ]); + } +} diff --git a/tests/app/src/Command/ApplicationInProductionCommand.php b/tests/app/src/Command/ApplicationInProductionCommand.php new file mode 100644 index 000000000..d6bd39b55 --- /dev/null +++ b/tests/app/src/Command/ApplicationInProductionCommand.php @@ -0,0 +1,25 @@ +confirmToProceed('Application in production.')) { + $this->writeln('Application is in production.'); + } + + $this->writeln('Application is in testing.'); + + return self::SUCCESS; + } +} From 82fed4525fc0c80030f906f27d359b2aeea3a15e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 20 Feb 2024 08:58:11 +0000 Subject: [PATCH 26/84] Apply fixes from StyleCI [ci skip] [skip ci] --- src/Console/src/Command.php | 2 +- src/Console/src/Console.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 51f3e3f55..92341be8c 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -97,7 +97,7 @@ final protected function execute(InputInterface $input, OutputInterface $output) ], autowire: true, ), - fn(CommandCoreFactory $factory) => $factory->make( + fn (CommandCoreFactory $factory) => $factory->make( $this->interceptors, $this->eventDispatcher, )->callAction( diff --git a/src/Console/src/Console.php b/src/Console/src/Console.php index cdba03289..e076e60a1 100644 --- a/src/Console/src/Console.php +++ b/src/Console/src/Console.php @@ -81,7 +81,7 @@ public function run( OutputInterface::class => $output, ], ), - fn() => $this->getApplication()->doRun($input, $output), + fn () => $this->getApplication()->doRun($input, $output), ); return new CommandOutput($code ?? self::CODE_NONE, $output); From 2f9c1e0c2b74e99db6e37b33a7b267f3ccbb7a24 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 11:53:41 +0200 Subject: [PATCH 27/84] Add PaginationFactory binding --- src/Framework/Bootloader/Http/PaginationBootloader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Framework/Bootloader/Http/PaginationBootloader.php b/src/Framework/Bootloader/Http/PaginationBootloader.php index 0d4a0c28b..c8355b636 100644 --- a/src/Framework/Bootloader/Http/PaginationBootloader.php +++ b/src/Framework/Bootloader/Http/PaginationBootloader.php @@ -27,9 +27,9 @@ public function defineDependencies(): array public function defineSingletons(): array { - $this->binder - ->getBinder(Spiral::HttpRequest) - ->bindSingleton(PaginationProviderInterface::class, PaginationFactory::class); + $httpRequest = $this->binder->getBinder(Spiral::HttpRequest); + $httpRequest->bindSingleton(PaginationFactory::class, PaginationFactory::class); + $httpRequest->bindSingleton(PaginationProviderInterface::class, PaginationFactory::class); $this->binder->bind( PaginationProviderInterface::class, From 79d18b76c7a2b907591bdd46ffa8e9b24d1f978b Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 12:25:21 +0200 Subject: [PATCH 28/84] Simplify unit tests --- ...AuthTransportWithStorageMiddlewareTest.php | 2 +- src/AuthHttp/tests/CookieTransportTest.php | 2 +- src/AuthHttp/tests/HeaderTransportTest.php | 15 ++++----- .../tests/Middleware/AuthMiddlewareTest.php | 4 +-- .../Firewall/BaseFirewallTestCase.php | 4 +-- src/AuthHttp/tests/ScopedTestCase.php | 19 ----------- src/Cookies/tests/CookiesTest.php | 24 ++++---------- src/Csrf/tests/CsrfTest.php | 33 ++++++------------- src/Filters/tests/InputScopeTest.php | 12 ++----- src/Http/tests/HttpTest.php | 13 +++++++- src/Http/tests/PipelineTest.php | 12 ++++--- src/Http/tests/ScopedTestCase.php | 30 ----------------- src/Router/tests/BaseTestCase.php | 12 +------ 13 files changed, 51 insertions(+), 131 deletions(-) delete mode 100644 src/AuthHttp/tests/ScopedTestCase.php delete mode 100644 src/Http/tests/ScopedTestCase.php diff --git a/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php b/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php index 5e80da6bb..670b5a070 100644 --- a/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php +++ b/src/AuthHttp/tests/AuthTransportWithStorageMiddlewareTest.php @@ -17,7 +17,7 @@ use Spiral\Auth\TransportRegistry; use Spiral\Core\ScopeInterface; -final class AuthTransportWithStorageMiddlewareTest extends ScopedTestCase +final class AuthTransportWithStorageMiddlewareTest extends BaseTestCase { public function testProcessMiddlewareWithTokenStorageProvider(): void { diff --git a/src/AuthHttp/tests/CookieTransportTest.php b/src/AuthHttp/tests/CookieTransportTest.php index a0923146d..32dce0d50 100644 --- a/src/AuthHttp/tests/CookieTransportTest.php +++ b/src/AuthHttp/tests/CookieTransportTest.php @@ -20,7 +20,7 @@ use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; use Spiral\Tests\Auth\Stub\TestAuthHttpToken; -final class CookieTransportTest extends ScopedTestCase +final class CookieTransportTest extends BaseTestCase { public function testCookieToken(): void { diff --git a/src/AuthHttp/tests/HeaderTransportTest.php b/src/AuthHttp/tests/HeaderTransportTest.php index 6b587dbc3..da9438e3c 100644 --- a/src/AuthHttp/tests/HeaderTransportTest.php +++ b/src/AuthHttp/tests/HeaderTransportTest.php @@ -4,7 +4,6 @@ namespace Spiral\Tests\Auth; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Spiral\Auth\HttpTransportInterface; use Spiral\Auth\Middleware\AuthMiddleware; @@ -19,14 +18,14 @@ use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; use Spiral\Tests\Auth\Stub\TestAuthHttpToken; -final class HeaderTransportTest extends ScopedTestCase +final class HeaderTransportTest extends BaseTestCase { public function testHeaderToken(): void { $http = $this->getCore(new HeaderTransport()); $http->setHandler( - static function (ServerRequestInterface $request, ResponseInterface $response): void { + static function (ServerRequestInterface $request): void { if ($request->getAttribute('authContext')->getToken() === null) { echo 'no token'; } else { @@ -50,7 +49,7 @@ public function testHeaderTokenWithCustomValueFormat(): void $http = $this->getCore(new HeaderTransport('Authorization', 'Bearer %s')); $http->setHandler( - static function (ServerRequestInterface $request, ResponseInterface $response): void { + static function (ServerRequestInterface $request): void { if ($request->getAttribute('authContext')->getToken() === null) { echo 'no token'; } else { @@ -73,7 +72,7 @@ public function testBadHeaderToken(): void $http = $this->getCore(new HeaderTransport()); $http->setHandler( - static function (ServerRequestInterface $request, ResponseInterface $response): void { + static function (ServerRequestInterface $request): void { if ($request->getAttribute('authContext')->getToken() === null) { echo 'no token'; } else { @@ -97,7 +96,7 @@ public function testDeleteToken(): void $http = $this->getCore(new HeaderTransport()); $http->setHandler( - static function (ServerRequestInterface $request, ResponseInterface $response): void { + static function (ServerRequestInterface $request): void { $request->getAttribute('authContext')->close(); echo 'closed'; } @@ -115,7 +114,7 @@ public function testCommitToken(): void $http = $this->getCore(new HeaderTransport()); $http->setHandler( - static function (ServerRequestInterface $request, ResponseInterface $response): void { + static function (ServerRequestInterface $request): void { $request->getAttribute('authContext')->start( new TestAuthHttpToken('new-token', ['ok' => 1]) ); @@ -132,7 +131,7 @@ public function testCommitTokenWithCustomValueFormat(): void $http = $this->getCore(new HeaderTransport('Authorization', 'Bearer %s')); $http->setHandler( - static function (ServerRequestInterface $request, ResponseInterface $response): void { + static function (ServerRequestInterface $request): void { $request->getAttribute('authContext')->start( new TestAuthHttpToken('new-token', ['ok' => 1]) ); diff --git a/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php b/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php index 90ec2ba87..e4fecdb65 100644 --- a/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php +++ b/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php @@ -12,13 +12,13 @@ use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; +use Spiral\Tests\Auth\BaseTestCase; use Spiral\Tests\Auth\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; -use Spiral\Tests\Auth\ScopedTestCase; use Spiral\Tests\Auth\Stub\TestAuthHttpProvider; use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; -final class AuthMiddlewareTest extends ScopedTestCase +final class AuthMiddlewareTest extends BaseTestCase { public function testAttributeRead(): void { diff --git a/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php b/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php index fdf70bfd7..dfaf35afe 100644 --- a/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php +++ b/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php @@ -11,12 +11,12 @@ use Spiral\Http\Config\HttpConfig; use Spiral\Http\Http; use Spiral\Http\Pipeline; +use Spiral\Tests\Auth\BaseTestCase; use Spiral\Tests\Auth\Diactoros\ResponseFactory; -use Spiral\Tests\Auth\ScopedTestCase; use Spiral\Tests\Auth\Stub\TestAuthHttpProvider; use Spiral\Tests\Auth\Stub\TestAuthHttpStorage; -abstract class BaseFirewallTestCase extends ScopedTestCase +abstract class BaseFirewallTestCase extends BaseTestCase { protected function getCore(AbstractFirewall $firewall, HttpTransportInterface $transport): Http { diff --git a/src/AuthHttp/tests/ScopedTestCase.php b/src/AuthHttp/tests/ScopedTestCase.php deleted file mode 100644 index 573f3db1a..000000000 --- a/src/AuthHttp/tests/ScopedTestCase.php +++ /dev/null @@ -1,19 +0,0 @@ -container->runScope(new Scope('http'), function (Container $container): mixed { - $this->container = $container; - return parent::runTest(); - }); - } -} diff --git a/src/Cookies/tests/CookiesTest.php b/src/Cookies/tests/CookiesTest.php index 748e4ba91..96135e1d1 100644 --- a/src/Cookies/tests/CookiesTest.php +++ b/src/Cookies/tests/CookiesTest.php @@ -11,7 +11,6 @@ use Spiral\Cookies\CookieQueue; use Spiral\Cookies\Middleware\CookiesMiddleware; use Spiral\Core\Container; -use Spiral\Core\Scope; use Spiral\Encrypter\Config\EncrypterConfig; use Spiral\Encrypter\Encrypter; use Spiral\Encrypter\EncrypterFactory; @@ -21,8 +20,6 @@ use Spiral\Http\Http; use Spiral\Http\Pipeline; use Nyholm\Psr7\ServerRequest; -use Spiral\Telemetry\NullTracer; -use Spiral\Telemetry\TracerInterface; final class CookiesTest extends TestCase { @@ -253,7 +250,7 @@ public function testHMAC(): void $this->assertSame('value', (string)$response->getBody()); } - protected function httpCore(array $middleware = []): Http + private function httpCore(array $middleware = []): Http { $config = new HttpConfig([ 'basePath' => '/', @@ -271,9 +268,9 @@ protected function httpCore(array $middleware = []): Http ); } - protected function get( + private function get( Http $core, - $uri, + string $uri, array $query = [], array $headers = [], array $cookies = [] @@ -281,8 +278,8 @@ protected function get( return $core->handle($this->request($uri, 'GET', $query, $headers, $cookies)); } - protected function request( - $uri, + private function request( + string $uri, string $method, array $query = [], array $headers = [], @@ -295,7 +292,7 @@ protected function request( ->withCookieParams($cookies); } - protected function fetchCookies(ResponseInterface $response) + private function fetchCookies(ResponseInterface $response): array { $result = []; @@ -310,13 +307,4 @@ protected function fetchCookies(ResponseInterface $response) return $result; } - - protected function runTest(): mixed - { - return $this->container->runScope(new Scope('http'), function (Container $container): mixed { - $this->container = $container; - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); - return parent::runTest(); - }); - } } diff --git a/src/Csrf/tests/CsrfTest.php b/src/Csrf/tests/CsrfTest.php index 7e6c37c81..de3792cc4 100644 --- a/src/Csrf/tests/CsrfTest.php +++ b/src/Csrf/tests/CsrfTest.php @@ -20,7 +20,7 @@ use Spiral\Telemetry\NullTracer; use Spiral\Telemetry\TracerInterface; -class CsrfTest extends TestCase +final class CsrfTest extends TestCase { private Container $container; @@ -38,11 +38,7 @@ public function setUp(): void ) ); - $this->container->bind( - TracerInterface::class, - new NullTracer($this->container) - ); - + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); $this->container->bind( ResponseFactoryInterface::class, new TestResponseFactory(new HttpConfig(['headers' => []])) @@ -215,7 +211,7 @@ static function () { self::assertSame('all good', (string)$response->getBody()); } - protected function httpCore(array $middleware = []): Http + private function httpCore(array $middleware = []): Http { $config = new HttpConfig( [ @@ -235,9 +231,9 @@ protected function httpCore(array $middleware = []): Http ); } - protected function get( + private function get( Http $core, - $uri, + string $uri, array $query = [], array $headers = [], array $cookies = [] @@ -245,9 +241,9 @@ protected function get( return $core->handle($this->request($uri, 'GET', $query, $headers, $cookies)); } - protected function post( + private function post( Http $core, - $uri, + string $uri, array $data = [], array $headers = [], array $cookies = [] @@ -255,8 +251,8 @@ protected function post( return $core->handle($this->request($uri, 'POST', [], $headers, $cookies)->withParsedBody($data)); } - protected function request( - $uri, + private function request( + string $uri, string $method, array $query = [], array $headers = [], @@ -269,7 +265,7 @@ protected function request( ->withCookieParams($cookies); } - protected function fetchCookies(ResponseInterface $response): array + private function fetchCookies(ResponseInterface $response): array { $result = []; @@ -287,13 +283,4 @@ protected function fetchCookies(ResponseInterface $response): array return $result; } - - protected function runTest(): mixed - { - return $this->container->runScope(new Scope('http'), function (Container $container): mixed { - $this->container = $container; - - return parent::runTest(); - }); - } } diff --git a/src/Filters/tests/InputScopeTest.php b/src/Filters/tests/InputScopeTest.php index 11b78baaf..54de734d0 100644 --- a/src/Filters/tests/InputScopeTest.php +++ b/src/Filters/tests/InputScopeTest.php @@ -6,10 +6,9 @@ use Nyholm\Psr7\ServerRequest; use Psr\Http\Message\ServerRequestInterface; -use Spiral\Core\Container; -use Spiral\Core\Scope; use Spiral\Filter\InputScope; use Spiral\Filters\InputInterface; +use Spiral\Http\Request\InputManager; final class InputScopeTest extends BaseTestCase { @@ -18,6 +17,7 @@ public function setUp(): void parent::setUp(); $this->container->bindSingleton(InputInterface::class, InputScope::class); + $this->container->bindSingleton(InputManager::class, new InputManager($this->container)); $this->container->bindSingleton( ServerRequestInterface::class, (new ServerRequest('POST', '/test'))->withParsedBody([ @@ -68,12 +68,4 @@ private function getInput(): InputInterface { return $this->container->get(InputInterface::class); } - - protected function runTest(): mixed - { - return $this->container->runScope(new Scope('http.request'), function (Container $container): mixed { - $this->container = $container; - return parent::runTest(); - }); - } } diff --git a/src/Http/tests/HttpTest.php b/src/Http/tests/HttpTest.php index a61602efe..0ff8b06c0 100644 --- a/src/Http/tests/HttpTest.php +++ b/src/Http/tests/HttpTest.php @@ -5,7 +5,9 @@ namespace Spiral\Tests\Http; use Mockery as m; +use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; +use Spiral\Core\Container; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Event\RequestHandled; @@ -15,13 +17,22 @@ use Spiral\Http\Pipeline; use Spiral\Telemetry\NullTracer; use Spiral\Telemetry\TracerFactoryInterface; +use Spiral\Telemetry\TracerInterface; use Spiral\Tests\Http\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; -final class HttpTest extends ScopedTestCase +final class HttpTest extends TestCase { use m\Adapter\Phpunit\MockeryPHPUnitIntegration; + private Container $container; + + public function setUp(): void + { + $this->container = new Container(); + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); + } + public function testGetPipeline(): void { $core = $this->getCore(); diff --git a/src/Http/tests/PipelineTest.php b/src/Http/tests/PipelineTest.php index 950c7ef23..e61fdbbc3 100644 --- a/src/Http/tests/PipelineTest.php +++ b/src/Http/tests/PipelineTest.php @@ -4,11 +4,13 @@ namespace Spiral\Tests\Http; +use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Spiral\Core\Container; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Event\MiddlewareProcessing; @@ -18,11 +20,11 @@ use Spiral\Tests\Http\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; -final class PipelineTest extends ScopedTestCase +final class PipelineTest extends TestCase { public function testTarget(): void { - $pipeline = new Pipeline($this->container, $this->container); + $pipeline = new Pipeline(new Container(), new Container()); $handler = new CallableHandler(function () { return 'response'; @@ -37,7 +39,7 @@ public function testTarget(): void public function testHandle(): void { - $pipeline = new Pipeline($this->container, $this->container); + $pipeline = new Pipeline(new Container(), new Container()); $handler = new CallableHandler(function () { return 'response'; @@ -54,7 +56,7 @@ public function testHandleException(): void { $this->expectException(PipelineException::class); - $pipeline = new Pipeline($this->container, $this->container); + $pipeline = new Pipeline(new Container(), new Container()); $pipeline->handle(new ServerRequest('GET', '')); } @@ -77,7 +79,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->method('dispatch') ->with(new MiddlewareProcessing($request, $middleware)); - $pipeline = new Pipeline($this->container, $this->container, $dispatcher, new NullTracer($this->container)); + $pipeline = new Pipeline(new Container(), new Container(), $dispatcher, new NullTracer(new Container())); $pipeline->pushMiddleware($middleware); diff --git a/src/Http/tests/ScopedTestCase.php b/src/Http/tests/ScopedTestCase.php deleted file mode 100644 index 4f3f97182..000000000 --- a/src/Http/tests/ScopedTestCase.php +++ /dev/null @@ -1,30 +0,0 @@ -container = new Container(); - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); - } - - protected function runTest(): mixed - { - return $this->container->runScope(new Scope('http'), function (Container $container): mixed { - $this->container = $container; - return parent::runTest(); - }); - } -} diff --git a/src/Router/tests/BaseTestCase.php b/src/Router/tests/BaseTestCase.php index 921ffed2c..6d154ad5e 100644 --- a/src/Router/tests/BaseTestCase.php +++ b/src/Router/tests/BaseTestCase.php @@ -12,7 +12,6 @@ use Spiral\Core\Container; use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterface; -use Spiral\Core\Scope; use Spiral\Http\Config\HttpConfig; use Spiral\Http\CurrentRequest; use Spiral\Router\GroupRegistry; @@ -76,6 +75,7 @@ public static function middlewaresDataProvider(): \Traversable private function initContainer(): void { $this->container = new Container(); + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); $this->container->bind(ResponseFactoryInterface::class, new ResponseFactory(new HttpConfig(['headers' => []]))); $this->container->bind(UriFactoryInterface::class, new UriFactory()); $this->container->bind( @@ -101,14 +101,4 @@ private function initRouter(): void $this->router = $this->makeRouter(); $this->container->bindSingleton(RouterInterface::class, $this->router); } - - protected function runTest(): mixed - { - return $this->container->runScope(new Scope('http'), function (Container $container): mixed { - $this->container = $container; - $this->container->bind(TracerInterface::class, new NullTracer($this->container)); - - return parent::runTest(); - }); - } } From a54de6bfb4d1ce6fdae3f01c2ec92b4f4b9337c9 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 12:57:29 +0200 Subject: [PATCH 29/84] Deprecate SectionScope --- src/Framework/Session/SectionScope.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Framework/Session/SectionScope.php b/src/Framework/Session/SectionScope.php index 41b98670e..278c3c131 100644 --- a/src/Framework/Session/SectionScope.php +++ b/src/Framework/Session/SectionScope.php @@ -4,10 +4,13 @@ namespace Spiral\Session; +/** + * @deprecated Use {@see SessionInterface::getSection()} instead. Will be removed in v4.0. + */ final class SectionScope implements SessionSectionInterface { public function __construct( - private readonly SessionInterface $session, + private readonly SessionScope $session, private readonly string $name ) { } From 07b2f37fafebac438ab43d0844e0813bbf6d7b44 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 13:30:38 +0200 Subject: [PATCH 30/84] Remove unused code --- src/Cookies/tests/CookiesTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cookies/tests/CookiesTest.php b/src/Cookies/tests/CookiesTest.php index 96135e1d1..0f07f837b 100644 --- a/src/Cookies/tests/CookiesTest.php +++ b/src/Cookies/tests/CookiesTest.php @@ -173,7 +173,7 @@ public function testDelete(): void public function testUnprotected(): void { - $this->container->getBinder('root')->bind(CookiesConfig::class, new CookiesConfig([ + $this->container->bind(CookiesConfig::class, new CookiesConfig([ 'domain' => '.%s', 'method' => CookiesConfig::COOKIE_UNPROTECTED, 'excluded' => ['PHPSESSID', 'csrf-token'] @@ -197,7 +197,7 @@ public function testUnprotected(): void public function testGetUnprotected(): void { - $this->container->getBinder('root')->bind(CookiesConfig::class, new CookiesConfig([ + $this->container->bind(CookiesConfig::class, new CookiesConfig([ 'domain' => '.%s', 'method' => CookiesConfig::COOKIE_UNPROTECTED, 'excluded' => ['PHPSESSID', 'csrf-token'] From 35649c9d3216268d59c7c0cba2e54d8da5d05216 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 13:40:32 +0200 Subject: [PATCH 31/84] Add for CookieQueue in scope --- src/Cookies/tests/CookiesTest.php | 40 +++++++++------------------- tests/Framework/Http/CookiesTest.php | 30 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/Cookies/tests/CookiesTest.php b/src/Cookies/tests/CookiesTest.php index 0f07f837b..a1c989472 100644 --- a/src/Cookies/tests/CookiesTest.php +++ b/src/Cookies/tests/CookiesTest.php @@ -7,6 +7,7 @@ use Defuse\Crypto\Key; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Spiral\Cookies\Config\CookiesConfig; use Spiral\Cookies\CookieQueue; use Spiral\Cookies\Middleware\CookiesMiddleware; @@ -45,10 +46,10 @@ public function setUp(): void $this->container->bind(EncrypterInterface::class, Encrypter::class); } - public function testScope(): void + public function testCookieQueueInRequestAttribute(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { + $core->setHandler(function (ServerRequestInterface $r) { $this->assertInstanceOf(CookieQueue::class, $r->getAttribute(CookieQueue::ATTRIBUTE)); return 'all good'; }); @@ -61,7 +62,7 @@ public function testScope(): void public function testSetEncryptedCookie(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { + $core->setHandler(function (ServerRequestInterface $r) { $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); return 'all good'; @@ -82,7 +83,7 @@ public function testSetEncryptedCookie(): void public function testSetNotProtectedCookie(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { + $core->setHandler(function (ServerRequestInterface $r) { $r->getAttribute(CookieQueue::ATTRIBUTE)->set('PHPSESSID', 'value'); return 'all good'; @@ -100,11 +101,7 @@ public function testSetNotProtectedCookie(): void public function testDecrypt(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { - - /** - * @var ServerRequest $r - */ + $core->setHandler(function (ServerRequestInterface $r) { return $r->getCookieParams()['name']; }); @@ -118,11 +115,7 @@ public function testDecrypt(): void public function testDecryptArray(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { - - /** - * @var ServerRequest $r - */ + $core->setHandler(function (ServerRequestInterface $r) { return $r->getCookieParams()['name'][0]; }); @@ -136,11 +129,7 @@ public function testDecryptArray(): void public function testDecryptBroken(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { - - /** - * @var ServerRequest $r - */ + $core->setHandler(function (ServerRequestInterface $r) { return $r->getCookieParams()['name']; }); @@ -154,9 +143,8 @@ public function testDecryptBroken(): void public function testDelete(): void { $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { + $core->setHandler(function (ServerRequestInterface $r) { $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); - $r->getAttribute(CookieQueue::ATTRIBUTE)->delete('name'); return 'all good'; @@ -180,7 +168,7 @@ public function testUnprotected(): void ])); $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { + $core->setHandler(function (ServerRequestInterface $r) { $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); return 'all good'; @@ -204,11 +192,7 @@ public function testGetUnprotected(): void ])); $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { - - /** - * @var ServerRequest $r - */ + $core->setHandler(function (ServerRequestInterface $r) { return $r->getCookieParams()['name']; }); @@ -228,7 +212,7 @@ public function testHMAC(): void ])); $core = $this->httpCore([CookiesMiddleware::class]); - $core->setHandler(function ($r) { + $core->setHandler(function (ServerRequestInterface $r) { $r->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value'); return 'all good'; diff --git a/tests/Framework/Http/CookiesTest.php b/tests/Framework/Http/CookiesTest.php index 5227d2282..5252fcf00 100644 --- a/tests/Framework/Http/CookiesTest.php +++ b/tests/Framework/Http/CookiesTest.php @@ -7,6 +7,8 @@ use Psr\Http\Message\ServerRequestInterface; use Spiral\Cookies\Cookie; use Spiral\Cookies\CookieManager; +use Spiral\Cookies\CookieQueue; +use Spiral\Core\ContainerScope; use Spiral\Core\Exception\ScopeException; use Spiral\Encrypter\EncrypterInterface; use Spiral\Framework\Spiral; @@ -39,6 +41,34 @@ public function testOutsideOfScopeFail(): void $this->cookies()->get('name'); } + public function testCookieQueueInScope(): void + { + $this->setHttpHandler(static function (ServerRequestInterface $request) { + ContainerScope::getContainer()->bindSingleton(ServerRequestInterface::class, $request); + + self::assertInstanceOf( + CookieQueue::class, + ContainerScope::getContainer()->get(ServerRequestInterface::class)->getAttribute(CookieQueue::ATTRIBUTE) + ); + + self::assertSame( + ContainerScope::getContainer() + ->get(ServerRequestInterface::class) + ->getAttribute(CookieQueue::ATTRIBUTE), + $request->getAttribute(CookieQueue::ATTRIBUTE) + ); + + self::assertSame( + ContainerScope::getContainer() + ->get(ServerRequestInterface::class) + ->getAttribute(CookieQueue::ATTRIBUTE), + ContainerScope::getContainer()->get(CookieQueue::class) + ); + }); + + $this->fakeHttp()->get('/')->assertOk(); + } + public function testHasCookie(): void { $this->setHttpHandler(function (ServerRequestInterface $request) { From 0440963b18aad0e735be44b9743e65bcc3c66c2c Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 13:44:09 +0200 Subject: [PATCH 32/84] Remove unused code --- src/Csrf/tests/CsrfTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Csrf/tests/CsrfTest.php b/src/Csrf/tests/CsrfTest.php index de3792cc4..1961da47f 100644 --- a/src/Csrf/tests/CsrfTest.php +++ b/src/Csrf/tests/CsrfTest.php @@ -66,7 +66,7 @@ static function ($r) { public function testLengthException(): void { $this->expectException(\RuntimeException::class); - $this->container->getBinder('root')->bind( + $this->container->bind( CsrfConfig::class, new CsrfConfig( [ From 23bea6d6f8cec82454976e8cd3746d375f2a07ec Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 15:03:06 +0200 Subject: [PATCH 33/84] Add http scope to Session, SessionFactory --- src/AuthHttp/tests/BaseTestCase.php | 5 +++- src/Cookies/tests/CookiesTest.php | 5 +++- src/Csrf/tests/CsrfTest.php | 6 +++-- .../Bootloader/Http/SessionBootloader.php | 10 +++++--- src/Http/tests/HttpTest.php | 5 +++- src/Http/tests/PipelineTest.php | 10 ++++---- src/Http/tests/TestCase.php | 23 +++++++++++++++++++ src/Router/tests/BaseTestCase.php | 5 +++- src/Session/src/Session.php | 2 ++ src/Session/src/SessionFactory.php | 2 ++ src/Session/tests/FactoryTest.php | 12 ++++------ src/Session/tests/SessionTest.php | 19 ++++++--------- src/Session/tests/TestCase.php | 20 ++++++++++++++++ 13 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 src/Http/tests/TestCase.php create mode 100644 src/Session/tests/TestCase.php diff --git a/src/AuthHttp/tests/BaseTestCase.php b/src/AuthHttp/tests/BaseTestCase.php index e35fff70b..fac77fa7d 100644 --- a/src/AuthHttp/tests/BaseTestCase.php +++ b/src/AuthHttp/tests/BaseTestCase.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Spiral\Core\Container; +use Spiral\Core\Options; use Spiral\Telemetry\NullTracer; use Spiral\Telemetry\TracerInterface; @@ -15,7 +16,9 @@ abstract class BaseTestCase extends TestCase protected function setUp(): void { - $this->container = new Container(); + $options = new Options(); + $options->checkScope = false; + $this->container = new Container(options: $options); $this->container->bind( TracerInterface::class, diff --git a/src/Cookies/tests/CookiesTest.php b/src/Cookies/tests/CookiesTest.php index a1c989472..aafc474c0 100644 --- a/src/Cookies/tests/CookiesTest.php +++ b/src/Cookies/tests/CookiesTest.php @@ -12,6 +12,7 @@ use Spiral\Cookies\CookieQueue; use Spiral\Cookies\Middleware\CookiesMiddleware; use Spiral\Core\Container; +use Spiral\Core\Options; use Spiral\Encrypter\Config\EncrypterConfig; use Spiral\Encrypter\Encrypter; use Spiral\Encrypter\EncrypterFactory; @@ -28,7 +29,9 @@ final class CookiesTest extends TestCase public function setUp(): void { - $this->container = new Container(); + $options = new Options(); + $options->checkScope = false; + $this->container = new Container(options: $options); $this->container->bind(CookiesConfig::class, new CookiesConfig([ 'domain' => '.%s', 'method' => CookiesConfig::COOKIE_ENCRYPT, diff --git a/src/Csrf/tests/CsrfTest.php b/src/Csrf/tests/CsrfTest.php index 1961da47f..24cf7530d 100644 --- a/src/Csrf/tests/CsrfTest.php +++ b/src/Csrf/tests/CsrfTest.php @@ -8,7 +8,7 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Spiral\Core\Container; -use Spiral\Core\Scope; +use Spiral\Core\Options; use Spiral\Csrf\Config\CsrfConfig; use Spiral\Csrf\Middleware\CsrfFirewall; use Spiral\Csrf\Middleware\CsrfMiddleware; @@ -26,7 +26,9 @@ final class CsrfTest extends TestCase public function setUp(): void { - $this->container = new Container(); + $options = new Options(); + $options->checkScope = false; + $this->container = new Container(options: $options); $this->container->bind( CsrfConfig::class, new CsrfConfig( diff --git a/src/Framework/Bootloader/Http/SessionBootloader.php b/src/Framework/Bootloader/Http/SessionBootloader.php index 0576db19c..ee6f23b50 100644 --- a/src/Framework/Bootloader/Http/SessionBootloader.php +++ b/src/Framework/Bootloader/Http/SessionBootloader.php @@ -45,9 +45,13 @@ static function (CurrentRequest $request): SessionInterface { public function defineSingletons(): array { - return [ - SessionFactoryInterface::class => SessionFactory::class, - ]; + $http = $this->binder->getBinder(Spiral::Http); + $http->bindSingleton(SessionFactory::class, SessionFactory::class); + $http->bindSingleton(SessionFactoryInterface::class, SessionFactory::class); + + $this->binder->bind(SessionFactoryInterface::class, new Proxy(SessionFactoryInterface::class, true)); + + return []; } /** diff --git a/src/Http/tests/HttpTest.php b/src/Http/tests/HttpTest.php index 0ff8b06c0..6b4d22319 100644 --- a/src/Http/tests/HttpTest.php +++ b/src/Http/tests/HttpTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Core\Container; +use Spiral\Core\Options; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Event\RequestHandled; @@ -29,7 +30,9 @@ final class HttpTest extends TestCase public function setUp(): void { - $this->container = new Container(); + $options = new Options(); + $options->checkScope = false; + $this->container = new Container(options: $options); $this->container->bind(TracerInterface::class, new NullTracer($this->container)); } diff --git a/src/Http/tests/PipelineTest.php b/src/Http/tests/PipelineTest.php index e61fdbbc3..b47c61d61 100644 --- a/src/Http/tests/PipelineTest.php +++ b/src/Http/tests/PipelineTest.php @@ -4,13 +4,11 @@ namespace Spiral\Tests\Http; -use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Spiral\Core\Container; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; use Spiral\Http\Event\MiddlewareProcessing; @@ -24,7 +22,7 @@ final class PipelineTest extends TestCase { public function testTarget(): void { - $pipeline = new Pipeline(new Container(), new Container()); + $pipeline = new Pipeline($this->container, $this->container); $handler = new CallableHandler(function () { return 'response'; @@ -39,7 +37,7 @@ public function testTarget(): void public function testHandle(): void { - $pipeline = new Pipeline(new Container(), new Container()); + $pipeline = new Pipeline($this->container, $this->container); $handler = new CallableHandler(function () { return 'response'; @@ -56,7 +54,7 @@ public function testHandleException(): void { $this->expectException(PipelineException::class); - $pipeline = new Pipeline(new Container(), new Container()); + $pipeline = new Pipeline($this->container, $this->container); $pipeline->handle(new ServerRequest('GET', '')); } @@ -79,7 +77,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->method('dispatch') ->with(new MiddlewareProcessing($request, $middleware)); - $pipeline = new Pipeline(new Container(), new Container(), $dispatcher, new NullTracer(new Container())); + $pipeline = new Pipeline($this->container, $this->container, $dispatcher, new NullTracer($this->container)); $pipeline->pushMiddleware($middleware); diff --git a/src/Http/tests/TestCase.php b/src/Http/tests/TestCase.php new file mode 100644 index 000000000..6c8b983fa --- /dev/null +++ b/src/Http/tests/TestCase.php @@ -0,0 +1,23 @@ +checkScope = false; + $this->container = new Container(options: $options); + $this->container->bind(TracerInterface::class, new NullTracer($this->container)); + } +} diff --git a/src/Router/tests/BaseTestCase.php b/src/Router/tests/BaseTestCase.php index 6d154ad5e..27af8ec1d 100644 --- a/src/Router/tests/BaseTestCase.php +++ b/src/Router/tests/BaseTestCase.php @@ -12,6 +12,7 @@ use Spiral\Core\Container; use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterface; +use Spiral\Core\Options; use Spiral\Http\Config\HttpConfig; use Spiral\Http\CurrentRequest; use Spiral\Router\GroupRegistry; @@ -74,7 +75,9 @@ public static function middlewaresDataProvider(): \Traversable private function initContainer(): void { - $this->container = new Container(); + $options = new Options(); + $options->checkScope = false; + $this->container = new Container(options: $options); $this->container->bind(TracerInterface::class, new NullTracer($this->container)); $this->container->bind(ResponseFactoryInterface::class, new ResponseFactory(new HttpConfig(['headers' => []]))); $this->container->bind(UriFactoryInterface::class, new UriFactory()); diff --git a/src/Session/src/Session.php b/src/Session/src/Session.php index 23379ac52..2b4ccac9a 100644 --- a/src/Session/src/Session.php +++ b/src/Session/src/Session.php @@ -4,6 +4,7 @@ namespace Spiral\Session; +use Spiral\Core\Attribute\Scope; use Spiral\Session\Exception\SessionException; /** @@ -14,6 +15,7 @@ * * @see https://www.owasp.org/index.php/Session_Management_Cheat_Sheet */ +#[Scope('http')] final class Session implements SessionInterface { /** diff --git a/src/Session/src/SessionFactory.php b/src/Session/src/SessionFactory.php index e8b36ef54..f1873f07b 100644 --- a/src/Session/src/SessionFactory.php +++ b/src/Session/src/SessionFactory.php @@ -5,6 +5,7 @@ namespace Spiral\Session; use Psr\Container\ContainerExceptionInterface; +use Spiral\Core\Attribute\Scope; use Spiral\Core\Attribute\Singleton; use Spiral\Core\FactoryInterface; use Spiral\Session\Config\SessionConfig; @@ -15,6 +16,7 @@ * Initiates session instance and configures session handlers. */ #[Singleton] +#[Scope('http')] final class SessionFactory implements SessionFactoryInterface { public function __construct( diff --git a/src/Session/tests/FactoryTest.php b/src/Session/tests/FactoryTest.php index 0129d582e..dad2fdba3 100644 --- a/src/Session/tests/FactoryTest.php +++ b/src/Session/tests/FactoryTest.php @@ -4,8 +4,6 @@ namespace Spiral\Tests\Session; -use PHPUnit\Framework\TestCase; -use Spiral\Core\Container; use Spiral\Session\Config\SessionConfig; use Spiral\Session\Exception\SessionException; use Spiral\Session\Handler\FileHandler; @@ -13,7 +11,7 @@ use Spiral\Session\SessionFactory; use Spiral\Session\SessionInterface; -class FactoryTest extends TestCase +final class FactoryTest extends TestCase { public function tearDown(): void { @@ -33,7 +31,7 @@ public function testConstructInvalid(): void 'handlers' => [ //No directory ] - ]), new Container()); + ]), $this->container); $factory->initSession('sig', 'sessionid'); } @@ -49,7 +47,7 @@ public function testAlreadyStarted(): void 'handlers' => [ //No directory ] - ]), new Container()); + ]), $this->container); $factory->initSession('sig', 'sessionid'); } @@ -64,9 +62,9 @@ public function testMultipleSessions(): void 'secure' => false, 'handler' => null, 'handlers' => [] - ]), $c = new Container()); + ]), $this->container); - $c->bind(SessionInterface::class, Session::class); + $this->container->bind(SessionInterface::class, Session::class); $session = $factory->initSession('sig'); $session->resume(); diff --git a/src/Session/tests/SessionTest.php b/src/Session/tests/SessionTest.php index a043259fa..0c12ce335 100644 --- a/src/Session/tests/SessionTest.php +++ b/src/Session/tests/SessionTest.php @@ -4,7 +4,6 @@ namespace Spiral\Tests\Session; -use PHPUnit\Framework\TestCase; use Spiral\Core\Container; use Spiral\Files\Files; use Spiral\Files\FilesInterface; @@ -16,21 +15,17 @@ use Spiral\Session\SessionInterface; use Spiral\Session\SessionSection; -class SessionTest extends TestCase +final class SessionTest extends TestCase { - - /** - * @var SessionFactory - */ - private $factory; + private SessionFactory $factory; public function setUp(): void { - $container = new Container(); - $container->bind(FilesInterface::class, Files::class); + parent::setUp(); - $container->bind(SessionInterface::class, Session::class); - $container->bind(SessionSectionInterface::class, SessionSection::class); + $this->container->bind(FilesInterface::class, Files::class); + $this->container->bind(SessionInterface::class, Session::class); + $this->container->bind(SessionSectionInterface::class, SessionSection::class); $this->factory = new SessionFactory(new SessionConfig([ 'lifetime' => 86400, @@ -39,7 +34,7 @@ public function setUp(): void 'handler' => new Container\Autowire(FileHandler::class, [ 'directory' => sys_get_temp_dir() ]), - ]), $container); + ]), $this->container); } public function tearDown(): void diff --git a/src/Session/tests/TestCase.php b/src/Session/tests/TestCase.php new file mode 100644 index 000000000..354860354 --- /dev/null +++ b/src/Session/tests/TestCase.php @@ -0,0 +1,20 @@ +checkScope = false; + $this->container = new Container(options: $options); + } +} From b7e48f4af301f7bbad7cfc7e264007cdd9cf1cb8 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 15:24:56 +0200 Subject: [PATCH 34/84] Add unit test for SessionFactoryInterface binding in root scope --- tests/Framework/Bootloader/Http/SessionBootloaderTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Framework/Bootloader/Http/SessionBootloaderTest.php b/tests/Framework/Bootloader/Http/SessionBootloaderTest.php index 1630fb8e0..5a1f6719c 100644 --- a/tests/Framework/Bootloader/Http/SessionBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/SessionBootloaderTest.php @@ -4,14 +4,22 @@ namespace Framework\Bootloader\Http; +use Spiral\Framework\Spiral; use Spiral\Session\SessionFactory; use Spiral\Session\SessionFactoryInterface; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class SessionBootloaderTest extends BaseTestCase { + #[TestScope(Spiral::Http)] public function testSessionFactoryInterfaceBinding(): void { $this->assertContainerBoundAsSingleton(SessionFactoryInterface::class, SessionFactory::class); } + + public function testSessionFactoryInterfaceBindingInRootScope(): void + { + $this->assertContainerBoundAsSingleton(SessionFactoryInterface::class, SessionFactoryInterface::class); + } } From 83c195eae1712adfe9eac545bff69fc8e52a33e7 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 15:52:45 +0200 Subject: [PATCH 35/84] Add exceptions test --- tests/Framework/Http/AuthSessionTest.php | 24 ++++++++++++++++++++++++ tests/Framework/Http/SessionTest.php | 23 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tests/Framework/Http/AuthSessionTest.php b/tests/Framework/Http/AuthSessionTest.php index 9ad68bcb6..1461af7f5 100644 --- a/tests/Framework/Http/AuthSessionTest.php +++ b/tests/Framework/Http/AuthSessionTest.php @@ -4,7 +4,11 @@ namespace Spiral\Tests\Framework\Http; +use Spiral\Auth\AuthContextInterface; +use Spiral\Auth\Exception\InvalidAuthContext; +use Spiral\Auth\Middleware\AuthMiddleware; use Spiral\Framework\Spiral; +use Spiral\Http\Config\HttpConfig; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; @@ -62,4 +66,24 @@ public function testLoginPayload(): void $this->fakeHttp()->get('/auth/token3', cookies: $result->getCookies())->assertBodySame('{"userID":1}'); } + + public function testInvalidSessionContextException(): void + { + $this->getContainer()->bind(HttpConfig::class, new HttpConfig([ + 'middleware' => [], + ])); + + $this->setHttpHandler(function (): void { + $this->expectException(InvalidAuthContext::class); + $this->expectExceptionMessage(\sprintf( + 'The `%s` attribute was not found. To use the auth, the `%s` must be configured.', + AuthMiddleware::ATTRIBUTE, + AuthMiddleware::class + )); + + $this->getContainer()->get(AuthContextInterface::class); + }); + + $this->fakeHttp()->get('/'); + } } diff --git a/tests/Framework/Http/SessionTest.php b/tests/Framework/Http/SessionTest.php index 367e3a12c..5cc9c4d1b 100644 --- a/tests/Framework/Http/SessionTest.php +++ b/tests/Framework/Http/SessionTest.php @@ -5,6 +5,9 @@ namespace Spiral\Tests\Framework\Http; use Spiral\Framework\Spiral; +use Spiral\Http\Config\HttpConfig; +use Spiral\Session\Exception\InvalidSessionContext; +use Spiral\Session\Middleware\SessionMiddleware; use Spiral\Session\SessionInterface; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; @@ -102,6 +105,26 @@ public function testDestroySession(): void ->assertBodySame('1'); } + public function testInvalidSessionContextException(): void + { + $this->getContainer()->bind(HttpConfig::class, new HttpConfig([ + 'middleware' => [], + ])); + + $this->setHttpHandler(function (): void { + $this->expectException(InvalidSessionContext::class); + $this->expectExceptionMessage(\sprintf( + 'The `%s` attribute was not found. To use the session, the `%s` must be configured.', + SessionMiddleware::ATTRIBUTE, + SessionMiddleware::class + )); + + $this->session(); + }); + + $this->fakeHttp()->get(uri: '/')->assertOk(); + } + private function session(): SessionInterface { return $this->getContainer()->get(SessionInterface::class); From 683faf54b03a3a869d84358247e38f7555b1ba78 Mon Sep 17 00:00:00 2001 From: butschster Date: Tue, 20 Feb 2024 19:19:58 +0400 Subject: [PATCH 36/84] Fixed bootloader scoped bindings --- src/Console/src/Bootloader/ConsoleBootloader.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Console/src/Bootloader/ConsoleBootloader.php b/src/Console/src/Bootloader/ConsoleBootloader.php index e522837ec..b53ab06fa 100644 --- a/src/Console/src/Bootloader/ConsoleBootloader.php +++ b/src/Console/src/Bootloader/ConsoleBootloader.php @@ -9,13 +9,17 @@ use Spiral\Config\ConfiguratorInterface; use Spiral\Config\Patch\Append; use Spiral\Config\Patch\Prepend; +use Spiral\Console\CommandCore; +use Spiral\Console\CommandCoreFactory; use Spiral\Console\CommandLocatorListener; use Spiral\Console\Config\ConsoleConfig; +use Spiral\Console\Confirmation\ApplicationInProduction; use Spiral\Console\Console; use Spiral\Console\ConsoleDispatcher; use Spiral\Console\Sequence\CallableSequence; use Spiral\Console\Sequence\CommandSequence; use Spiral\Core\Attribute\Singleton; +use Spiral\Core\BinderInterface; use Spiral\Core\CoreInterceptorInterface; use Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader; use Spiral\Tokenizer\TokenizerListenerRegistryInterface; @@ -40,13 +44,21 @@ public function __construct( ) { } - public function init(AbstractKernel $kernel): void + public function init(AbstractKernel $kernel, BinderInterface $binder): void { // Lowest priority $kernel->bootstrapped(static function (AbstractKernel $kernel): void { $kernel->addDispatcher(ConsoleDispatcher::class); }); + // Registering necessary scope bindings + $commandBinder = $binder->getBinder('console.command'); + $commandBinder->bindSingleton(ApplicationInProduction::class, ApplicationInProduction::class); + $commandBinder->bindSingleton(CommandCoreFactory::class, CommandCoreFactory::class); + $commandBinder->bindSingleton(CommandCore::class, CommandCore::class); + + $binder->getBinder('console')->bindSingleton(Console::class, Console::class); + $this->config->setDefaults( ConsoleConfig::CONFIG, [ From aefe52675123aba680bc89d3c5241c9e2980b7b3 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 18:25:44 +0200 Subject: [PATCH 37/84] Add http.request scope to InputManager and InputScope --- src/Filters/tests/BaseTestCase.php | 5 ++++- src/Framework/Filter/InputScope.php | 3 +++ src/Http/src/Request/InputManager.php | 2 ++ tests/Framework/Filter/Model/CastingErrorMessagesTest.php | 3 +++ tests/Framework/Filter/Model/FilterWithSettersTest.php | 3 +++ tests/Framework/Filter/Model/MethodAttributeTest.php | 3 +++ tests/Framework/Filter/Model/NestedArrayFiltersTest.php | 3 +++ tests/Framework/Filter/Model/NestedFilterTest.php | 3 +++ 8 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Filters/tests/BaseTestCase.php b/src/Filters/tests/BaseTestCase.php index 4b6ab9695..adbf765b9 100644 --- a/src/Filters/tests/BaseTestCase.php +++ b/src/Filters/tests/BaseTestCase.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Spiral\Core\Container; +use Spiral\Core\Options; use Spiral\Validation\ValidationInterface; use Spiral\Validation\ValidationProvider; @@ -16,7 +17,9 @@ abstract class BaseTestCase extends TestCase public function setUp(): void { - $this->container = new Container(); + $options = new Options(); + $options->checkScope = false; + $this->container = new Container(options: $options); $this->container->bindSingleton(ValidationInterface::class, ValidationProvider::class); } } diff --git a/src/Framework/Filter/InputScope.php b/src/Framework/Filter/InputScope.php index 8e04df2d9..b6fd80ece 100644 --- a/src/Framework/Filter/InputScope.php +++ b/src/Framework/Filter/InputScope.php @@ -4,13 +4,16 @@ namespace Spiral\Filter; +use Spiral\Core\Attribute\Scope; use Spiral\Filters\Exception\InputException; use Spiral\Filters\InputInterface; +use Spiral\Framework\Spiral; use Spiral\Http\Request\InputManager; /** * Provides ability to use http request scope as filters input. */ +#[Scope(Spiral::HttpRequest)] final class InputScope implements InputInterface { public function __construct( diff --git a/src/Http/src/Request/InputManager.php b/src/Http/src/Request/InputManager.php index 1d0fcbc2f..66f2dd3fc 100644 --- a/src/Http/src/Request/InputManager.php +++ b/src/Http/src/Request/InputManager.php @@ -10,6 +10,7 @@ use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; use Spiral\Core\Attribute\Proxy; +use Spiral\Core\Attribute\Scope; use Spiral\Core\Attribute\Singleton; use Spiral\Core\Exception\ScopeException; use Spiral\Http\Config\HttpConfig; @@ -49,6 +50,7 @@ * @method mixed attribute(string $name, mixed $default = null) */ #[Singleton] +#[Scope('http.request')] final class InputManager { /** diff --git a/tests/Framework/Filter/Model/CastingErrorMessagesTest.php b/tests/Framework/Filter/Model/CastingErrorMessagesTest.php index c90633d98..6c8d00a7c 100644 --- a/tests/Framework/Filter/Model/CastingErrorMessagesTest.php +++ b/tests/Framework/Filter/Model/CastingErrorMessagesTest.php @@ -6,10 +6,13 @@ use Spiral\App\Request\CastingErrorMessages; use Spiral\Filters\Exception\ValidationException; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\Filter\FilterTestCase; final class CastingErrorMessagesTest extends FilterTestCase { + #[TestScope(Spiral::HttpRequest)] public function testValidationMessages(): void { try { diff --git a/tests/Framework/Filter/Model/FilterWithSettersTest.php b/tests/Framework/Filter/Model/FilterWithSettersTest.php index 79ba75fae..e80088b4b 100644 --- a/tests/Framework/Filter/Model/FilterWithSettersTest.php +++ b/tests/Framework/Filter/Model/FilterWithSettersTest.php @@ -7,8 +7,11 @@ use Spiral\App\Request\FilterWithSetters; use Spiral\App\Request\PostFilter; use Spiral\Filters\Exception\ValidationException; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\Filter\FilterTestCase; +#[TestScope(Spiral::HttpRequest)] final class FilterWithSettersTest extends FilterTestCase { public function testSetters(): void diff --git a/tests/Framework/Filter/Model/MethodAttributeTest.php b/tests/Framework/Filter/Model/MethodAttributeTest.php index 17c5bf70f..425b41ad4 100644 --- a/tests/Framework/Filter/Model/MethodAttributeTest.php +++ b/tests/Framework/Filter/Model/MethodAttributeTest.php @@ -5,10 +5,13 @@ namespace Framework\Filter\Model; use Spiral\App\Request\TestRequest; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\Filter\FilterTestCase; final class MethodAttributeTest extends FilterTestCase { + #[TestScope(Spiral::HttpRequest)] public function testGetMethodValue(): void { $filter = $this->getFilter(TestRequest::class, method: 'GET'); diff --git a/tests/Framework/Filter/Model/NestedArrayFiltersTest.php b/tests/Framework/Filter/Model/NestedArrayFiltersTest.php index a899ca797..98569ddfe 100644 --- a/tests/Framework/Filter/Model/NestedArrayFiltersTest.php +++ b/tests/Framework/Filter/Model/NestedArrayFiltersTest.php @@ -8,8 +8,11 @@ use Spiral\App\Request\AddressFilter; use Spiral\App\Request\MultipleAddressesFilter; use Spiral\Filters\Exception\ValidationException; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\Filter\FilterTestCase; +#[TestScope(Spiral::HttpRequest)] final class NestedArrayFiltersTest extends FilterTestCase { public function testGetsNestedFilter(): void diff --git a/tests/Framework/Filter/Model/NestedFilterTest.php b/tests/Framework/Filter/Model/NestedFilterTest.php index 6aaa03086..62160c94e 100644 --- a/tests/Framework/Filter/Model/NestedFilterTest.php +++ b/tests/Framework/Filter/Model/NestedFilterTest.php @@ -14,8 +14,11 @@ use Spiral\App\Request\WithNullableNestedFilter; use Spiral\App\Request\WithNullableRequiredNestedFilter; use Spiral\Filters\Exception\ValidationException; +use Spiral\Framework\Spiral; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\Filter\FilterTestCase; +#[TestScope(Spiral::HttpRequest)] final class NestedFilterTest extends FilterTestCase { public function testGetsNestedFilter(): void From cb6df8692d03b76e7ad9562913ce7fc2282d0d85 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 20 Feb 2024 18:44:23 +0200 Subject: [PATCH 38/84] Add http.request scope to CookieManager --- src/Framework/Cookies/CookieManager.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Framework/Cookies/CookieManager.php b/src/Framework/Cookies/CookieManager.php index 43815017b..4b750e6ff 100644 --- a/src/Framework/Cookies/CookieManager.php +++ b/src/Framework/Cookies/CookieManager.php @@ -8,13 +8,16 @@ use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ServerRequestInterface; use Spiral\Core\Attribute\Proxy; +use Spiral\Core\Attribute\Scope; use Spiral\Core\Attribute\Singleton; use Spiral\Core\Exception\ScopeException; +use Spiral\Framework\Spiral; /** * Cookies manages provides the ability to write and read cookies from the active request/response scope. */ #[Singleton] +#[Scope(Spiral::HttpRequest)] final class CookieManager { public function __construct( From 9a2b173d7c3a68f8768c6320d79a0372e9f98a08 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 21 Feb 2024 12:12:43 +0200 Subject: [PATCH 39/84] Add Spiral enum instead of string --- src/Console/src/Console.php | 3 +++ src/Framework/Console/Confirmation/ApplicationInProduction.php | 3 ++- .../Confirmation/ApplicationInProductionCommandTest.php | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Console/src/Console.php b/src/Console/src/Console.php index e076e60a1..18c1718ca 100644 --- a/src/Console/src/Console.php +++ b/src/Console/src/Console.php @@ -74,6 +74,9 @@ public function run( $input = new InputProxy($input, ['firstArgument' => $command]); } + /** + * @psalm-suppress InvalidArgument + */ $code = $this->scope->runScope( new Scope( bindings: [ diff --git a/src/Framework/Console/Confirmation/ApplicationInProduction.php b/src/Framework/Console/Confirmation/ApplicationInProduction.php index 5b702373e..0e7de6231 100644 --- a/src/Framework/Console/Confirmation/ApplicationInProduction.php +++ b/src/Framework/Console/Confirmation/ApplicationInProduction.php @@ -7,10 +7,11 @@ use Spiral\Boot\Environment\AppEnvironment; use Spiral\Console\Traits\HelpersTrait; use Spiral\Core\Attribute\Scope; +use Spiral\Framework\Spiral; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[Scope('console.command')] +#[Scope(Spiral::ConsoleCommand)] final class ApplicationInProduction { use HelpersTrait; diff --git a/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php b/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php index 0a06a7b1e..a9250bb54 100644 --- a/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php +++ b/tests/Framework/Console/Confirmation/ApplicationInProductionCommandTest.php @@ -4,11 +4,12 @@ namespace Framework\Console\Confirmation; +use Spiral\Framework\Spiral; use Spiral\Testing\Attribute\Env; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; -#[TestScope('console')] +#[TestScope(Spiral::Console)] final class ApplicationInProductionCommandTest extends BaseTestCase { #[Env('APP_ENV', 'production')] From 69fe624be0f2ac463546134e7f834137c18fe8d1 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 26 Feb 2024 19:21:21 +0200 Subject: [PATCH 40/84] Add proxy AuthContextInterface binding --- .../Bootloader/Auth/HttpAuthBootloader.php | 2 ++ .../Http/HttpAuthBootloaderTest.php | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Framework/Bootloader/Auth/HttpAuthBootloader.php b/src/Framework/Bootloader/Auth/HttpAuthBootloader.php index 044f73fbb..edf5a1270 100644 --- a/src/Framework/Bootloader/Auth/HttpAuthBootloader.php +++ b/src/Framework/Bootloader/Auth/HttpAuthBootloader.php @@ -24,6 +24,7 @@ use Spiral\Config\Patch\Append; use Spiral\Core\Attribute\Singleton; use Spiral\Core\BinderInterface; +use Spiral\Core\Config\Proxy; use Spiral\Core\Container\Autowire; use Spiral\Core\FactoryInterface; use Spiral\Framework\Spiral; @@ -59,6 +60,7 @@ public function defineBindings(): array static fn (CurrentRequest $request): AuthContextInterface => $request->get()->getAttribute(AuthMiddleware::ATTRIBUTE) ?? throw new InvalidAuthContext() ); + $this->binder->bind(AuthContextInterface::class, new Proxy(AuthContextInterface::class, false)); return []; } diff --git a/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php b/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php index ed3f81bec..f5dc61f9e 100644 --- a/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/HttpAuthBootloaderTest.php @@ -4,6 +4,10 @@ namespace Framework\Bootloader\Http; +use Psr\Http\Message\ServerRequestInterface; +use Spiral\Auth\ActorProviderInterface; +use Spiral\Auth\AuthContext; +use Spiral\Auth\AuthContextInterface; use Spiral\Auth\Config\AuthConfig; use Spiral\Auth\Session\TokenStorage; use Spiral\Auth\Session\TokenStorage as SessionTokenStorage; @@ -13,6 +17,9 @@ use Spiral\Bootloader\Auth\HttpAuthBootloader; use Spiral\Config\LoaderInterface; use Spiral\Config\ConfigManager; +use Spiral\Framework\Spiral; +use Spiral\Http\CurrentRequest; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\BaseTestCase; final class HttpAuthBootloaderTest extends BaseTestCase @@ -27,6 +34,26 @@ public function testTokenStorageInterfaceBinding(): void $this->assertContainerBoundAsSingleton(TokenStorageInterface::class, TokenStorage::class); } + public function testProxyAuthContextInterfaceBinding(): void + { + $this->assertContainerBound(AuthContextInterface::class, AuthContextInterface::class); + } + + #[TestScope(Spiral::Http)] + public function testAuthContextInterfaceBinding(): void + { + $request = $this->createMock(ServerRequestInterface::class); + $request + ->method('getAttribute') + ->willReturn(new AuthContext($this->createMock(ActorProviderInterface::class))); + + $currentRequest = new CurrentRequest(); + $currentRequest->set($request); + $this->getContainer()->bindSingleton(CurrentRequest::class, $currentRequest); + + $this->assertContainerBound(AuthContextInterface::class, AuthContext::class); + } + public function testConfig(): void { $this->assertConfigHasFragments(AuthConfig::CONFIG, [ From 66b3b0ef902beddff669c2b17111ce0ca6d360ee Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 26 Feb 2024 19:32:21 +0200 Subject: [PATCH 41/84] Fix InputInterface binding --- src/Framework/Bootloader/Security/FiltersBootloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/Bootloader/Security/FiltersBootloader.php b/src/Framework/Bootloader/Security/FiltersBootloader.php index cb470be3e..7e3a7f771 100644 --- a/src/Framework/Bootloader/Security/FiltersBootloader.php +++ b/src/Framework/Bootloader/Security/FiltersBootloader.php @@ -51,7 +51,7 @@ public function defineSingletons(): array ->getBinder(Spiral::HttpRequest) ->bindSingleton( InputInterface::class, - static fn (ContainerInterface $container): InputScope => new InputScope(new InputManager($container)) + static fn (InputManager $inputManager): InputScope => new InputScope($inputManager) ); $this->binder->bind(InputInterface::class, new Proxy(InputInterface::class, true)); From 9a77110cc0e15b9dd46429b551bf59676b7ab887 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 26 Feb 2024 20:17:17 +0200 Subject: [PATCH 42/84] Fix InputInterface binding and add test --- .../Bootloader/Security/FiltersBootloader.php | 5 +- tests/Framework/Filter/InputScopeTest.php | 62 +++++++++++-------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/Framework/Bootloader/Security/FiltersBootloader.php b/src/Framework/Bootloader/Security/FiltersBootloader.php index 7e3a7f771..010b2d10e 100644 --- a/src/Framework/Bootloader/Security/FiltersBootloader.php +++ b/src/Framework/Bootloader/Security/FiltersBootloader.php @@ -30,6 +30,7 @@ use Spiral\Filters\Model\Mapper\CasterRegistryInterface; use Spiral\Filters\Model\Mapper\UuidCaster; use Spiral\Framework\Spiral; +use Spiral\Http\Config\HttpConfig; use Spiral\Http\Request\InputManager; /** @@ -51,7 +52,9 @@ public function defineSingletons(): array ->getBinder(Spiral::HttpRequest) ->bindSingleton( InputInterface::class, - static fn (InputManager $inputManager): InputScope => new InputScope($inputManager) + static function (ContainerInterface $container, HttpConfig $config): InputScope { + return new InputScope(new InputManager($container, $config)); + } ); $this->binder->bind(InputInterface::class, new Proxy(InputInterface::class, true)); diff --git a/tests/Framework/Filter/InputScopeTest.php b/tests/Framework/Filter/InputScopeTest.php index 0eea268c8..be19ada69 100644 --- a/tests/Framework/Filter/InputScopeTest.php +++ b/tests/Framework/Filter/InputScopeTest.php @@ -5,23 +5,25 @@ namespace Framework\Filter; use Nyholm\Psr7\ServerRequest; -use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; -use Spiral\Core\Container; -use Spiral\Filter\InputScope; -use Spiral\Http\Request\InputManager; - -final class InputScopeTest extends TestCase +use Spiral\Filters\InputInterface; +use Spiral\Framework\Spiral; +use Spiral\Http\Config\HttpConfig; +use Spiral\Http\Request\InputBag; +use Spiral\Testing\Attribute\TestScope; +use Spiral\Tests\Framework\BaseTestCase; + +#[TestScope(Spiral::HttpRequest)] +final class InputScopeTest extends BaseTestCase { - private InputScope $input; private ServerRequestInterface $request; protected function setUp(): void { parent::setUp(); - $container = new Container(); $request = new ServerRequest( method: 'POST', uri: 'https://site.com/users', @@ -37,7 +39,7 @@ protected function setUp(): void ] ); - $container->bind( + $this->getContainer()->bind( ServerRequestInterface::class, $this->request = $request ->withQueryParams(['foo' => 'bar']) @@ -45,23 +47,21 @@ protected function setUp(): void ->withParsedBody(['quux' => 'corge']) ->withAttribute('foz', 'baf'), ); - - $this->input = new InputScope(new InputManager($container)); } public function testGetsMethod(): void { - $this->assertSame('POST', $this->input->getValue('method')); + $this->assertSame('POST', $this->getContainer()->get(InputInterface::class)->getValue('method')); } public function testGetsPath(): void { - $this->assertSame('/users', $this->input->getValue('path')); + $this->assertSame('/users', $this->getContainer()->get(InputInterface::class)->getValue('path')); } public function testGetsUri(): void { - $uri = $this->input->getValue('uri'); + $uri = $this->getContainer()->get(InputInterface::class)->getValue('uri'); $this->assertInstanceOf(UriInterface::class, $uri); $this->assertSame('https://site.com/users', (string)$uri); @@ -69,45 +69,57 @@ public function testGetsUri(): void public function testGetsRequest(): void { - $this->assertSame($this->request, $this->input->getValue('request')); + $this->assertSame($this->request, $this->getContainer()->get(InputInterface::class)->getValue('request')); } public function testGetsBearerToken(): void { - $this->assertSame('123', $this->input->getValue('bearerToken')); + $this->assertSame('123', $this->getContainer()->get(InputInterface::class)->getValue('bearerToken')); } public function testIsSecure(): void { - $this->assertTrue($this->input->getValue('isSecure')); + $this->assertTrue($this->getContainer()->get(InputInterface::class)->getValue('isSecure')); } public function testIsAjax(): void { - $this->assertTrue($this->input->getValue('isAjax')); + $this->assertTrue($this->getContainer()->get(InputInterface::class)->getValue('isAjax')); } public function testIsXmlHttpRequest(): void { - $this->assertTrue($this->input->getValue('isXmlHttpRequest')); + $this->assertTrue($this->getContainer()->get(InputInterface::class)->getValue('isXmlHttpRequest')); } public function testIsJsonExpected(): void { - $this->assertTrue($this->input->getValue('isJsonExpected', true)); + $this->assertTrue($this->getContainer()->get(InputInterface::class)->getValue('isJsonExpected', true)); } public function testGetsRemoteAddress(): void { - $this->assertSame('123.123.123', $this->input->getValue('remoteAddress')); + $this->assertSame('123.123.123', $this->getContainer()->get(InputInterface::class)->getValue('remoteAddress')); } - /** - * @dataProvider InputBagsDataProvider - */ + #[DataProvider('InputBagsDataProvider')] public function testGetsInputBag(string $source, string $name, mixed $expected): void { - $this->assertSame($expected, $this->input->getValue($source, $name)); + $this->assertSame($expected, $this->getContainer()->get(InputInterface::class)->getValue($source, $name)); + } + + public function testGetValueFromCustomInputBag(): void + { + $this->getContainer() + ->bind( + HttpConfig::class, + new HttpConfig(['inputBags' => ['test' => ['class' => InputBag::class, 'source' => 'getParsedBody']]]) + ); + + $this->assertSame( + 'corge', + $this->getContainer()->get(InputInterface::class)->getValue('test', 'quux') + ); } public static function InputBagsDataProvider(): \Traversable From 8b2f4dd0b95c98aa19a431e5ed5c2fed550bbd41 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 11 Mar 2024 16:29:49 +0200 Subject: [PATCH 43/84] Using phpdoc final instead of final keyword --- src/Console/src/Command.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 92341be8c..93dd2f8d3 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -72,8 +72,9 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v /** * Pass execution to "perform" method using container to resolve method dependencies. + * @final */ - final protected function execute(InputInterface $input, OutputInterface $output): int + protected function execute(InputInterface $input, OutputInterface $output): int { if ($this->container === null) { throw new ScopeException('Container is not set'); From a7c5129e849aaffc3da4c6f44e5a54e3a48344d1 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 11 Mar 2024 16:39:21 +0200 Subject: [PATCH 44/84] Add todo --- src/Console/src/Command.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 93dd2f8d3..d2891420f 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -73,6 +73,7 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v /** * Pass execution to "perform" method using container to resolve method dependencies. * @final + * @TODO Change to final in v4.0 */ protected function execute(InputInterface $input, OutputInterface $output): int { From 48ecd2894bb25a90c52eaac95468073084cfb174 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 11 Mar 2024 19:04:02 +0400 Subject: [PATCH 45/84] Remove the second Container parameter from the HTTP Pipeline --- src/AuthHttp/tests/CookieTransportTest.php | 2 +- src/AuthHttp/tests/HeaderTransportTest.php | 2 +- src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php | 2 +- .../tests/Middleware/Firewall/BaseFirewallTestCase.php | 2 +- src/Cookies/tests/CookiesTest.php | 2 +- src/Csrf/tests/CsrfTest.php | 2 +- src/Http/src/Pipeline.php | 10 +++++----- src/Http/tests/HttpTest.php | 4 ++-- src/Http/tests/PipelineTest.php | 8 ++++---- src/Router/tests/PipelineFactoryTest.php | 4 +--- 10 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/AuthHttp/tests/CookieTransportTest.php b/src/AuthHttp/tests/CookieTransportTest.php index 32dce0d50..0409cd4c9 100644 --- a/src/AuthHttp/tests/CookieTransportTest.php +++ b/src/AuthHttp/tests/CookieTransportTest.php @@ -175,7 +175,7 @@ protected function getCore(HttpTransportInterface $transport): Http $http = new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/HeaderTransportTest.php b/src/AuthHttp/tests/HeaderTransportTest.php index da9438e3c..3a05ccbee 100644 --- a/src/AuthHttp/tests/HeaderTransportTest.php +++ b/src/AuthHttp/tests/HeaderTransportTest.php @@ -155,7 +155,7 @@ protected function getCore(HttpTransportInterface $transport): Http $http = new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php b/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php index e4fecdb65..efc22c800 100644 --- a/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php +++ b/src/AuthHttp/tests/Middleware/AuthMiddlewareTest.php @@ -84,7 +84,7 @@ protected function getCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new ResponseFactory($config), $this->container ); diff --git a/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php b/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php index dfaf35afe..c6feb52e6 100644 --- a/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php +++ b/src/AuthHttp/tests/Middleware/Firewall/BaseFirewallTestCase.php @@ -30,7 +30,7 @@ protected function getCore(AbstractFirewall $firewall, HttpTransportInterface $t $http = new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new ResponseFactory($config), $this->container ); diff --git a/src/Cookies/tests/CookiesTest.php b/src/Cookies/tests/CookiesTest.php index aafc474c0..c87fc0d91 100644 --- a/src/Cookies/tests/CookiesTest.php +++ b/src/Cookies/tests/CookiesTest.php @@ -249,7 +249,7 @@ private function httpCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new TestResponseFactory($config), $this->container ); diff --git a/src/Csrf/tests/CsrfTest.php b/src/Csrf/tests/CsrfTest.php index 24cf7530d..9888cab99 100644 --- a/src/Csrf/tests/CsrfTest.php +++ b/src/Csrf/tests/CsrfTest.php @@ -227,7 +227,7 @@ private function httpCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new TestResponseFactory($config), $this->container ); diff --git a/src/Http/src/Pipeline.php b/src/Http/src/Pipeline.php index c5987c386..7ce01f8b4 100644 --- a/src/Http/src/Pipeline.php +++ b/src/Http/src/Pipeline.php @@ -4,13 +4,13 @@ namespace Spiral\Http; -use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Spiral\Core\Attribute\Proxy; +use Spiral\Core\ContainerScope; use Spiral\Core\ScopeInterface; use Spiral\Http\Event\MiddlewareProcessing; use Spiral\Http\Exception\PipelineException; @@ -32,7 +32,6 @@ final class Pipeline implements RequestHandlerInterface, MiddlewareInterface public function __construct( #[Proxy] private readonly ScopeInterface $scope, - #[Proxy] private readonly ContainerInterface $container, private readonly ?EventDispatcherInterface $dispatcher = null, ?TracerInterface $tracer = null ) { @@ -91,14 +90,15 @@ public function handle(Request $request): Response return $response; }, - scoped: true, attributes: [ 'http.middleware' => $middleware::class, - ] + ], + scoped: true ); } - $this->container->get(CurrentRequest::class)->set($request); + // todo: find a better solution in the Spiral v4.0 + ContainerScope::getContainer()?->get(CurrentRequest::class)->set($request); return $this->handler->handle($request); } diff --git a/src/Http/tests/HttpTest.php b/src/Http/tests/HttpTest.php index 6b4d22319..3e0de41cb 100644 --- a/src/Http/tests/HttpTest.php +++ b/src/Http/tests/HttpTest.php @@ -267,7 +267,7 @@ public function testPassingTracerIntoScope(): void $http = new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new ResponseFactory($config), $this->container, $tracerFactory = m::mock(TracerFactoryInterface::class), @@ -292,7 +292,7 @@ protected function getCore(array $middleware = []): Http return new Http( $config, - new Pipeline($this->container, $this->container), + new Pipeline($this->container), new ResponseFactory($config), $this->container ); diff --git a/src/Http/tests/PipelineTest.php b/src/Http/tests/PipelineTest.php index b47c61d61..7ea6e1597 100644 --- a/src/Http/tests/PipelineTest.php +++ b/src/Http/tests/PipelineTest.php @@ -22,7 +22,7 @@ final class PipelineTest extends TestCase { public function testTarget(): void { - $pipeline = new Pipeline($this->container, $this->container); + $pipeline = new Pipeline($this->container); $handler = new CallableHandler(function () { return 'response'; @@ -37,7 +37,7 @@ public function testTarget(): void public function testHandle(): void { - $pipeline = new Pipeline($this->container, $this->container); + $pipeline = new Pipeline($this->container); $handler = new CallableHandler(function () { return 'response'; @@ -54,7 +54,7 @@ public function testHandleException(): void { $this->expectException(PipelineException::class); - $pipeline = new Pipeline($this->container, $this->container); + $pipeline = new Pipeline($this->container); $pipeline->handle(new ServerRequest('GET', '')); } @@ -77,7 +77,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->method('dispatch') ->with(new MiddlewareProcessing($request, $middleware)); - $pipeline = new Pipeline($this->container, $this->container, $dispatcher, new NullTracer($this->container)); + $pipeline = new Pipeline($this->container, $dispatcher, new NullTracer($this->container)); $pipeline->pushMiddleware($middleware); diff --git a/src/Router/tests/PipelineFactoryTest.php b/src/Router/tests/PipelineFactoryTest.php index 2a0c4d9a9..f090717e3 100644 --- a/src/Router/tests/PipelineFactoryTest.php +++ b/src/Router/tests/PipelineFactoryTest.php @@ -41,7 +41,6 @@ public function testCreatesFromArrayWithPipeline(): void { $newPipeline = new Pipeline( scope: $this->createMock(ScopeInterface::class), - container: $this->createMock(ContainerInterface::class), ); $this->assertSame( @@ -61,7 +60,6 @@ public function testCreates(): void ->with(Pipeline::class) ->andReturn($p = new Pipeline( $this->createMock(ScopeInterface::class), - $container, tracer: new NullTracer($container) )); @@ -116,7 +114,7 @@ public function testInvalidTypeShouldThrowAnException(mixed $value, string $type ->shouldReceive('make') ->once() ->with(Pipeline::class) - ->andReturn(new Pipeline(m::mock(ScopeInterface::class), $this->container)); + ->andReturn(new Pipeline(m::mock(ScopeInterface::class))); $this->expectException(RouteException::class); $this->expectExceptionMessage(\sprintf('Invalid middleware `%s`', $type)); From d082e02015bf2330de99207f1a5ecc483842f6af Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 11 Mar 2024 17:37:01 +0200 Subject: [PATCH 46/84] Revert buildCore method --- src/Console/src/Command.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index d2891420f..31af539b1 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -15,7 +15,9 @@ use Spiral\Console\Event\CommandFinished; use Spiral\Console\Event\CommandStarting; use Spiral\Console\Traits\HelpersTrait; +use Spiral\Core\ContainerScope; use Spiral\Core\CoreInterceptorInterface; +use Spiral\Core\CoreInterface; use Spiral\Core\Exception\ScopeException; use Spiral\Core\Scope; use Spiral\Core\ScopeInterface; @@ -99,10 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ], autowire: true, ), - fn (CommandCoreFactory $factory) => $factory->make( - $this->interceptors, - $this->eventDispatcher, - )->callAction( + fn () => $this->buildCore()->callAction( static::class, $method, [ @@ -121,6 +120,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + /** + * @deprecated This method will be removed in v4.0. + */ + protected function buildCore(): CoreInterface + { + $factory = ContainerScope::getContainer()->get(CommandCoreFactory::class); + + return $factory->make($this->interceptors, $this->eventDispatcher); + } + protected function prepareInput(InputInterface $input): InputInterface { return $input; From 99ae1ec905c62bb784f1e846e0f36fb6084310f8 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 11 Mar 2024 17:40:05 +0200 Subject: [PATCH 47/84] Add phpdoc --- src/Console/src/CommandCoreFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Console/src/CommandCoreFactory.php b/src/Console/src/CommandCoreFactory.php index b07686307..0bfd70415 100644 --- a/src/Console/src/CommandCoreFactory.php +++ b/src/Console/src/CommandCoreFactory.php @@ -8,6 +8,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Console\Interceptor\AttributeInterceptor; use Spiral\Core\Attribute\Scope; +use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\InterceptableCore; @@ -19,6 +20,9 @@ public function __construct( ) { } + /** + * @param array> $interceptors + */ public function make(array $interceptors, ?EventDispatcherInterface $eventDispatcher = null): CoreInterface { /** @var CommandCore $core */ From 90b366c51019ee3a62fb47c20e62aabbb87ba26f Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 12 Mar 2024 14:50:34 +0400 Subject: [PATCH 48/84] Improve exceptions when CookieQueue is resolved --- .../Shared/InvalidContainerScopeException.php | 30 +++++++++++++++++ .../Bootloader/Http/CookiesBootloader.php | 17 ++++++---- .../ContextualObjectNotFoundException.php | 25 ++++++++++++++ .../InvalidRequestScopeException.php | 26 +++++++++++++++ .../Bootloader/Http/CookiesBootloaderTest.php | 33 ++++++++++++------- 5 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 src/Core/src/Exception/Shared/InvalidContainerScopeException.php create mode 100644 src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php create mode 100644 src/Framework/Bootloader/Http/Exception/InvalidRequestScopeException.php diff --git a/src/Core/src/Exception/Shared/InvalidContainerScopeException.php b/src/Core/src/Exception/Shared/InvalidContainerScopeException.php new file mode 100644 index 000000000..48952d3e7 --- /dev/null +++ b/src/Core/src/Exception/Shared/InvalidContainerScopeException.php @@ -0,0 +1,30 @@ +scope = \is_string($scopeOrContainer) + ? $scopeOrContainer + : Introspector::scopeName($scopeOrContainer); + + $req = $this->requiredScope !== null ? ", `$this->requiredScope` is required" : ''; + + parent::__construct("Unable to resolve `$id` in the `$this->scope` scope{$req}."); + } +} diff --git a/src/Framework/Bootloader/Http/CookiesBootloader.php b/src/Framework/Bootloader/Http/CookiesBootloader.php index 2bd1af5a1..0d3e1998d 100644 --- a/src/Framework/Bootloader/Http/CookiesBootloader.php +++ b/src/Framework/Bootloader/Http/CookiesBootloader.php @@ -6,13 +6,14 @@ use Psr\Http\Message\ServerRequestInterface; use Spiral\Boot\Bootloader\Bootloader; +use Spiral\Bootloader\Http\Exception\ContextualObjectNotFoundException; +use Spiral\Bootloader\Http\Exception\InvalidRequestScopeException; use Spiral\Config\ConfiguratorInterface; use Spiral\Config\Patch\Append; use Spiral\Cookies\Config\CookiesConfig; use Spiral\Cookies\CookieQueue; use Spiral\Core\Attribute\Singleton; use Spiral\Core\BinderInterface; -use Spiral\Core\Exception\ScopeException; use Spiral\Framework\Spiral; #[Singleton] @@ -26,7 +27,7 @@ public function __construct( public function defineBindings(): array { - $this->binder->getBinder(Spiral::HttpRequest)->bind(CookieQueue::class, [self::class, 'cookieQueue']); + $this->binder->getBinder(Spiral::Http)->bind(CookieQueue::class, [self::class, 'cookieQueue']); return []; } @@ -51,13 +52,15 @@ public function whitelistCookie(string $cookie): void $this->config->modify(CookiesConfig::CONFIG, new Append('excluded', null, $cookie)); } - private function cookieQueue(ServerRequestInterface $request): CookieQueue + private function cookieQueue(?ServerRequestInterface $request): CookieQueue { - $cookieQueue = $request->getAttribute(CookieQueue::ATTRIBUTE, null); - if ($cookieQueue === null) { - throw new ScopeException('Unable to resolve CookieQueue, invalid request scope'); + if ($request === null) { + throw new InvalidRequestScopeException(CookieQueue::class); } - return $cookieQueue; + return $request->getAttribute(CookieQueue::ATTRIBUTE) ?? throw new ContextualObjectNotFoundException( + CookieQueue::class, + CookieQueue::ATTRIBUTE, + ); } } diff --git a/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php b/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php new file mode 100644 index 000000000..021843a91 --- /dev/null +++ b/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php @@ -0,0 +1,25 @@ +value); + } +} diff --git a/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php b/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php index beab7be2d..4e6544cc4 100644 --- a/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php +++ b/tests/Framework/Bootloader/Http/CookiesBootloaderTest.php @@ -6,6 +6,8 @@ use Psr\Http\Message\ServerRequestInterface; use Spiral\Bootloader\Http\CookiesBootloader; +use Spiral\Bootloader\Http\Exception\ContextualObjectNotFoundException; +use Spiral\Bootloader\Http\Exception\InvalidRequestScopeException; use Spiral\Config\ConfigManager; use Spiral\Config\LoaderInterface; use Spiral\Cookies\Config\CookiesConfig; @@ -16,29 +18,38 @@ final class CookiesBootloaderTest extends BaseTestCase { - #[TestScope(Spiral::HttpRequest)] + #[TestScope(Spiral::Http)] public function testCookieQueueBinding(): void { $request = $this->mockContainer(ServerRequestInterface::class); $request->shouldReceive('getAttribute') - ->once() - ->with(CookieQueue::ATTRIBUTE, null) - ->andReturn(new CookieQueue()); + ->with(CookieQueue::ATTRIBUTE) + ->andReturn(new CookieQueue(), new CookieQueue(), new CookieQueue(), new CookieQueue()); $this->assertContainerBound(CookieQueue::class); + // Makes 3 calls to the container + $this->assertContainerBoundNotAsSingleton(CookieQueue::class, CookieQueue::class); } - #[TestScope(Spiral::HttpRequest)] - public function testCookieQueueBindingShouldThrowAndExceptionWhenAttributeIsEmpty(): void + #[TestScope(Spiral::Http)] + public function testCookieQueueBindingWithoutCookieQueueInRequest(): void { - $this->expectExceptionMessage('Unable to resolve CookieQueue, invalid request scope'); $request = $this->mockContainer(ServerRequestInterface::class); $request->shouldReceive('getAttribute') - ->once() - ->with(CookieQueue::ATTRIBUTE, null) - ->andReturnNull(); + ->with(CookieQueue::ATTRIBUTE) + ->andReturn(null); - $this->assertContainerBound(CookieQueue::class); + $this->expectException(ContextualObjectNotFoundException::class); + + $this->getContainer()->get(CookieQueue::class); + } + + #[TestScope(Spiral::Http)] + public function testCookieQueueBindingWithoutRequest(): void + { + $this->expectException(InvalidRequestScopeException::class); + + $this->getContainer()->get(CookieQueue::class); } public function testConfig(): void From 0dbf4363ad0e1be353eca80d230a282f213d0963 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 12 Mar 2024 21:45:09 +0400 Subject: [PATCH 49/84] Improve exceptions when SessionInterface is resolved --- .../Auth/Exception/InvalidAuthContext.php | 19 ------------------- .../Bootloader/Auth/HttpAuthBootloader.php | 12 +++++++++--- .../ContextualObjectNotFoundException.php | 8 +++++++- .../Bootloader/Http/SessionBootloader.php | 15 +++++++++------ .../Exception/InvalidSessionContext.php | 19 ------------------- src/Http/src/CurrentRequest.php | 5 +++-- tests/Framework/Http/AuthSessionTest.php | 19 ++++++++++--------- tests/Framework/Http/SessionTest.php | 18 ++++++++++-------- 8 files changed, 48 insertions(+), 67 deletions(-) delete mode 100644 src/Framework/Auth/Exception/InvalidAuthContext.php delete mode 100644 src/Framework/Session/Exception/InvalidSessionContext.php diff --git a/src/Framework/Auth/Exception/InvalidAuthContext.php b/src/Framework/Auth/Exception/InvalidAuthContext.php deleted file mode 100644 index d65410e4a..000000000 --- a/src/Framework/Auth/Exception/InvalidAuthContext.php +++ /dev/null @@ -1,19 +0,0 @@ -getBinder(Spiral::Http) ->bind( AuthContextInterface::class, - static fn (CurrentRequest $request): AuthContextInterface => - $request->get()->getAttribute(AuthMiddleware::ATTRIBUTE) ?? throw new InvalidAuthContext() + static fn (?ServerRequestInterface $request): AuthContextInterface => + ($request ?? throw new InvalidRequestScopeException(AuthContextInterface::class)) + ->getAttribute(AuthMiddleware::ATTRIBUTE) ?? throw new ContextualObjectNotFoundException( + AuthContextInterface::class, + AuthMiddleware::ATTRIBUTE, + ) ); $this->binder->bind(AuthContextInterface::class, new Proxy(AuthContextInterface::class, false)); diff --git a/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php b/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php index 021843a91..c2ee5c219 100644 --- a/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php +++ b/src/Framework/Bootloader/Http/Exception/ContextualObjectNotFoundException.php @@ -6,7 +6,11 @@ use Psr\Container\NotFoundExceptionInterface; use Psr\Http\Message\ServerRequestInterface; +use Spiral\Auth\AuthContextInterface; +use Spiral\Auth\Middleware\AuthMiddleware; use Spiral\Cookies\Middleware\CookiesMiddleware; +use Spiral\Session\Middleware\SessionMiddleware; +use Spiral\Session\SessionInterface; /** * The requested object depends on the {@see ServerRequestInterface} context. @@ -14,12 +18,14 @@ * the object is requested from the container. * For example, * - {@see CookieQueue} requires {@see CookiesMiddleware} + * - {@see SessionInterface} requires {@see SessionMiddleware} + * - {@see AuthContextInterface} requires {@see AuthMiddleware} */ final class ContextualObjectNotFoundException extends \RuntimeException implements NotFoundExceptionInterface { public function __construct(string $id, ?string $key = null) { $keyStr = $key !== null ? " by the key `$key`" : ''; - parent::__construct("`$id` not found in the Request context{$keyStr}."); + parent::__construct("`$id` not found in Request attributes{$keyStr}."); } } diff --git a/src/Framework/Bootloader/Http/SessionBootloader.php b/src/Framework/Bootloader/Http/SessionBootloader.php index ee6f23b50..4939d9ffe 100644 --- a/src/Framework/Bootloader/Http/SessionBootloader.php +++ b/src/Framework/Bootloader/Http/SessionBootloader.php @@ -4,16 +4,17 @@ namespace Spiral\Bootloader\Http; +use Psr\Http\Message\ServerRequestInterface; use Spiral\Boot\Bootloader\Bootloader; use Spiral\Boot\DirectoriesInterface; +use Spiral\Bootloader\Http\Exception\ContextualObjectNotFoundException; +use Spiral\Bootloader\Http\Exception\InvalidRequestScopeException; use Spiral\Config\ConfiguratorInterface; use Spiral\Core\BinderInterface; use Spiral\Core\Config\Proxy; use Spiral\Core\Container\Autowire; use Spiral\Framework\Spiral; -use Spiral\Http\CurrentRequest; use Spiral\Session\Config\SessionConfig; -use Spiral\Session\Exception\InvalidSessionContext; use Spiral\Session\Handler\FileHandler; use Spiral\Session\Middleware\SessionMiddleware; use Spiral\Session\SessionFactory; @@ -33,10 +34,12 @@ public function defineBindings(): array ->getBinder(Spiral::Http) ->bind( SessionInterface::class, - static function (CurrentRequest $request): SessionInterface { - return $request->get() - ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new InvalidSessionContext(); - } + static fn (?ServerRequestInterface $request): SessionInterface => + ($request ?? throw new InvalidRequestScopeException(SessionInterface::class)) + ->getAttribute(SessionMiddleware::ATTRIBUTE) ?? throw new ContextualObjectNotFoundException( + SessionInterface::class, + SessionMiddleware::ATTRIBUTE, + ) ); $this->binder->bind(SessionInterface::class, new Proxy(SessionInterface::class, false)); diff --git a/src/Framework/Session/Exception/InvalidSessionContext.php b/src/Framework/Session/Exception/InvalidSessionContext.php deleted file mode 100644 index 41c647c48..000000000 --- a/src/Framework/Session/Exception/InvalidSessionContext.php +++ /dev/null @@ -1,19 +0,0 @@ -request ?? throw new HttpException('Unable to resolve current request.'); + return $this->request ?? throw new HttpException('Unable to resolve current server request.'); } } diff --git a/tests/Framework/Http/AuthSessionTest.php b/tests/Framework/Http/AuthSessionTest.php index 1461af7f5..ee330a607 100644 --- a/tests/Framework/Http/AuthSessionTest.php +++ b/tests/Framework/Http/AuthSessionTest.php @@ -5,8 +5,8 @@ namespace Spiral\Tests\Framework\Http; use Spiral\Auth\AuthContextInterface; -use Spiral\Auth\Exception\InvalidAuthContext; -use Spiral\Auth\Middleware\AuthMiddleware; +use Spiral\Bootloader\Http\Exception\ContextualObjectNotFoundException; +use Spiral\Bootloader\Http\Exception\InvalidRequestScopeException; use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; use Spiral\Testing\Attribute\TestScope; @@ -74,16 +74,17 @@ public function testInvalidSessionContextException(): void ])); $this->setHttpHandler(function (): void { - $this->expectException(InvalidAuthContext::class); - $this->expectExceptionMessage(\sprintf( - 'The `%s` attribute was not found. To use the auth, the `%s` must be configured.', - AuthMiddleware::ATTRIBUTE, - AuthMiddleware::class - )); - + $this->expectException(ContextualObjectNotFoundException::class); $this->getContainer()->get(AuthContextInterface::class); }); $this->fakeHttp()->get('/'); } + + public function testCookieQueueBindingWithoutRequest(): void + { + $this->expectException(InvalidRequestScopeException::class); + + $this->getContainer()->get(AuthContextInterface::class); + } } diff --git a/tests/Framework/Http/SessionTest.php b/tests/Framework/Http/SessionTest.php index 5cc9c4d1b..595ed40d7 100644 --- a/tests/Framework/Http/SessionTest.php +++ b/tests/Framework/Http/SessionTest.php @@ -4,10 +4,10 @@ namespace Spiral\Tests\Framework\Http; +use Spiral\Bootloader\Http\Exception\ContextualObjectNotFoundException; +use Spiral\Bootloader\Http\Exception\InvalidRequestScopeException; use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; -use Spiral\Session\Exception\InvalidSessionContext; -use Spiral\Session\Middleware\SessionMiddleware; use Spiral\Session\SessionInterface; use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; @@ -112,12 +112,7 @@ public function testInvalidSessionContextException(): void ])); $this->setHttpHandler(function (): void { - $this->expectException(InvalidSessionContext::class); - $this->expectExceptionMessage(\sprintf( - 'The `%s` attribute was not found. To use the session, the `%s` must be configured.', - SessionMiddleware::ATTRIBUTE, - SessionMiddleware::class - )); + $this->expectException(ContextualObjectNotFoundException::class); $this->session(); }); @@ -125,6 +120,13 @@ public function testInvalidSessionContextException(): void $this->fakeHttp()->get(uri: '/')->assertOk(); } + public function testSessionBindingWithoutRequest(): void + { + $this->expectException(InvalidRequestScopeException::class); + + $this->session(); + } + private function session(): SessionInterface { return $this->getContainer()->get(SessionInterface::class); From 6d5422f08bcad9652ac88592b3b88702d3299d72 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 12 Mar 2024 22:43:47 +0400 Subject: [PATCH 50/84] Cleanup; move `CookieQueue` into `http` scope --- src/AuthHttp/src/Middleware/AuthMiddleware.php | 2 +- src/AuthHttp/src/Middleware/AuthTransportMiddleware.php | 2 +- .../src/Middleware/AuthTransportWithStorageMiddleware.php | 2 +- src/Cookies/src/CookieQueue.php | 2 +- src/Framework/Session/Middleware/SessionMiddleware.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AuthHttp/src/Middleware/AuthMiddleware.php b/src/AuthHttp/src/Middleware/AuthMiddleware.php index aed474282..5d779a863 100644 --- a/src/AuthHttp/src/Middleware/AuthMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthMiddleware.php @@ -25,7 +25,7 @@ final class AuthMiddleware implements MiddlewareInterface public const TOKEN_STORAGE_ATTRIBUTE = 'tokenStorage'; /** - * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + * @param ScopeInterface $scope Deprecated, will be removed in v4.0. */ public function __construct( private readonly ScopeInterface $scope, diff --git a/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php b/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php index 883128516..603f085e2 100644 --- a/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthTransportMiddleware.php @@ -22,7 +22,7 @@ final class AuthTransportMiddleware implements MiddlewareInterface private readonly AuthMiddleware $authMiddleware; /** - * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + * @param ScopeInterface $scope Deprecated, will be removed in v4.0. */ public function __construct( string $transportName, diff --git a/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php b/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php index 4470a16fc..b92363eef 100644 --- a/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php +++ b/src/AuthHttp/src/Middleware/AuthTransportWithStorageMiddleware.php @@ -22,7 +22,7 @@ final class AuthTransportWithStorageMiddleware implements MiddlewareInterface private readonly MiddlewareInterface $authMiddleware; /** - * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + * @param ScopeInterface $scope Deprecated, will be removed in v4.0. */ public function __construct( string $transportName, diff --git a/src/Cookies/src/CookieQueue.php b/src/Cookies/src/CookieQueue.php index 699528f61..c6bfdaea5 100644 --- a/src/Cookies/src/CookieQueue.php +++ b/src/Cookies/src/CookieQueue.php @@ -6,7 +6,7 @@ use Spiral\Core\Attribute\Scope; -#[Scope('http.request')] +#[Scope('http')] final class CookieQueue { public const ATTRIBUTE = 'cookieQueue'; diff --git a/src/Framework/Session/Middleware/SessionMiddleware.php b/src/Framework/Session/Middleware/SessionMiddleware.php index 8e3dfaeef..47f850b43 100644 --- a/src/Framework/Session/Middleware/SessionMiddleware.php +++ b/src/Framework/Session/Middleware/SessionMiddleware.php @@ -26,7 +26,7 @@ final class SessionMiddleware implements MiddlewareInterface private const SIGNATURE_HEADERS = ['User-Agent', 'Accept-Language', 'Accept-Encoding']; /** - * @param ScopeInterface $scope. Deprecated, will be removed in v4.0. + * @param ScopeInterface $scope Deprecated, will be removed in v4.0. */ public function __construct( private readonly SessionConfig $config, From a7d7103a719bb770b20594e2c8b7b6db73d9d288 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 12 Mar 2024 20:47:33 +0200 Subject: [PATCH 51/84] Add proxy to a InvokerInterface in JobHandler (#1091) --- src/Queue/src/JobHandler.php | 3 ++- tests/Framework/Queue/QueueRegistryTest.php | 30 +++++++++++++++++++++ tests/app/src/Job/SampleJob.php | 14 ++++++++++ tests/app/src/Job/Task.php | 9 +++++++ tests/app/src/Job/TaskInterface.php | 9 +++++++ 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/Framework/Queue/QueueRegistryTest.php create mode 100644 tests/app/src/Job/SampleJob.php create mode 100644 tests/app/src/Job/Task.php create mode 100644 tests/app/src/Job/TaskInterface.php diff --git a/src/Queue/src/JobHandler.php b/src/Queue/src/JobHandler.php index 29d10f9ef..a89bced8a 100644 --- a/src/Queue/src/JobHandler.php +++ b/src/Queue/src/JobHandler.php @@ -4,6 +4,7 @@ namespace Spiral\Queue; +use Spiral\Core\Attribute\Proxy; use Spiral\Core\InvokerInterface; use Spiral\Queue\Exception\JobException; @@ -18,7 +19,7 @@ abstract class JobHandler implements HandlerInterface protected const HANDLE_FUNCTION = 'invoke'; public function __construct( - protected InvokerInterface $invoker, + #[Proxy] protected InvokerInterface $invoker, ) { } diff --git a/tests/Framework/Queue/QueueRegistryTest.php b/tests/Framework/Queue/QueueRegistryTest.php new file mode 100644 index 000000000..f01ddd0b5 --- /dev/null +++ b/tests/Framework/Queue/QueueRegistryTest.php @@ -0,0 +1,30 @@ + Task::class])] + #[DoesNotPerformAssertions] + public function testHandleJobWithDependencyInScope(): void + { + /** @var Handler $handler */ + $handler = $this->getContainer()->get(Handler::class); + + /** + * Method invoke in SampleJob requires TaskInterface and it's available only in queue scope. + */ + $handler->handle(SampleJob::class, '', '', '', ''); + } +} diff --git a/tests/app/src/Job/SampleJob.php b/tests/app/src/Job/SampleJob.php new file mode 100644 index 000000000..956f09b04 --- /dev/null +++ b/tests/app/src/Job/SampleJob.php @@ -0,0 +1,14 @@ + Date: Wed, 13 Mar 2024 11:56:10 +0200 Subject: [PATCH 52/84] Add ServerRequestInterface binding --- .../Bootloader/Http/HttpBootloader.php | 11 ++++++++-- tests/Framework/Http/CookiesTest.php | 21 +++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index a6e54e635..18ed0074d 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -6,6 +6,7 @@ use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Spiral\Boot\Bootloader\Bootloader; @@ -45,8 +46,14 @@ public function defineDependencies(): array public function defineSingletons(): array { - $this->binder->getBinder(Spiral::Http)->bindSingleton(Http::class, [self::class, 'httpCore']); - $this->binder->getBinder(Spiral::Http)->bindSingleton(CurrentRequest::class, CurrentRequest::class); + $httpBinder = $this->binder->getBinder(Spiral::Http); + + $httpBinder->bindSingleton(Http::class, [self::class, 'httpCore']); + $httpBinder->bindSingleton(CurrentRequest::class, CurrentRequest::class); + $httpBinder->bind( + ServerRequestInterface::class, + static fn (CurrentRequest $request): ServerRequestInterface => $request->get() + ); /** * @deprecated since v3.12. Will be removed in v4.0. diff --git a/tests/Framework/Http/CookiesTest.php b/tests/Framework/Http/CookiesTest.php index 5252fcf00..ec56143e1 100644 --- a/tests/Framework/Http/CookiesTest.php +++ b/tests/Framework/Http/CookiesTest.php @@ -15,7 +15,6 @@ use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Framework\HttpTestCase; -#[TestScope([Spiral::Http, Spiral::HttpRequest])] final class CookiesTest extends HttpTestCase { public const ENV = [ @@ -41,11 +40,10 @@ public function testOutsideOfScopeFail(): void $this->cookies()->get('name'); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testCookieQueueInScope(): void { $this->setHttpHandler(static function (ServerRequestInterface $request) { - ContainerScope::getContainer()->bindSingleton(ServerRequestInterface::class, $request); - self::assertInstanceOf( CookieQueue::class, ContainerScope::getContainer()->get(ServerRequestInterface::class)->getAttribute(CookieQueue::ATTRIBUTE) @@ -69,22 +67,20 @@ public function testCookieQueueInScope(): void $this->fakeHttp()->get('/')->assertOk(); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testHasCookie(): void { $this->setHttpHandler(function (ServerRequestInterface $request) { - $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); - return (int)$this->cookies()->has('a'); }); $this->fakeHttp()->get('/')->assertOk()->assertBodySame('0'); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testHasCookie2(): void { $this->setHttpHandler(function (ServerRequestInterface $request) { - $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); - return (int)$this->cookies()->has('a'); }); @@ -98,11 +94,10 @@ public function testHasCookie2(): void ->assertBodySame('1'); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testGetCookie2(): void { $this->setHttpHandler(function (ServerRequestInterface $request) { - $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); - return $this->cookies()->get('a'); }); @@ -116,11 +111,10 @@ public function testGetCookie2(): void ->assertBodySame('hello'); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testSetCookie(): void { $this->setHttpHandler(function (ServerRequestInterface $request) { - $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); - $this->cookies()->set('a', 'value'); return 'ok'; }); @@ -135,11 +129,10 @@ public function testSetCookie(): void ); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testSetCookie2(): void { $this->setHttpHandler(function (ServerRequestInterface $request): string { - $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); - $this->cookies()->schedule(Cookie::create('a', 'value')); $this->assertSame([], $this->cookies()->getAll()); $this->assertCount(1, $this->cookies()->getScheduled()); @@ -157,10 +150,10 @@ public function testSetCookie2(): void ); } + #[TestScope([Spiral::Http, Spiral::HttpRequest])] public function testDeleteCookie(): void { $this->setHttpHandler(function (ServerRequestInterface $request): string { - $this->getContainer()->bindSingleton(ServerRequestInterface::class, $request); $this->cookies()->delete('cookie'); return 'ok'; }); From 466ffc5d4bcd7279b7f0a26dd5e92011f1000263 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 13 Mar 2024 17:40:06 +0400 Subject: [PATCH 53/84] Http Pipeline: fix CurrentRequest hydration --- .../Bootloader/Auth/HttpAuthBootloader.php | 4 +- .../Bootloader/Http/HttpBootloader.php | 5 +- src/Http/src/CurrentRequest.php | 4 +- src/Http/src/Pipeline.php | 61 +++++++++++-------- src/Http/tests/PipelineTest.php | 45 ++++++++++++++ 5 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/Framework/Bootloader/Auth/HttpAuthBootloader.php b/src/Framework/Bootloader/Auth/HttpAuthBootloader.php index 5170d8c20..e6dfd7716 100644 --- a/src/Framework/Bootloader/Auth/HttpAuthBootloader.php +++ b/src/Framework/Bootloader/Auth/HttpAuthBootloader.php @@ -165,8 +165,8 @@ private function transportRegistry(AuthConfig $config, FactoryInterface $factory */ private function getTokenStorage( TokenStorageProviderInterface $provider, - CurrentRequest $request + ServerRequestInterface $request ): TokenStorageInterface { - return $request->get()->getAttribute(AuthMiddleware::TOKEN_STORAGE_ATTRIBUTE) ?? $provider->getStorage(); + return $request->getAttribute(AuthMiddleware::TOKEN_STORAGE_ATTRIBUTE) ?? $provider->getStorage(); } } diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index 18ed0074d..4ce96cae1 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -20,6 +20,7 @@ use Spiral\Framework\Spiral; use Spiral\Http\Config\HttpConfig; use Spiral\Http\CurrentRequest; +use Spiral\Http\Exception\HttpException; use Spiral\Http\Http; use Spiral\Http\Pipeline; use Spiral\Telemetry\Bootloader\TelemetryBootloader; @@ -52,7 +53,9 @@ public function defineSingletons(): array $httpBinder->bindSingleton(CurrentRequest::class, CurrentRequest::class); $httpBinder->bind( ServerRequestInterface::class, - static fn (CurrentRequest $request): ServerRequestInterface => $request->get() + static fn (CurrentRequest $request): ServerRequestInterface => $request->get() ?? throw new HttpException( + 'Unable to resolve current server request.', + ) ); /** diff --git a/src/Http/src/CurrentRequest.php b/src/Http/src/CurrentRequest.php index a2248e649..4feee69ca 100644 --- a/src/Http/src/CurrentRequest.php +++ b/src/Http/src/CurrentRequest.php @@ -22,8 +22,8 @@ public function set(ServerRequestInterface $request): void $this->request = $request; } - public function get(): ServerRequestInterface + public function get(): ?ServerRequestInterface { - return $this->request ?? throw new HttpException('Unable to resolve current server request.'); + return $this->request; } } diff --git a/src/Http/src/Pipeline.php b/src/Http/src/Pipeline.php index 7ce01f8b4..79dc91ae1 100644 --- a/src/Http/src/Pipeline.php +++ b/src/Http/src/Pipeline.php @@ -63,43 +63,50 @@ public function handle(Request $request): Response throw new PipelineException('Unable to run pipeline, no handler given.'); } - $position = $this->position++; - if (isset($this->middleware[$position])) { + // todo: find a better solution in the Spiral v4.0 + /** @var CurrentRequest|null $currentRequest */ + $currentRequest = ContainerScope::getContainer()?->get(CurrentRequest::class); + + $previousRequest = $currentRequest?->get(); + $currentRequest?->set($request); + try { + $position = $this->position++; + if (!isset($this->middleware[$position])) { + return $this->handler->handle($request); + } + $middleware = $this->middleware[$position]; $this->dispatcher?->dispatch(new MiddlewareProcessing($request, $middleware)); + $callback = function (SpanInterface $span) use ($request, $middleware): Response { + $response = $middleware->process($request, $this); + + $span + ->setAttribute( + 'http.status_code', + $response->getStatusCode() + ) + ->setAttribute( + 'http.response_content_length', + $response->getHeaderLine('Content-Length') ?: $response->getBody()->getSize() + ) + ->setStatus($response->getStatusCode() < 500 ? 'OK' : 'ERROR'); + + return $response; + }; + return $this->tracer->trace( name: \sprintf('Middleware processing [%s]', $middleware::class), - callback: function ( - SpanInterface $span, - CurrentRequest $current - ) use ($request, $middleware): Response { - $current->set($request); - $response = $middleware->process($request, $this); - - $span - ->setAttribute( - 'http.status_code', - $response->getStatusCode() - ) - ->setAttribute( - 'http.response_content_length', - $response->getHeaderLine('Content-Length') ?: $response->getBody()->getSize() - ) - ->setStatus($response->getStatusCode() < 500 ? 'OK' : 'ERROR'); - - return $response; - }, + callback: $callback, attributes: [ 'http.middleware' => $middleware::class, ], scoped: true ); + } finally { + if ($previousRequest !== null) { + $currentRequest?->set($previousRequest); + } } - - // todo: find a better solution in the Spiral v4.0 - ContainerScope::getContainer()?->get(CurrentRequest::class)->set($request); - - return $this->handler->handle($request); } } diff --git a/src/Http/tests/PipelineTest.php b/src/Http/tests/PipelineTest.php index 7ea6e1597..492488954 100644 --- a/src/Http/tests/PipelineTest.php +++ b/src/Http/tests/PipelineTest.php @@ -9,12 +9,16 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Spiral\Core\ContainerScope; +use Spiral\Core\ScopeInterface; use Spiral\Http\CallableHandler; use Spiral\Http\Config\HttpConfig; +use Spiral\Http\CurrentRequest; use Spiral\Http\Event\MiddlewareProcessing; use Spiral\Http\Exception\PipelineException; use Spiral\Http\Pipeline; use Spiral\Telemetry\NullTracer; +use Spiral\Testing\Attribute\TestScope; use Spiral\Tests\Http\Diactoros\ResponseFactory; use Nyholm\Psr7\ServerRequest; @@ -83,4 +87,45 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $pipeline->withHandler($handler)->handle($request); } + + public function testRequestResetThroughPipeline(): void + { + $this->container->getBinder('http') + ->bindSingleton(CurrentRequest::class, new CurrentRequest()); + $this->container->getBinder('http') + ->bind(ServerRequestInterface::class, static fn(CurrentRequest $cr) => $cr->get()); + + $middleware = new class implements MiddlewareInterface { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ): ResponseInterface { + $cRequest = ContainerScope::getContainer()->get(ServerRequestInterface::class); + PipelineTest::assertSame($cRequest, $request); + + $response = $handler->handle($request->withAttribute('foo', 'bar')); + + $cRequest = ContainerScope::getContainer()->get(ServerRequestInterface::class); + PipelineTest::assertSame($cRequest, $request); + return $response; + } + }; + + $this->container->runScope( + new \Spiral\Core\Scope(name: 'http'), + function (ScopeInterface $c) use ($middleware) { + $request = new ServerRequest('GET', ''); + $handler = new CallableHandler(function () { + return 'response'; + }, new ResponseFactory(new HttpConfig(['headers' => []]))); + + $pipeline = new Pipeline($c, null, new NullTracer($c)); + + $pipeline->pushMiddleware($middleware); + $pipeline->pushMiddleware($middleware); + + $pipeline->withHandler($handler)->handle($request); + } + ); + } } From db4978556d061567e6191960c77cc54704e4e032 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 13 Mar 2024 19:15:23 +0400 Subject: [PATCH 54/84] Http class: move event dispatcher into constructor --- src/Http/src/Http.php | 11 ++++------- src/Http/tests/HttpTest.php | 5 ++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Http/src/Http.php b/src/Http/src/Http.php index 3dd124551..8ce37f96b 100644 --- a/src/Http/src/Http.php +++ b/src/Http/src/Http.php @@ -31,7 +31,8 @@ public function __construct( private readonly Pipeline $pipeline, private readonly ResponseFactoryInterface $responseFactory, private readonly ContainerInterface $container, - ?TracerFactoryInterface $tracerFactory = null + ?TracerFactoryInterface $tracerFactory = null, + private readonly ?EventDispatcherInterface $dispatcher = null, ) { foreach ($this->config->getMiddleware() as $middleware) { $this->pipeline->pushMiddleware($this->container->get($middleware)); @@ -63,11 +64,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $callback = function (SpanInterface $span, CurrentRequest $currentRequest) use ($request): ResponseInterface { $currentRequest->set($request); - $dispatcher = $this->container->has(EventDispatcherInterface::class) - ? $this->container->get(EventDispatcherInterface::class) - : null; - - $dispatcher?->dispatch(new RequestReceived($request)); + $this->dispatcher?->dispatch(new RequestReceived($request)); if ($this->handler === null) { throw new HttpException('Unable to run HttpCore, no handler is set.'); @@ -86,7 +83,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface ) ->setStatus($response->getStatusCode() < 500 ? 'OK' : 'ERROR'); - $dispatcher?->dispatch(new RequestHandled($request, $response)); + $this->dispatcher?->dispatch(new RequestHandled($request, $response)); return $response; }; diff --git a/src/Http/tests/HttpTest.php b/src/Http/tests/HttpTest.php index 3e0de41cb..755d74c97 100644 --- a/src/Http/tests/HttpTest.php +++ b/src/Http/tests/HttpTest.php @@ -294,7 +294,10 @@ protected function getCore(array $middleware = []): Http $config, new Pipeline($this->container), new ResponseFactory($config), - $this->container + $this->container, + dispatcher: $this->container->has(EventDispatcherInterface::class) + ? $this->container->get(EventDispatcherInterface::class) + : null, ); } From 078591a1f88b46aca6a2923585c7353baf808a8b Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 15 Mar 2024 18:34:15 +0400 Subject: [PATCH 55/84] Add contract for the new interceptors --- .../Attributed/AttributedInterface.php | 56 +++++++++++++++++++ .../Context/Attributed/AttributedTrait.php | 37 ++++++++++++ src/Hmvc/src/Context/CallContext.php | 45 +++++++++++++++ src/Hmvc/src/Context/CallContextInterface.php | 19 +++++++ .../src/Context/Target/TargetInterface.php | 28 ++++++++++ src/Hmvc/src/Reborn/HandlerInterface.php | 12 ++++ src/Hmvc/src/Reborn/InterceptorInterface.php | 12 ++++ 7 files changed, 209 insertions(+) create mode 100644 src/Hmvc/src/Context/Attributed/AttributedInterface.php create mode 100644 src/Hmvc/src/Context/Attributed/AttributedTrait.php create mode 100644 src/Hmvc/src/Context/CallContext.php create mode 100644 src/Hmvc/src/Context/CallContextInterface.php create mode 100644 src/Hmvc/src/Context/Target/TargetInterface.php create mode 100644 src/Hmvc/src/Reborn/HandlerInterface.php create mode 100644 src/Hmvc/src/Reborn/InterceptorInterface.php diff --git a/src/Hmvc/src/Context/Attributed/AttributedInterface.php b/src/Hmvc/src/Context/Attributed/AttributedInterface.php new file mode 100644 index 000000000..cd9ca073b --- /dev/null +++ b/src/Hmvc/src/Context/Attributed/AttributedInterface.php @@ -0,0 +1,56 @@ + Attributes derived from the context. + */ + public function getAttributes(): array; + + /** + * Retrieve a single derived context attribute. + * + * Retrieves a single derived context attribute as described in {@see getAttributes()}. + * If the attribute has not been previously set, returns the default value as provided. + * + * This method obviates the need for a {@see hasAttribute()} method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @param non-empty-string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + */ + public function getAttribute(string $name, mixed $default = null): mixed; + + /** + * Return an instance with the specified attribute. + * + * This method allows setting a single context attribute as + * described in {@see getAttributes()}. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @param non-empty-string $name The attribute name. + * @param mixed $value The value of the attribute. + */ + public function withAttribute(string $name, mixed $value): static; + + /** + * Return an instance that removes the specified context attribute. + * + * This method allows removing a single context attribute as described in {@see getAttributes()}. + * + * @param non-empty-string $name The attribute name. + */ + public function withoutAttribute(string $name): static; +} diff --git a/src/Hmvc/src/Context/Attributed/AttributedTrait.php b/src/Hmvc/src/Context/Attributed/AttributedTrait.php new file mode 100644 index 000000000..1f1b72755 --- /dev/null +++ b/src/Hmvc/src/Context/Attributed/AttributedTrait.php @@ -0,0 +1,37 @@ +attributes; + } + + public function getAttribute(string $name, mixed $default = null): mixed + { + return $this->attributes[$name] ?? $default; + } + + public function withAttribute(string $name, mixed $value): static + { + $clone = clone $this; + $clone->attributes[$name] = $value; + return $clone; + } + + public function withoutAttribute(string $name): static + { + $clone = clone $this; + unset($clone->attributes[$name]); + return $clone; + } +} diff --git a/src/Hmvc/src/Context/CallContext.php b/src/Hmvc/src/Context/CallContext.php new file mode 100644 index 000000000..f08ee3179 --- /dev/null +++ b/src/Hmvc/src/Context/CallContext.php @@ -0,0 +1,45 @@ +attributes = $attributes; + } + + public function getTarget(): TargetInterface + { + return $this->target; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function withTarget(TargetInterface $target): static + { + $clone = clone $this; + $clone->target = $target; + return $clone; + } + + public function withArguments(array $arguments): static + { + $clone = clone $this; + $clone->arguments = $arguments; + return $clone; + } +} diff --git a/src/Hmvc/src/Context/CallContextInterface.php b/src/Hmvc/src/Context/CallContextInterface.php new file mode 100644 index 000000000..f84519d2a --- /dev/null +++ b/src/Hmvc/src/Context/CallContextInterface.php @@ -0,0 +1,19 @@ + + */ + public function getPath(): array; + + public function withPath(array $path): static; + + public function getReflection(): ?\ReflectionFunctionAbstract; + + /** + * @param \ReflectionFunctionAbstract|null $reflection Pass null to remove the reflection. + */ + public function withReflection(?\ReflectionFunctionAbstract $reflection): static; +} diff --git a/src/Hmvc/src/Reborn/HandlerInterface.php b/src/Hmvc/src/Reborn/HandlerInterface.php new file mode 100644 index 000000000..efb2f9a4c --- /dev/null +++ b/src/Hmvc/src/Reborn/HandlerInterface.php @@ -0,0 +1,12 @@ + Date: Mon, 18 Mar 2024 23:35:13 +0400 Subject: [PATCH 56/84] Add Target implementation --- src/Hmvc/src/Context/Target/Target.php | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/Hmvc/src/Context/Target/Target.php diff --git a/src/Hmvc/src/Context/Target/Target.php b/src/Hmvc/src/Context/Target/Target.php new file mode 100644 index 000000000..a6b6aba29 --- /dev/null +++ b/src/Hmvc/src/Context/Target/Target.php @@ -0,0 +1,77 @@ + $path + */ + public static function fromPathArray(array $path, string $delimiter = '.'): static + { + return new self(path: $path, delimiter: $delimiter); + } + + public function __toString(): string + { + return match (true) { + $this->path !== null => \implode($this->delimiter, $this->path), + $this->reflection !== null => $this->reflection->getName(), + }; + } + + public function getPath(): array + { + return match (true) { + $this->path !== null => $this->path, + $this->reflection instanceof \ReflectionMethod => [ + $this->reflection->getDeclaringClass()->getName(), + $this->reflection->getName(), + ], + $this->reflection instanceof \ReflectionFunction => [$this->reflection->getName()], + default => [], + }; + } + + public function withPath(array $path): static + { + $clone = clone $this; + $clone->path = $path; + return $clone; + } + + public function getReflection(): ?\ReflectionFunctionAbstract + { + return $this->reflection; + } + + public function withReflection(?\ReflectionFunctionAbstract $reflection): static + { + $clone = clone $this; + $clone->reflection = $reflection; + return $clone; + } + + /** + * @param list $path + * @param \ReflectionFunctionAbstract|null $reflection + */ + private function __construct( + private ?array $path = null, + private ?\ReflectionFunctionAbstract $reflection = null, + private readonly string $delimiter = '.', + ) { + } +} From 99e8f743cee11e83a8bf4072663ba2b0df179c92 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 18 Mar 2024 23:36:34 +0400 Subject: [PATCH 57/84] make interceptor pipeline compatible with both new and old interceptors --- src/Hmvc/src/Context/CallContext.php | 4 +- src/Hmvc/src/Event/InterceptorCalling.php | 5 +- src/Hmvc/src/InterceptorPipeline.php | 73 +++++++++++++++++--- src/Hmvc/src/Reborn/InterceptorInterface.php | 2 +- src/Hmvc/tests/InterceptorPipelineTest.php | 10 ++- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/Hmvc/src/Context/CallContext.php b/src/Hmvc/src/Context/CallContext.php index f08ee3179..a264e1979 100644 --- a/src/Hmvc/src/Context/CallContext.php +++ b/src/Hmvc/src/Context/CallContext.php @@ -13,8 +13,8 @@ final class CallContext implements CallContextInterface public function __construct( private TargetInterface $target, - private array $arguments, - array $attributes, + private array $arguments = [], + array $attributes = [], ) { $this->attributes = $attributes; } diff --git a/src/Hmvc/src/Event/InterceptorCalling.php b/src/Hmvc/src/Event/InterceptorCalling.php index 60becba73..1768ba02f 100644 --- a/src/Hmvc/src/Event/InterceptorCalling.php +++ b/src/Hmvc/src/Event/InterceptorCalling.php @@ -4,7 +4,9 @@ namespace Spiral\Core\Event; +use Spiral\Core\Context\CallContextInterface; use Spiral\Core\CoreInterceptorInterface; +use Spiral\Core\Reborn\InterceptorInterface; final class InterceptorCalling { @@ -12,7 +14,8 @@ public function __construct( public readonly string $controller, public readonly string $action, public readonly array $parameters, - public readonly CoreInterceptorInterface $interceptor + public readonly CoreInterceptorInterface|InterceptorInterface $interceptor, + public readonly CallContextInterface $context, ) { } } diff --git a/src/Hmvc/src/InterceptorPipeline.php b/src/Hmvc/src/InterceptorPipeline.php index ea104e8cf..ebc2775ac 100644 --- a/src/Hmvc/src/InterceptorPipeline.php +++ b/src/Hmvc/src/InterceptorPipeline.php @@ -5,27 +5,33 @@ namespace Spiral\Core; use Psr\EventDispatcher\EventDispatcherInterface; +use Spiral\Core\Context\CallContext; +use Spiral\Core\Context\Target\Target; use Spiral\Core\Event\InterceptorCalling; use Spiral\Core\Exception\InterceptorException; +use Spiral\Core\Reborn\HandlerInterface; +use Spiral\Core\Reborn\InterceptorInterface; /** * Provides the ability to modify the call to the domain core on it's way to the action. */ -final class InterceptorPipeline implements CoreInterface +final class InterceptorPipeline implements CoreInterface, HandlerInterface { private ?CoreInterface $core = null; + private ?HandlerInterface $handler = null; - /** @var CoreInterceptorInterface[] */ + /** @var list */ private array $interceptors = []; private int $position = 0; + private ?CallContext $context = null; public function __construct( private readonly ?EventDispatcherInterface $dispatcher = null ) { } - public function addInterceptor(CoreInterceptorInterface $interceptor): void + public function addInterceptor(CoreInterceptorInterface|InterceptorInterface $interceptor): void { $this->interceptors[] = $interceptor; } @@ -34,7 +40,15 @@ public function withCore(CoreInterface $core): self { $pipeline = clone $this; $pipeline->core = $core; + $pipeline->handler = null; + return $pipeline; + } + public function withHandler(HandlerInterface $handler): self + { + $pipeline = clone $this; + $pipeline->handler = $handler; + $pipeline->core = null; return $pipeline; } @@ -43,23 +57,60 @@ public function withCore(CoreInterface $core): self */ public function callAction(string $controller, string $action, array $parameters = []): mixed { - if ($this->core === null) { - throw new InterceptorException('Unable to invoke pipeline without assigned core'); + if ($this->context === null) { + return $this->handle( + new CallContext(Target::fromPathArray([$controller, $action]), $parameters), + ); + } + + if ($this->context->getTarget()->getPath() === [$controller, $action]) { + return $this->handle($this->context->withArguments($parameters)); } + return $this->handle( + $this->context->withTarget( + Target::fromPathArray([$controller, $action]), + )->withArguments($parameters) + ); + } + + /** + * @throws \Throwable + */ + public function handle(CallContext $context): mixed + { + if ($this->core === null && $this->handler === null) { + throw new InterceptorException('Unable to invoke pipeline without last handler.'); + } + + $path = $context->getTarget()->getPath(); + $position = $this->position++; if (isset($this->interceptors[$position])) { $interceptor = $this->interceptors[$position]; + $this->dispatcher?->dispatch(new InterceptorCalling( - controller: $controller, - action: $action, - parameters: $parameters, - interceptor: $interceptor + controller: $path[0] ?? '', + action: $path[1] ?? '', + parameters: $context->getArguments(), + interceptor: $interceptor, + context: $context, )); - return $interceptor->process($controller, $action, $parameters, $this); + return $interceptor instanceof CoreInterceptorInterface + ? $interceptor->process($path[0] ?? '', $path[1] ?? '', $context->getArguments(), $this->withContext($context)) + : $interceptor->intercept($context, $this->withContext($context)); } - return $this->core->callAction($controller, $action, $parameters); + return $this->core !== null + ? $this->core->callAction($path[0] ?? '', $path[1] ?? '', $context->getArguments()) + : $this->handler->handle($context); + } + + private function withContext(CallContext $context): self + { + $pipeline = clone $this; + $pipeline->context = $context; + return $pipeline; } } diff --git a/src/Hmvc/src/Reborn/InterceptorInterface.php b/src/Hmvc/src/Reborn/InterceptorInterface.php index 464502773..0b052e245 100644 --- a/src/Hmvc/src/Reborn/InterceptorInterface.php +++ b/src/Hmvc/src/Reborn/InterceptorInterface.php @@ -8,5 +8,5 @@ interface InterceptorInterface { - public function intercept(CallContextInterface $context, HandlerInterface $next): mixed; + public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed; } diff --git a/src/Hmvc/tests/InterceptorPipelineTest.php b/src/Hmvc/tests/InterceptorPipelineTest.php index b8bd84431..fdfa2bab7 100644 --- a/src/Hmvc/tests/InterceptorPipelineTest.php +++ b/src/Hmvc/tests/InterceptorPipelineTest.php @@ -6,6 +6,8 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; +use Spiral\Core\Context\CallContext; +use Spiral\Core\Context\Target\Target; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\Event\InterceptorCalling; @@ -26,7 +28,13 @@ public function process(string $controller, string $action, array $parameters, C $dispatcher ->expects(self::once()) ->method('dispatch') - ->with(new InterceptorCalling('test', 'test2', [], $interceptor)); + ->with(new InterceptorCalling( + 'test', + 'test2', + [], + $interceptor, + new CallContext(Target::fromPathArray(['test', 'test2'])) + )); $pipeline = new InterceptorPipeline($dispatcher); $pipeline->addInterceptor($interceptor); From de7725e2df0979e0737847fe34ab7d41efa2f03a Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 19 Mar 2024 11:49:42 +0400 Subject: [PATCH 58/84] Recompose files --- src/Hmvc/src/AbstractCore.php | 3 +- .../{Attributed => }/AttributedInterface.php | 2 +- .../{Attributed => }/AttributedTrait.php | 2 +- src/Hmvc/src/Context/CallContext.php | 3 -- src/Hmvc/src/Context/CallContextInterface.php | 3 -- src/Hmvc/src/Context/{Target => }/Target.php | 40 +++++++++---------- .../Context/{Target => }/TargetInterface.php | 4 +- src/Hmvc/src/CoreInterceptorInterface.php | 2 + src/Hmvc/src/CoreInterface.php | 2 + src/Hmvc/src/Event/InterceptorCalling.php | 5 ++- .../src/{Reborn => }/HandlerInterface.php | 2 +- .../src/{Reborn => }/InterceptorInterface.php | 2 +- src/Hmvc/src/InterceptorPipeline.php | 10 ++--- src/Hmvc/tests/InterceptorPipelineTest.php | 2 +- 14 files changed, 41 insertions(+), 41 deletions(-) rename src/Hmvc/src/Context/{Attributed => }/AttributedInterface.php (97%) rename src/Hmvc/src/Context/{Attributed => }/AttributedTrait.php (94%) rename src/Hmvc/src/Context/{Target => }/Target.php (98%) rename src/Hmvc/src/Context/{Target => }/TargetInterface.php (86%) rename src/Hmvc/src/{Reborn => }/HandlerInterface.php (84%) rename src/Hmvc/src/{Reborn => }/InterceptorInterface.php (87%) diff --git a/src/Hmvc/src/AbstractCore.php b/src/Hmvc/src/AbstractCore.php index 9b473e189..0a610192c 100644 --- a/src/Hmvc/src/AbstractCore.php +++ b/src/Hmvc/src/AbstractCore.php @@ -9,12 +9,13 @@ use Spiral\Core\Exception\ControllerException; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; use Spiral\Core\Exception\Resolver\InvalidArgumentException; -use TypeError; /** * Provides ability to call controllers in IoC scope. * * Make sure to bind ScopeInterface in your container. + * + * @deprecated */ abstract class AbstractCore implements CoreInterface { diff --git a/src/Hmvc/src/Context/Attributed/AttributedInterface.php b/src/Hmvc/src/Context/AttributedInterface.php similarity index 97% rename from src/Hmvc/src/Context/Attributed/AttributedInterface.php rename to src/Hmvc/src/Context/AttributedInterface.php index cd9ca073b..59ec7a745 100644 --- a/src/Hmvc/src/Context/Attributed/AttributedInterface.php +++ b/src/Hmvc/src/Context/AttributedInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Spiral\Core\Context\Attributed; +namespace Spiral\Core\Context; interface AttributedInterface { diff --git a/src/Hmvc/src/Context/Attributed/AttributedTrait.php b/src/Hmvc/src/Context/AttributedTrait.php similarity index 94% rename from src/Hmvc/src/Context/Attributed/AttributedTrait.php rename to src/Hmvc/src/Context/AttributedTrait.php index 1f1b72755..363e550a0 100644 --- a/src/Hmvc/src/Context/Attributed/AttributedTrait.php +++ b/src/Hmvc/src/Context/AttributedTrait.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Spiral\Core\Context\Attributed; +namespace Spiral\Core\Context; /** * Provides a basic implementation of the {@see AttributedInterface}. diff --git a/src/Hmvc/src/Context/CallContext.php b/src/Hmvc/src/Context/CallContext.php index a264e1979..a47934390 100644 --- a/src/Hmvc/src/Context/CallContext.php +++ b/src/Hmvc/src/Context/CallContext.php @@ -4,9 +4,6 @@ namespace Spiral\Core\Context; -use Spiral\Core\Context\Attributed\AttributedTrait; -use Spiral\Core\Context\Target\TargetInterface; - final class CallContext implements CallContextInterface { use AttributedTrait; diff --git a/src/Hmvc/src/Context/CallContextInterface.php b/src/Hmvc/src/Context/CallContextInterface.php index f84519d2a..fcc9ee8a1 100644 --- a/src/Hmvc/src/Context/CallContextInterface.php +++ b/src/Hmvc/src/Context/CallContextInterface.php @@ -4,9 +4,6 @@ namespace Spiral\Core\Context; -use Spiral\Core\Context\Attributed\AttributedInterface; -use Spiral\Core\Context\Target\TargetInterface; - interface CallContextInterface extends AttributedInterface { public function getTarget(): mixed; diff --git a/src/Hmvc/src/Context/Target/Target.php b/src/Hmvc/src/Context/Target.php similarity index 98% rename from src/Hmvc/src/Context/Target/Target.php rename to src/Hmvc/src/Context/Target.php index a6b6aba29..988460a8d 100644 --- a/src/Hmvc/src/Context/Target/Target.php +++ b/src/Hmvc/src/Context/Target.php @@ -2,10 +2,29 @@ declare(strict_types=1); -namespace Spiral\Core\Context\Target; +namespace Spiral\Core\Context; final class Target implements TargetInterface { + /** + * @param list $path + * @param \ReflectionFunctionAbstract|null $reflection + */ + private function __construct( + private ?array $path = null, + private ?\ReflectionFunctionAbstract $reflection = null, + private readonly string $delimiter = '.', + ) { + } + + public function __toString(): string + { + return match (true) { + $this->path !== null => \implode($this->delimiter, $this->path), + $this->reflection !== null => $this->reflection->getName(), + }; + } + public static function fromReflection(\ReflectionFunctionAbstract $reflection): static { return new self(reflection: $reflection); @@ -24,14 +43,6 @@ public static function fromPathArray(array $path, string $delimiter = '.'): stat return new self(path: $path, delimiter: $delimiter); } - public function __toString(): string - { - return match (true) { - $this->path !== null => \implode($this->delimiter, $this->path), - $this->reflection !== null => $this->reflection->getName(), - }; - } - public function getPath(): array { return match (true) { @@ -63,15 +74,4 @@ public function withReflection(?\ReflectionFunctionAbstract $reflection): static $clone->reflection = $reflection; return $clone; } - - /** - * @param list $path - * @param \ReflectionFunctionAbstract|null $reflection - */ - private function __construct( - private ?array $path = null, - private ?\ReflectionFunctionAbstract $reflection = null, - private readonly string $delimiter = '.', - ) { - } } diff --git a/src/Hmvc/src/Context/Target/TargetInterface.php b/src/Hmvc/src/Context/TargetInterface.php similarity index 86% rename from src/Hmvc/src/Context/Target/TargetInterface.php rename to src/Hmvc/src/Context/TargetInterface.php index 912700f84..04316815e 100644 --- a/src/Hmvc/src/Context/Target/TargetInterface.php +++ b/src/Hmvc/src/Context/TargetInterface.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Spiral\Core\Context\Target; +namespace Spiral\Core\Context; use Stringable; /** - * The target may be a concrete reflection or alias. + * The target may be a concrete reflection or an alias. * In both cases, you can get a path to the target. */ interface TargetInterface extends Stringable diff --git a/src/Hmvc/src/CoreInterceptorInterface.php b/src/Hmvc/src/CoreInterceptorInterface.php index a0776cf54..921757b32 100644 --- a/src/Hmvc/src/CoreInterceptorInterface.php +++ b/src/Hmvc/src/CoreInterceptorInterface.php @@ -8,6 +8,8 @@ /** * Provides the ability to intercept and wrap the call to the domain core with all the call context. + * + * @deprecated Use {@see InterceptorInterface} instead. */ interface CoreInterceptorInterface { diff --git a/src/Hmvc/src/CoreInterface.php b/src/Hmvc/src/CoreInterface.php index 2fab3f757..354178097 100644 --- a/src/Hmvc/src/CoreInterface.php +++ b/src/Hmvc/src/CoreInterface.php @@ -8,6 +8,8 @@ /** * General application enterpoint class. + * + * @deprecated Use {@see HandlerInterface} instead. */ interface CoreInterface { diff --git a/src/Hmvc/src/Event/InterceptorCalling.php b/src/Hmvc/src/Event/InterceptorCalling.php index 1768ba02f..cbbfa53ff 100644 --- a/src/Hmvc/src/Event/InterceptorCalling.php +++ b/src/Hmvc/src/Event/InterceptorCalling.php @@ -6,13 +6,16 @@ use Spiral\Core\Context\CallContextInterface; use Spiral\Core\CoreInterceptorInterface; -use Spiral\Core\Reborn\InterceptorInterface; +use Spiral\Core\InterceptorInterface; final class InterceptorCalling { public function __construct( + /** @deprecated will be removed in Spiral v4.0. Use $context instead */ public readonly string $controller, + /** @deprecated will be removed in Spiral v4.0. Use $context instead */ public readonly string $action, + /** @deprecated will be removed in Spiral v4.0. Use $context instead */ public readonly array $parameters, public readonly CoreInterceptorInterface|InterceptorInterface $interceptor, public readonly CallContextInterface $context, diff --git a/src/Hmvc/src/Reborn/HandlerInterface.php b/src/Hmvc/src/HandlerInterface.php similarity index 84% rename from src/Hmvc/src/Reborn/HandlerInterface.php rename to src/Hmvc/src/HandlerInterface.php index efb2f9a4c..c00a23804 100644 --- a/src/Hmvc/src/Reborn/HandlerInterface.php +++ b/src/Hmvc/src/HandlerInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Spiral\Core\Reborn; +namespace Spiral\Core; use Spiral\Core\Context\CallContext; diff --git a/src/Hmvc/src/Reborn/InterceptorInterface.php b/src/Hmvc/src/InterceptorInterface.php similarity index 87% rename from src/Hmvc/src/Reborn/InterceptorInterface.php rename to src/Hmvc/src/InterceptorInterface.php index 0b052e245..fe43231b9 100644 --- a/src/Hmvc/src/Reborn/InterceptorInterface.php +++ b/src/Hmvc/src/InterceptorInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Spiral\Core\Reborn; +namespace Spiral\Core; use Spiral\Core\Context\CallContextInterface; diff --git a/src/Hmvc/src/InterceptorPipeline.php b/src/Hmvc/src/InterceptorPipeline.php index ebc2775ac..cb3be82ac 100644 --- a/src/Hmvc/src/InterceptorPipeline.php +++ b/src/Hmvc/src/InterceptorPipeline.php @@ -6,11 +6,9 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Core\Context\CallContext; -use Spiral\Core\Context\Target\Target; +use Spiral\Core\Context\Target; use Spiral\Core\Event\InterceptorCalling; use Spiral\Core\Exception\InterceptorException; -use Spiral\Core\Reborn\HandlerInterface; -use Spiral\Core\Reborn\InterceptorInterface; /** * Provides the ability to modify the call to the domain core on it's way to the action. @@ -102,9 +100,9 @@ public function handle(CallContext $context): mixed : $interceptor->intercept($context, $this->withContext($context)); } - return $this->core !== null - ? $this->core->callAction($path[0] ?? '', $path[1] ?? '', $context->getArguments()) - : $this->handler->handle($context); + return $this->core === null + ? $this->handler->handle($context) + : $this->core->callAction($path[0] ?? '', $path[1] ?? '', $context->getArguments()); } private function withContext(CallContext $context): self diff --git a/src/Hmvc/tests/InterceptorPipelineTest.php b/src/Hmvc/tests/InterceptorPipelineTest.php index fdfa2bab7..cc12a20e4 100644 --- a/src/Hmvc/tests/InterceptorPipelineTest.php +++ b/src/Hmvc/tests/InterceptorPipelineTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Core\Context\CallContext; -use Spiral\Core\Context\Target\Target; +use Spiral\Core\Context\Target; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\Event\InterceptorCalling; From bfda7e105b37ecce669c8125aeb700a3c053c950 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 19 Mar 2024 16:33:22 +0400 Subject: [PATCH 59/84] Add tests --- src/Hmvc/src/Context/CallContextInterface.php | 2 +- .../tests/Unit/InterceptorPipelineTest.php | 120 ++++++++++++++++++ .../Unit/Stub/AddAttributeInterceptor.php | 23 ++++ .../tests/Unit/Stub/ExceptionInterceptor.php | 17 +++ .../Stub/Legacy/LegacyChangerInterceptor.php | 27 ++++ .../Stub/Legacy/LegacyStatefulInterceptor.php | 26 ++++ .../tests/Unit/Stub/StatefulInterceptor.php | 23 ++++ 7 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 src/Hmvc/tests/Unit/InterceptorPipelineTest.php create mode 100644 src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php create mode 100644 src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php create mode 100644 src/Hmvc/tests/Unit/Stub/Legacy/LegacyChangerInterceptor.php create mode 100644 src/Hmvc/tests/Unit/Stub/Legacy/LegacyStatefulInterceptor.php create mode 100644 src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php diff --git a/src/Hmvc/src/Context/CallContextInterface.php b/src/Hmvc/src/Context/CallContextInterface.php index fcc9ee8a1..0812a82f4 100644 --- a/src/Hmvc/src/Context/CallContextInterface.php +++ b/src/Hmvc/src/Context/CallContextInterface.php @@ -6,7 +6,7 @@ interface CallContextInterface extends AttributedInterface { - public function getTarget(): mixed; + public function getTarget(): TargetInterface; public function getArguments(): array; diff --git a/src/Hmvc/tests/Unit/InterceptorPipelineTest.php b/src/Hmvc/tests/Unit/InterceptorPipelineTest.php new file mode 100644 index 000000000..81684b6dd --- /dev/null +++ b/src/Hmvc/tests/Unit/InterceptorPipelineTest.php @@ -0,0 +1,120 @@ +createPipeline(); + + self::expectExceptionMessage('Unable to invoke pipeline without last handler.'); + + $pipeline->callAction('controller', 'action'); + } + + public function testHandleWithoutHandler(): void + { + $pipeline = $this->createPipeline(); + + self::expectExceptionMessage('Unable to invoke pipeline without last handler.'); + + $pipeline->handle(new \Spiral\Core\Context\CallContext(Target::fromPathArray(['controller', 'action']))); + } + + public function testCrossCompatibility(): void + { + $handler = self::createMock(CoreInterface::class); + $handler->expects(self::once()) + ->method('callAction') + ->with('controller', 'action') + ->willReturn('result'); + + $pipeline = $this->createPipeline([ + new AddAttributeInterceptor('key', 'value'), + new LegacyStatefulInterceptor(), + new AddAttributeInterceptor('foo', 'bar'), + new LegacyStatefulInterceptor(), + $state = new StatefulInterceptor(), + ], $handler); + + $result = $pipeline->callAction('controller', 'action'); + // Attributes won't be lost after legacy interceptor + self::assertSame(['key' => 'value', 'foo' => 'bar'], $state->context->getAttributes()); + self::assertSame('result', $result); + } + + public function testLegacyChangesContextPath(): void + { + $handler = self::createMock(CoreInterface::class); + $handler->expects(self::once()) + ->method('callAction') + ->with('foo', 'bar') + ->willReturn('result'); + + $pipeline = $this->createPipeline([ + new LegacyChangerInterceptor(controller: 'newController', action: 'newAction'), + $state = new StatefulInterceptor(), + new LegacyChangerInterceptor(controller: 'foo', action: 'bar'), + ], $handler); + + $result = $pipeline->callAction('controller', 'action'); + // Attributes won't be lost after legacy interceptor + self::assertSame(['newController', 'newAction'], $state->context->getTarget()->getPath()); + self::assertSame('result', $result); + } + + public function testAttributesCompatibilityAttributes(): void + { + $pipeline = $this->createPipeline([ + new AddAttributeInterceptor('key', 'value'), + new LegacyStatefulInterceptor(), + $state = new StatefulInterceptor(), + new ExceptionInterceptor(), + ], self::createMock(CoreInterface::class)); + + try { + $pipeline->callAction('controller', 'action'); + } catch (\RuntimeException) { + // Attributes won't be lost after legacy interceptor + self::assertSame(['key' => 'value'], $state->context->getAttributes()); + } + } + + /** + * @param array $interceptors + */ + private function createPipeline( + array $interceptors = [], + CoreInterface|HandlerInterface|null $lastHandler = null, + EventDispatcherInterface|null $dispatcher = null, + ): InterceptorPipeline + { + $pipeline = new InterceptorPipeline($dispatcher); + + $lastHandler instanceof CoreInterface and $pipeline = $pipeline->withCore($lastHandler); + $lastHandler instanceof HandlerInterface and $pipeline = $pipeline->withHandler($lastHandler); + + foreach ($interceptors as $interceptor) { + $pipeline->addInterceptor($interceptor); + } + + return $pipeline; + } +} diff --git a/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php b/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php new file mode 100644 index 000000000..ed3372c08 --- /dev/null +++ b/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php @@ -0,0 +1,23 @@ +handle($context->withAttribute($this->attribute, $this->value)); + } +} diff --git a/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php b/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php new file mode 100644 index 000000000..e837b18b5 --- /dev/null +++ b/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php @@ -0,0 +1,17 @@ +callAction( + $this->controller ?? $controller, + $this->action ?? $action, + $this->parameters ?? $parameters, + ); + } +} diff --git a/src/Hmvc/tests/Unit/Stub/Legacy/LegacyStatefulInterceptor.php b/src/Hmvc/tests/Unit/Stub/Legacy/LegacyStatefulInterceptor.php new file mode 100644 index 000000000..2a0a4005b --- /dev/null +++ b/src/Hmvc/tests/Unit/Stub/Legacy/LegacyStatefulInterceptor.php @@ -0,0 +1,26 @@ +controller = $controller; + $this->action = $action; + $this->parameters = $parameters; + $this->next = $core; + return $this->result = $core->callAction($controller, $action, $parameters); + } +} diff --git a/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php b/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php new file mode 100644 index 000000000..aa09ddb35 --- /dev/null +++ b/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php @@ -0,0 +1,23 @@ +context = $context; + $this->next = $handler; + return $this->result = $handler->handle($context); + } +} From bd3cbac3d5ab76f5acb95fb6389bf84e57088cc4 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 19 Mar 2024 17:24:16 +0400 Subject: [PATCH 60/84] Fix psalm issues --- src/Hmvc/psalm.xml | 6 +++++- src/Hmvc/src/AbstractCore.php | 10 +++++++++- src/Hmvc/src/Context/AttributedTrait.php | 13 +++++++++++++ src/Hmvc/src/Context/CallContext.php | 3 +++ src/Hmvc/src/Context/Target.php | 5 +++-- src/Hmvc/src/Context/TargetInterface.php | 5 ++++- 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Hmvc/psalm.xml b/src/Hmvc/psalm.xml index 3f2636b8d..d03402bf2 100644 --- a/src/Hmvc/psalm.xml +++ b/src/Hmvc/psalm.xml @@ -4,7 +4,7 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" - errorLevel="4" + errorLevel="1" hoistConstants="true" resolveFromConfigFile="true" findUnusedPsalmSuppress="true" @@ -17,4 +17,8 @@ + + + + diff --git a/src/Hmvc/src/AbstractCore.php b/src/Hmvc/src/AbstractCore.php index 0a610192c..446525835 100644 --- a/src/Hmvc/src/AbstractCore.php +++ b/src/Hmvc/src/AbstractCore.php @@ -27,12 +27,18 @@ public function __construct( protected ContainerInterface $container ) { // resolver is usually the container itself + /** @psalm-suppress MixedAssignment */ $this->resolver = $container->get(ResolverInterface::class); } + /** + * @psalm-assert class-string $controller + * @psalm-assert non-empty-string $action + */ public function callAction(string $controller, string $action, array $parameters = []): mixed { try { + /** @psalm-suppress ArgumentTypeCoercion */ $method = new \ReflectionMethod($controller, $action); } catch (\ReflectionException $e) { throw new ControllerException( @@ -68,7 +74,8 @@ public function callAction(string $controller, string $action, array $parameters $container = $this->container; return ContainerScope::runScope( $container, - static fn () => $method->invokeArgs($container->get($controller), $args) + /** @psalm-suppress MixedArgument */ + static fn (): mixed => $method->invokeArgs($container->get($controller), $args) ); } @@ -81,6 +88,7 @@ protected function resolveArguments(\ReflectionMethod $method, array $parameters $parameters[$name] === null && $parameter->isDefaultValueAvailable() ) { + /** @psalm-suppress MixedAssignment */ $parameters[$name] = $parameter->getDefaultValue(); } } diff --git a/src/Hmvc/src/Context/AttributedTrait.php b/src/Hmvc/src/Context/AttributedTrait.php index 363e550a0..3c41d90ea 100644 --- a/src/Hmvc/src/Context/AttributedTrait.php +++ b/src/Hmvc/src/Context/AttributedTrait.php @@ -9,18 +9,28 @@ */ trait AttributedTrait { + /** @var array */ private array $attributes = []; + /** + * @return array Attributes derived from the context. + */ public function getAttributes(): array { return $this->attributes; } + /** + * @param non-empty-string $name + */ public function getAttribute(string $name, mixed $default = null): mixed { return $this->attributes[$name] ?? $default; } + /** + * @param non-empty-string $name + */ public function withAttribute(string $name, mixed $value): static { $clone = clone $this; @@ -28,6 +38,9 @@ public function withAttribute(string $name, mixed $value): static return $clone; } + /** + * @param non-empty-string $name + */ public function withoutAttribute(string $name): static { $clone = clone $this; diff --git a/src/Hmvc/src/Context/CallContext.php b/src/Hmvc/src/Context/CallContext.php index a47934390..05e65522a 100644 --- a/src/Hmvc/src/Context/CallContext.php +++ b/src/Hmvc/src/Context/CallContext.php @@ -8,6 +8,9 @@ final class CallContext implements CallContextInterface { use AttributedTrait; + /** + * @param array $attributes + */ public function __construct( private TargetInterface $target, private array $arguments = [], diff --git a/src/Hmvc/src/Context/Target.php b/src/Hmvc/src/Context/Target.php index 988460a8d..1b4560199 100644 --- a/src/Hmvc/src/Context/Target.php +++ b/src/Hmvc/src/Context/Target.php @@ -7,7 +7,7 @@ final class Target implements TargetInterface { /** - * @param list $path + * @param list $path * @param \ReflectionFunctionAbstract|null $reflection */ private function __construct( @@ -32,11 +32,12 @@ public static function fromReflection(\ReflectionFunctionAbstract $reflection): public static function fromPathString(string $path, string $delimiter = '.'): static { + /** @psalm-suppress ArgumentTypeCoercion */ return new self(path: \explode($delimiter, $path), delimiter: $delimiter); } /** - * @param list $path + * @param list $path */ public static function fromPathArray(array $path, string $delimiter = '.'): static { diff --git a/src/Hmvc/src/Context/TargetInterface.php b/src/Hmvc/src/Context/TargetInterface.php index 04316815e..0c5d78329 100644 --- a/src/Hmvc/src/Context/TargetInterface.php +++ b/src/Hmvc/src/Context/TargetInterface.php @@ -13,10 +13,13 @@ interface TargetInterface extends Stringable { /** - * @return list + * @return list */ public function getPath(): array; + /** + * @param list $path + */ public function withPath(array $path): static; public function getReflection(): ?\ReflectionFunctionAbstract; From 52a1329483bc4d825032e387ed0516fd677f61f4 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 21 Mar 2024 15:04:02 +0400 Subject: [PATCH 61/84] Add Spiral\Interceptors namespace; deprecate old classes and make aliases; --- composer.json | 3 +- src/Hmvc/composer.json | 3 +- src/Hmvc/src/{ => Core}/AbstractCore.php | 27 +++--- src/Hmvc/src/{ => Core}/Core.php | 0 .../{ => Core}/CoreInterceptorInterface.php | 1 + src/Hmvc/src/{ => Core}/CoreInterface.php | 1 + .../{ => Core}/Event/InterceptorCalling.php | 10 +-- .../Core/Exception/ControllerException.php | 28 ++++++ .../{ => Core}/Exception/CoreException.php | 3 + .../Core/Exception/InterceptorException.php | 16 ++++ src/Hmvc/src/{ => Core}/InterceptableCore.php | 0 .../src/{ => Core}/InterceptorPipeline.php | 7 +- .../src/Exception/InterceptorException.php | 9 -- .../Context/AttributedInterface.php | 2 +- .../Context/AttributedTrait.php | 2 +- .../Context/CallContext.php | 2 +- .../Context/CallContextInterface.php | 2 +- .../src/{ => Interceptors}/Context/Target.php | 2 +- .../Context/TargetInterface.php | 2 +- .../Interceptors/Event/InterceptorCalling.php | 17 ++++ .../Exception/InterceptorException.php | 11 +++ .../Exception/TargetCallException.php} | 6 +- .../Interceptors/Handler/MethodResolver.php | 87 +++++++++++++++++++ .../{ => Interceptors}/HandlerInterface.php | 4 +- .../InterceptorInterface.php | 4 +- src/Hmvc/tests/InterceptorPipelineTest.php | 4 +- .../tests/Unit/InterceptorPipelineTest.php | 8 +- .../Unit/Stub/AddAttributeInterceptor.php | 6 +- .../tests/Unit/Stub/ExceptionInterceptor.php | 6 +- .../tests/Unit/Stub/StatefulInterceptor.php | 6 +- 30 files changed, 217 insertions(+), 62 deletions(-) rename src/Hmvc/src/{ => Core}/AbstractCore.php (83%) rename src/Hmvc/src/{ => Core}/Core.php (100%) rename src/Hmvc/src/{ => Core}/CoreInterceptorInterface.php (92%) rename src/Hmvc/src/{ => Core}/CoreInterface.php (94%) rename src/Hmvc/src/{ => Core}/Event/InterceptorCalling.php (51%) create mode 100644 src/Hmvc/src/Core/Exception/ControllerException.php rename src/Hmvc/src/{ => Core}/Exception/CoreException.php (68%) create mode 100644 src/Hmvc/src/Core/Exception/InterceptorException.php rename src/Hmvc/src/{ => Core}/InterceptableCore.php (100%) rename src/Hmvc/src/{ => Core}/InterceptorPipeline.php (95%) delete mode 100644 src/Hmvc/src/Exception/InterceptorException.php rename src/Hmvc/src/{ => Interceptors}/Context/AttributedInterface.php (98%) rename src/Hmvc/src/{ => Interceptors}/Context/AttributedTrait.php (96%) rename src/Hmvc/src/{ => Interceptors}/Context/CallContext.php (95%) rename src/Hmvc/src/{ => Interceptors}/Context/CallContextInterface.php (89%) rename src/Hmvc/src/{ => Interceptors}/Context/Target.php (98%) rename src/Hmvc/src/{ => Interceptors}/Context/TargetInterface.php (94%) create mode 100644 src/Hmvc/src/Interceptors/Event/InterceptorCalling.php create mode 100644 src/Hmvc/src/Interceptors/Exception/InterceptorException.php rename src/Hmvc/src/{Exception/ControllerException.php => Interceptors/Exception/TargetCallException.php} (67%) create mode 100644 src/Hmvc/src/Interceptors/Handler/MethodResolver.php rename src/Hmvc/src/{ => Interceptors}/HandlerInterface.php (61%) rename src/Hmvc/src/{ => Interceptors}/InterceptorInterface.php (66%) diff --git a/composer.json b/composer.json index a87055b21..e2fa6664d 100644 --- a/composer.json +++ b/composer.json @@ -78,7 +78,7 @@ "Spiral\\Cookies\\": "src/Cookies/src", "Spiral\\Core\\": [ "src/Core/src", - "src/Hmvc/src" + "src/Hmvc/src/Core" ], "Spiral\\Csrf\\": "src/Csrf/src", "Spiral\\Debug\\": "src/Debug/src", @@ -90,6 +90,7 @@ "Spiral\\Files\\": "src/Files/src", "Spiral\\Filters\\": "src/Filters/src", "Spiral\\Http\\": "src/Http/src", + "Spiral\\Interceptors\\": "src/Hmvc/src/Interceptors", "Spiral\\Logger\\": "src/Logger/src", "Spiral\\Mailer\\": "src/Mailer/src", "Spiral\\Models\\": "src/Models/src", diff --git a/src/Hmvc/composer.json b/src/Hmvc/composer.json index 8ca888fc5..1cc278ca8 100644 --- a/src/Hmvc/composer.json +++ b/src/Hmvc/composer.json @@ -37,7 +37,8 @@ }, "autoload": { "psr-4": { - "Spiral\\Core\\": "src" + "Spiral\\Core\\": "src/Core", + "Spiral\\Interceptors\\": "src/Interceptors" } }, "autoload-dev": { diff --git a/src/Hmvc/src/AbstractCore.php b/src/Hmvc/src/Core/AbstractCore.php similarity index 83% rename from src/Hmvc/src/AbstractCore.php rename to src/Hmvc/src/Core/AbstractCore.php index 446525835..90a774a6b 100644 --- a/src/Hmvc/src/AbstractCore.php +++ b/src/Hmvc/src/Core/AbstractCore.php @@ -9,13 +9,14 @@ use Spiral\Core\Exception\ControllerException; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; use Spiral\Core\Exception\Resolver\InvalidArgumentException; +use Spiral\Interceptors\Handler\MethodResolver; /** * Provides ability to call controllers in IoC scope. * * Make sure to bind ScopeInterface in your container. * - * @deprecated + * @deprecated will be removed in Spiral v4.0 */ abstract class AbstractCore implements CoreInterface { @@ -37,20 +38,16 @@ public function __construct( */ public function callAction(string $controller, string $action, array $parameters = []): mixed { - try { - /** @psalm-suppress ArgumentTypeCoercion */ - $method = new \ReflectionMethod($controller, $action); - } catch (\ReflectionException $e) { - throw new ControllerException( - \sprintf('Invalid action `%s`->`%s`', $controller, $action), - ControllerException::BAD_ACTION, - $e - ); - } + $method = MethodResolver::pathToReflection($controller, $action); + // Validate method if ($method->isStatic() || !$method->isPublic()) { throw new ControllerException( - \sprintf('Invalid action `%s`->`%s`', $controller, $action), + \sprintf( + 'Invalid action `%s`->`%s`', + $method->getDeclaringClass()->getName(), + $method->getName(), + ), ControllerException::BAD_ACTION ); } @@ -61,13 +58,13 @@ public function callAction(string $controller, string $action, array $parameters throw new ControllerException( \sprintf('Missing/invalid parameter %s of `%s`->`%s`', $e->getParameter(), $controller, $action), ControllerException::BAD_ARGUMENT, - $e + $e, ); } catch (ContainerExceptionInterface $e) { throw new ControllerException( $e->getMessage(), ControllerException::ERROR, - $e + $e, ); } @@ -94,6 +91,6 @@ protected function resolveArguments(\ReflectionMethod $method, array $parameters } // getting the set of arguments should be sent to requested method - return $this->resolver->resolveArguments($method, $parameters, validate: true); + return $this->resolver->resolveArguments($method, $parameters); } } diff --git a/src/Hmvc/src/Core.php b/src/Hmvc/src/Core/Core.php similarity index 100% rename from src/Hmvc/src/Core.php rename to src/Hmvc/src/Core/Core.php diff --git a/src/Hmvc/src/CoreInterceptorInterface.php b/src/Hmvc/src/Core/CoreInterceptorInterface.php similarity index 92% rename from src/Hmvc/src/CoreInterceptorInterface.php rename to src/Hmvc/src/Core/CoreInterceptorInterface.php index 921757b32..b222d9226 100644 --- a/src/Hmvc/src/CoreInterceptorInterface.php +++ b/src/Hmvc/src/Core/CoreInterceptorInterface.php @@ -5,6 +5,7 @@ namespace Spiral\Core; use Spiral\Core\Exception\ControllerException; +use Spiral\Interceptors\InterceptorInterface; /** * Provides the ability to intercept and wrap the call to the domain core with all the call context. diff --git a/src/Hmvc/src/CoreInterface.php b/src/Hmvc/src/Core/CoreInterface.php similarity index 94% rename from src/Hmvc/src/CoreInterface.php rename to src/Hmvc/src/Core/CoreInterface.php index 354178097..512e3cdfd 100644 --- a/src/Hmvc/src/CoreInterface.php +++ b/src/Hmvc/src/Core/CoreInterface.php @@ -5,6 +5,7 @@ namespace Spiral\Core; use Spiral\Core\Exception\ControllerException; +use Spiral\Interceptors\HandlerInterface; /** * General application enterpoint class. diff --git a/src/Hmvc/src/Event/InterceptorCalling.php b/src/Hmvc/src/Core/Event/InterceptorCalling.php similarity index 51% rename from src/Hmvc/src/Event/InterceptorCalling.php rename to src/Hmvc/src/Core/Event/InterceptorCalling.php index cbbfa53ff..9757bd4a7 100644 --- a/src/Hmvc/src/Event/InterceptorCalling.php +++ b/src/Hmvc/src/Core/Event/InterceptorCalling.php @@ -4,21 +4,19 @@ namespace Spiral\Core\Event; -use Spiral\Core\Context\CallContextInterface; use Spiral\Core\CoreInterceptorInterface; -use Spiral\Core\InterceptorInterface; +use Spiral\Interceptors\InterceptorInterface; +/** + * @deprecated use {@see \Spiral\Interceptors\Event\InterceptorCalling} instead + */ final class InterceptorCalling { public function __construct( - /** @deprecated will be removed in Spiral v4.0. Use $context instead */ public readonly string $controller, - /** @deprecated will be removed in Spiral v4.0. Use $context instead */ public readonly string $action, - /** @deprecated will be removed in Spiral v4.0. Use $context instead */ public readonly array $parameters, public readonly CoreInterceptorInterface|InterceptorInterface $interceptor, - public readonly CallContextInterface $context, ) { } } diff --git a/src/Hmvc/src/Core/Exception/ControllerException.php b/src/Hmvc/src/Core/Exception/ControllerException.php new file mode 100644 index 000000000..4aa218795 --- /dev/null +++ b/src/Hmvc/src/Core/Exception/ControllerException.php @@ -0,0 +1,28 @@ +getArguments(), interceptor: $interceptor, - context: $context, )); return $interceptor instanceof CoreInterceptorInterface diff --git a/src/Hmvc/src/Exception/InterceptorException.php b/src/Hmvc/src/Exception/InterceptorException.php deleted file mode 100644 index e83f2bff6..000000000 --- a/src/Hmvc/src/Exception/InterceptorException.php +++ /dev/null @@ -1,9 +0,0 @@ -`%s`', $controller, $action), + TargetCallException::BAD_ACTION, + $e + ); + } + + return $method; + } + + /** + * @throws TargetCallException + */ + public static function validateControllerMethod(\ReflectionMethod $method): void + { + if ($method->isStatic() || !$method->isPublic()) { + throw new TargetCallException( + \sprintf( + 'Invalid action `%s`->`%s`', + $method->getDeclaringClass()->getName(), + $method->getName(), + ), + TargetCallException::BAD_ACTION + ); + } + } + + /** + * @throws TargetCallException + * @throws \Throwable + */ + public static function resolveArguments( + ResolverInterface $resolver, + \ReflectionMethod $method, + array $arguments, + ): array { + try { + return $resolver->resolveArguments($method, $arguments); + } catch (ArgumentResolvingException|InvalidArgumentException $e) { + throw new TargetCallException( + \sprintf( + 'Missing/invalid parameter %s of `%s`->`%s`.', + $e->getParameter(), + $method->getDeclaringClass()->getName(), + $method->getName(), + ), + TargetCallException::BAD_ARGUMENT, + $e, + ); + } catch (ContainerExceptionInterface $e) { + throw new TargetCallException( + $e->getMessage(), + TargetCallException::ERROR, + $e, + ); + } + } +} diff --git a/src/Hmvc/src/HandlerInterface.php b/src/Hmvc/src/Interceptors/HandlerInterface.php similarity index 61% rename from src/Hmvc/src/HandlerInterface.php rename to src/Hmvc/src/Interceptors/HandlerInterface.php index c00a23804..a77224327 100644 --- a/src/Hmvc/src/HandlerInterface.php +++ b/src/Hmvc/src/Interceptors/HandlerInterface.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Spiral\Core; +namespace Spiral\Interceptors; -use Spiral\Core\Context\CallContext; +use Spiral\Interceptors\Context\CallContext; interface HandlerInterface { diff --git a/src/Hmvc/src/InterceptorInterface.php b/src/Hmvc/src/Interceptors/InterceptorInterface.php similarity index 66% rename from src/Hmvc/src/InterceptorInterface.php rename to src/Hmvc/src/Interceptors/InterceptorInterface.php index fe43231b9..7c3cb23d0 100644 --- a/src/Hmvc/src/InterceptorInterface.php +++ b/src/Hmvc/src/Interceptors/InterceptorInterface.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Spiral\Core; +namespace Spiral\Interceptors; -use Spiral\Core\Context\CallContextInterface; +use Spiral\Interceptors\Context\CallContextInterface; interface InterceptorInterface { diff --git a/src/Hmvc/tests/InterceptorPipelineTest.php b/src/Hmvc/tests/InterceptorPipelineTest.php index cc12a20e4..baa152966 100644 --- a/src/Hmvc/tests/InterceptorPipelineTest.php +++ b/src/Hmvc/tests/InterceptorPipelineTest.php @@ -6,12 +6,12 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; -use Spiral\Core\Context\CallContext; -use Spiral\Core\Context\Target; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\Event\InterceptorCalling; use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; final class InterceptorPipelineTest extends TestCase { diff --git a/src/Hmvc/tests/Unit/InterceptorPipelineTest.php b/src/Hmvc/tests/Unit/InterceptorPipelineTest.php index 81684b6dd..57e46e583 100644 --- a/src/Hmvc/tests/Unit/InterceptorPipelineTest.php +++ b/src/Hmvc/tests/Unit/InterceptorPipelineTest.php @@ -5,12 +5,12 @@ namespace Spiral\Tests\Core\Unit; use Psr\EventDispatcher\EventDispatcherInterface; -use Spiral\Core\Context\Target; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; -use Spiral\Core\HandlerInterface; -use Spiral\Core\InterceptorInterface; use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Testing\TestCase; use Spiral\Tests\Core\Unit\Stub\AddAttributeInterceptor; use Spiral\Tests\Core\Unit\Stub\ExceptionInterceptor; @@ -35,7 +35,7 @@ public function testHandleWithoutHandler(): void self::expectExceptionMessage('Unable to invoke pipeline without last handler.'); - $pipeline->handle(new \Spiral\Core\Context\CallContext(Target::fromPathArray(['controller', 'action']))); + $pipeline->handle(new \Spiral\Interceptors\Context\CallContext(Target::fromPathArray(['controller', 'action']))); } public function testCrossCompatibility(): void diff --git a/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php b/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php index ed3372c08..d6c1ea808 100644 --- a/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php +++ b/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php @@ -4,9 +4,9 @@ namespace Spiral\Tests\Core\Unit\Stub; -use Spiral\Core\Context\CallContextInterface; -use Spiral\Core\HandlerInterface; -use Spiral\Core\InterceptorInterface; +use Spiral\Interceptors\Context\CallContextInterface; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; final class AddAttributeInterceptor implements InterceptorInterface { diff --git a/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php b/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php index e837b18b5..2c0718e3f 100644 --- a/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php +++ b/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php @@ -4,9 +4,9 @@ namespace Spiral\Tests\Core\Unit\Stub; -use Spiral\Core\Context\CallContextInterface; -use Spiral\Core\HandlerInterface; -use Spiral\Core\InterceptorInterface; +use Spiral\Interceptors\Context\CallContextInterface; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; final class ExceptionInterceptor implements InterceptorInterface { diff --git a/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php b/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php index aa09ddb35..42c1029aa 100644 --- a/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php +++ b/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php @@ -4,9 +4,9 @@ namespace Spiral\Tests\Core\Unit\Stub; -use Spiral\Core\Context\CallContextInterface; -use Spiral\Core\HandlerInterface; -use Spiral\Core\InterceptorInterface; +use Spiral\Interceptors\Context\CallContextInterface; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; final class StatefulInterceptor implements InterceptorInterface { From b813426ac929888d2a74bdfca162645341396c84 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 21 Mar 2024 23:23:10 +0400 Subject: [PATCH 62/84] Add new InterceptorPipeline and ReflectionHandler --- src/Hmvc/src/Core/AbstractCore.php | 4 +- src/Hmvc/src/Core/Core.php | 2 + src/Hmvc/src/Core/InterceptableCore.php | 12 ++- src/Hmvc/src/Core/InterceptorPipeline.php | 29 ++++--- .../Exception/TargetCallException.php | 9 +- ...{MethodResolver.php => ActionResolver.php} | 20 ++++- .../Handler/ReflectionHandler.php | 83 +++++++++++++++++++ .../src/Interceptors/InterceptorPipeline.php | 69 +++++++++++++++ 8 files changed, 205 insertions(+), 23 deletions(-) rename src/Hmvc/src/Interceptors/Handler/{MethodResolver.php => ActionResolver.php} (79%) create mode 100644 src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php create mode 100644 src/Hmvc/src/Interceptors/InterceptorPipeline.php diff --git a/src/Hmvc/src/Core/AbstractCore.php b/src/Hmvc/src/Core/AbstractCore.php index 90a774a6b..369df6f5d 100644 --- a/src/Hmvc/src/Core/AbstractCore.php +++ b/src/Hmvc/src/Core/AbstractCore.php @@ -9,7 +9,7 @@ use Spiral\Core\Exception\ControllerException; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; use Spiral\Core\Exception\Resolver\InvalidArgumentException; -use Spiral\Interceptors\Handler\MethodResolver; +use Spiral\Interceptors\Handler\ActionResolver; /** * Provides ability to call controllers in IoC scope. @@ -38,7 +38,7 @@ public function __construct( */ public function callAction(string $controller, string $action, array $parameters = []): mixed { - $method = MethodResolver::pathToReflection($controller, $action); + $method = ActionResolver::pathToReflection($controller, $action); // Validate method if ($method->isStatic() || !$method->isPublic()) { diff --git a/src/Hmvc/src/Core/Core.php b/src/Hmvc/src/Core/Core.php index c6d848b14..d87053277 100644 --- a/src/Hmvc/src/Core/Core.php +++ b/src/Hmvc/src/Core/Core.php @@ -6,6 +6,8 @@ /** * Simple domain core to invoke controller actions. + * + * @deprecated use {@see \Spiral\Interceptors\Handler\ReflectionHandler} instead */ final class Core extends AbstractCore { diff --git a/src/Hmvc/src/Core/InterceptableCore.php b/src/Hmvc/src/Core/InterceptableCore.php index f63961ca6..e083714fe 100644 --- a/src/Hmvc/src/Core/InterceptableCore.php +++ b/src/Hmvc/src/Core/InterceptableCore.php @@ -5,11 +5,14 @@ namespace Spiral\Core; use Psr\EventDispatcher\EventDispatcherInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; /** * The domain core with a set of domain action interceptors (business logic middleware). */ -final class InterceptableCore implements CoreInterface +final class InterceptableCore implements CoreInterface, HandlerInterface { private InterceptorPipeline $pipeline; @@ -20,7 +23,7 @@ public function __construct( $this->pipeline = new InterceptorPipeline($dispatcher); } - public function addInterceptor(CoreInterceptorInterface $interceptor): void + public function addInterceptor(CoreInterceptorInterface|InterceptorInterface $interceptor): void { $this->pipeline->addInterceptor($interceptor); } @@ -29,4 +32,9 @@ public function callAction(string $controller, string $action, array $parameters { return $this->pipeline->withCore($this->core)->callAction($controller, $action, $parameters); } + + public function handle(CallContext $context): mixed + { + return $this->pipeline->withCore($this->core)->handle($context); + } } diff --git a/src/Hmvc/src/Core/InterceptorPipeline.php b/src/Hmvc/src/Core/InterceptorPipeline.php index 7fbb55811..03f7152c7 100644 --- a/src/Hmvc/src/Core/InterceptorPipeline.php +++ b/src/Hmvc/src/Core/InterceptorPipeline.php @@ -85,20 +85,22 @@ public function handle(CallContext $context): mixed $path = $context->getTarget()->getPath(); - $position = $this->position++; - if (isset($this->interceptors[$position])) { - $interceptor = $this->interceptors[$position]; - - $this->dispatcher?->dispatch(new InterceptorCalling( - controller: $path[0] ?? '', - action: $path[1] ?? '', - parameters: $context->getArguments(), - interceptor: $interceptor, - )); + if (isset($this->interceptors[$this->position])) { + $interceptor = $this->interceptors[$this->position]; + $handler = $this->nextWithContext($context); + + $this->dispatcher?->dispatch( + new InterceptorCalling( + controller: $path[0] ?? '', + action: $path[1] ?? '', + parameters: $context->getArguments(), + interceptor: $interceptor, + ) + ); return $interceptor instanceof CoreInterceptorInterface - ? $interceptor->process($path[0] ?? '', $path[1] ?? '', $context->getArguments(), $this->withContext($context)) - : $interceptor->intercept($context, $this->withContext($context)); + ? $interceptor->process($path[0] ?? '', $path[1] ?? '', $context->getArguments(), $handler) + : $interceptor->intercept($context, $handler); } return $this->core === null @@ -106,10 +108,11 @@ public function handle(CallContext $context): mixed : $this->core->callAction($path[0] ?? '', $path[1] ?? '', $context->getArguments()); } - private function withContext(CallContext $context): self + private function nextWithContext(CallContext $context): self { $pipeline = clone $this; $pipeline->context = $context; + ++$pipeline->position; return $pipeline; } } diff --git a/src/Hmvc/src/Interceptors/Exception/TargetCallException.php b/src/Hmvc/src/Interceptors/Exception/TargetCallException.php index 0ce818275..f9d309935 100644 --- a/src/Hmvc/src/Interceptors/Exception/TargetCallException.php +++ b/src/Hmvc/src/Interceptors/Exception/TargetCallException.php @@ -12,12 +12,13 @@ class TargetCallException extends \RuntimeException /** * Pre-defined controller error codes. */ - public const NOT_FOUND = 0; - public const BAD_ACTION = 1; + public const NOT_FOUND = 0; + public const BAD_ACTION = 1; public const BAD_ARGUMENT = 2; - public const FORBIDDEN = 3; - public const ERROR = 4; + public const FORBIDDEN = 3; + public const ERROR = 4; public const UNAUTHORIZED = 8; + public const INVALID_CONTROLLER = 16; } \class_alias(TargetCallException::class, 'Spiral\Core\Exception\ControllerException'); diff --git a/src/Hmvc/src/Interceptors/Handler/MethodResolver.php b/src/Hmvc/src/Interceptors/Handler/ActionResolver.php similarity index 79% rename from src/Hmvc/src/Interceptors/Handler/MethodResolver.php rename to src/Hmvc/src/Interceptors/Handler/ActionResolver.php index 62b75021b..09978bbe0 100644 --- a/src/Hmvc/src/Interceptors/Handler/MethodResolver.php +++ b/src/Hmvc/src/Interceptors/Handler/ActionResolver.php @@ -13,7 +13,7 @@ /** * @internal */ -final class MethodResolver +final class ActionResolver { /** * @psalm-assert class-string $controller @@ -39,8 +39,9 @@ public static function pathToReflection(string $controller, string $action): \Re /** * @throws TargetCallException + * @psalm-assert object|null $controller */ - public static function validateControllerMethod(\ReflectionMethod $method): void + public static function validateControllerMethod(\ReflectionMethod $method, mixed $controller = null): void { if ($method->isStatic() || !$method->isPublic()) { throw new TargetCallException( @@ -52,6 +53,21 @@ public static function validateControllerMethod(\ReflectionMethod $method): void TargetCallException::BAD_ACTION ); } + + if ($controller === null) { + return; + } + + if (!\is_object($controller) || !$method->getDeclaringClass()->isInstance($controller)) { + throw new TargetCallException( + \sprintf( + 'Invalid controller. Expected instance of `%s`, got `%s`.', + $method->getDeclaringClass()->getName(), + \get_debug_type($controller), + ), + TargetCallException::INVALID_CONTROLLER, + ); + } } /** diff --git a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php new file mode 100644 index 000000000..baa50502f --- /dev/null +++ b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php @@ -0,0 +1,83 @@ +getTarget()->getReflection(); + if ($method === null) { + $this->resolveFromPath or throw new TargetCallException( + "Reflection not provided for target `{$context->getTarget()}`.", + TargetCallException::NOT_FOUND, + ); + + $path = $context->getTarget()->getPath(); + if (\count($path) !== 2) { + throw new TargetCallException( + "Invalid target path to resolve reflection for `{$context->getTarget()}`." + . ' Expected two parts: class and method.', + TargetCallException::NOT_FOUND, + ); + } + + $method = ActionResolver::pathToReflection(\reset($path), \end($path)); + } + + if ($method instanceof \ReflectionFunction) { + $method->invokeArgs( + $this->resolveArguments($method, $context) + ); + } + + if (!$method instanceof \ReflectionMethod) { + throw new TargetCallException("Action not found for target `{$context->getTarget()}`."); + } + + $controller = $this->container->get($method->getDeclaringClass()->getName()); + + // Validate method and controller + ActionResolver::validateControllerMethod($method, $controller); + + // Run action + return $method->invokeArgs($controller, $this->resolveArguments($method, $context)); + } + + /** + * @throws ContainerExceptionInterface + */ + protected function resolveArguments(\ReflectionFunctionAbstract $method, CallContext $context): array + { + $resolver = $this->container->get(ResolverInterface::class); + \assert($resolver instanceof ResolverInterface); + + return $resolver->resolveArguments($method, $context->getArguments()); + } +} diff --git a/src/Hmvc/src/Interceptors/InterceptorPipeline.php b/src/Hmvc/src/Interceptors/InterceptorPipeline.php new file mode 100644 index 000000000..684a890f3 --- /dev/null +++ b/src/Hmvc/src/Interceptors/InterceptorPipeline.php @@ -0,0 +1,69 @@ + */ + private array $interceptors = []; + + private int $position = 0; + + public function __construct( + private readonly ?EventDispatcherInterface $dispatcher = null + ) { + } + + public function addInterceptor(InterceptorInterface $interceptor): void + { + $this->interceptors[] = $interceptor; + } + + public function withHandler(HandlerInterface $handler): self + { + $pipeline = clone $this; + $pipeline->handler = $handler; + return $pipeline; + } + + /** + * @throws \Throwable + */ + public function handle(CallContext $context): mixed + { + if ($this->handler === null) { + throw new InterceptorException('Unable to invoke pipeline without last handler.'); + } + + if (isset($this->interceptors[$this->position])) { + $interceptor = $this->interceptors[$this->position]; + + $this->dispatcher?->dispatch(new InterceptorCalling(context: $context, interceptor: $interceptor)); + + return $interceptor->intercept($context, $this->next()); + } + + return $this->handler->handle($context); + } + + private function next(): self + { + $pipeline = clone $this; + ++$pipeline->position; + return $pipeline; + } +} From b4fd411db949fb0cbe5f590498d689a0b08e2aad Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 22 Mar 2024 18:49:37 +0400 Subject: [PATCH 63/84] Polishing --- src/Hmvc/src/Core/AbstractCore.php | 2 +- src/Hmvc/src/Core/InterceptableCore.php | 2 ++ src/Hmvc/src/Core/InterceptorPipeline.php | 2 ++ .../src/Interceptors/{ => Handler}/InterceptorPipeline.php | 6 ++++-- src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php | 1 + .../Interceptors/{Handler => Internal}/ActionResolver.php | 2 +- 6 files changed, 11 insertions(+), 4 deletions(-) rename src/Hmvc/src/Interceptors/{ => Handler}/InterceptorPipeline.php (91%) rename src/Hmvc/src/Interceptors/{Handler => Internal}/ActionResolver.php (98%) diff --git a/src/Hmvc/src/Core/AbstractCore.php b/src/Hmvc/src/Core/AbstractCore.php index 369df6f5d..d6900c70e 100644 --- a/src/Hmvc/src/Core/AbstractCore.php +++ b/src/Hmvc/src/Core/AbstractCore.php @@ -9,7 +9,7 @@ use Spiral\Core\Exception\ControllerException; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; use Spiral\Core\Exception\Resolver\InvalidArgumentException; -use Spiral\Interceptors\Handler\ActionResolver; +use Spiral\Interceptors\Internal\ActionResolver; /** * Provides ability to call controllers in IoC scope. diff --git a/src/Hmvc/src/Core/InterceptableCore.php b/src/Hmvc/src/Core/InterceptableCore.php index e083714fe..1397c9760 100644 --- a/src/Hmvc/src/Core/InterceptableCore.php +++ b/src/Hmvc/src/Core/InterceptableCore.php @@ -11,6 +11,8 @@ /** * The domain core with a set of domain action interceptors (business logic middleware). + * + * @deprecated use {@see \Spiral\Interceptors\Handler\InterceptorPipeline} instead */ final class InterceptableCore implements CoreInterface, HandlerInterface { diff --git a/src/Hmvc/src/Core/InterceptorPipeline.php b/src/Hmvc/src/Core/InterceptorPipeline.php index 03f7152c7..dc353349f 100644 --- a/src/Hmvc/src/Core/InterceptorPipeline.php +++ b/src/Hmvc/src/Core/InterceptorPipeline.php @@ -14,6 +14,8 @@ /** * Provides the ability to modify the call to the domain core on it's way to the action. + * + * @deprecated use {@see \Spiral\Interceptors\Handler\InterceptorPipeline} instead */ final class InterceptorPipeline implements CoreInterface, HandlerInterface { diff --git a/src/Hmvc/src/Interceptors/InterceptorPipeline.php b/src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php similarity index 91% rename from src/Hmvc/src/Interceptors/InterceptorPipeline.php rename to src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php index 684a890f3..a47f55fec 100644 --- a/src/Hmvc/src/Interceptors/InterceptorPipeline.php +++ b/src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php @@ -2,12 +2,14 @@ declare(strict_types=1); -namespace Spiral\Interceptors; +namespace Spiral\Interceptors\Handler; use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Interceptors\Context\CallContext; use Spiral\Interceptors\Event\InterceptorCalling; use Spiral\Interceptors\Exception\InterceptorException; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; /** * Interceptor pipeline. @@ -18,7 +20,7 @@ final class InterceptorPipeline implements HandlerInterface { private ?HandlerInterface $handler = null; - /** @var list */ + /** @var InterceptorInterface */ private array $interceptors = []; private int $position = 0; diff --git a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php index baa50502f..b01181d72 100644 --- a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php +++ b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php @@ -10,6 +10,7 @@ use Spiral\Interceptors\Context\CallContext; use Spiral\Interceptors\Exception\TargetCallException; use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\Internal\ActionResolver; class ReflectionHandler implements HandlerInterface { diff --git a/src/Hmvc/src/Interceptors/Handler/ActionResolver.php b/src/Hmvc/src/Interceptors/Internal/ActionResolver.php similarity index 98% rename from src/Hmvc/src/Interceptors/Handler/ActionResolver.php rename to src/Hmvc/src/Interceptors/Internal/ActionResolver.php index 09978bbe0..7fb59adf2 100644 --- a/src/Hmvc/src/Interceptors/Handler/ActionResolver.php +++ b/src/Hmvc/src/Interceptors/Internal/ActionResolver.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Spiral\Interceptors\Handler; +namespace Spiral\Interceptors\Internal; use Psr\Container\ContainerExceptionInterface; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; From 157114eca7f399bc93080f88ee7b567b2e30a8cb Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 27 Mar 2024 19:40:44 +0400 Subject: [PATCH 64/84] Add more tests --- composer.json | 3 +- src/Hmvc/composer.json | 3 +- src/Hmvc/tests/{ => Core}/CoreTest.php | 0 src/Hmvc/tests/{ => Core}/DemoInterceptor.php | 0 .../{ => Core}/Fixtures/CleanController.php | 0 .../{ => Core}/Fixtures/DummyController.php | 0 .../tests/{ => Core}/Fixtures/SampleCore.php | 0 .../{ => Core}/InterceptableCoreTest.php | 0 .../{ => Core}/InterceptorPipelineTest.php | 0 .../Unit/InterceptorPipelineTest.php | 24 +++- .../Unit/Stub/AddAttributeInterceptor.php | 0 .../Unit/Stub/ExceptionInterceptor.php | 0 .../Stub/Legacy/LegacyChangerInterceptor.php | 0 .../Stub/Legacy/LegacyStatefulInterceptor.php | 0 .../Unit/Stub/MultipleCallNextInterceptor.php | 29 +++++ .../Unit/Stub/StatefulInterceptor.php | 0 .../Unit/Handler/InterceptorPipelineTest.php | 112 ++++++++++++++++++ .../Unit/Stub/AddAttributeInterceptor.php | 23 ++++ .../Unit/Stub/ExceptionInterceptor.php | 17 +++ .../Unit/Stub/MultipleCallNextInterceptor.php | 29 +++++ .../Unit/Stub/StatefulInterceptor.php | 23 ++++ 21 files changed, 260 insertions(+), 3 deletions(-) rename src/Hmvc/tests/{ => Core}/CoreTest.php (100%) rename src/Hmvc/tests/{ => Core}/DemoInterceptor.php (100%) rename src/Hmvc/tests/{ => Core}/Fixtures/CleanController.php (100%) rename src/Hmvc/tests/{ => Core}/Fixtures/DummyController.php (100%) rename src/Hmvc/tests/{ => Core}/Fixtures/SampleCore.php (100%) rename src/Hmvc/tests/{ => Core}/InterceptableCoreTest.php (100%) rename src/Hmvc/tests/{ => Core}/InterceptorPipelineTest.php (100%) rename src/Hmvc/tests/{ => Core}/Unit/InterceptorPipelineTest.php (82%) rename src/Hmvc/tests/{ => Core}/Unit/Stub/AddAttributeInterceptor.php (100%) rename src/Hmvc/tests/{ => Core}/Unit/Stub/ExceptionInterceptor.php (100%) rename src/Hmvc/tests/{ => Core}/Unit/Stub/Legacy/LegacyChangerInterceptor.php (100%) rename src/Hmvc/tests/{ => Core}/Unit/Stub/Legacy/LegacyStatefulInterceptor.php (100%) create mode 100644 src/Hmvc/tests/Core/Unit/Stub/MultipleCallNextInterceptor.php rename src/Hmvc/tests/{ => Core}/Unit/Stub/StatefulInterceptor.php (100%) create mode 100644 src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/AddAttributeInterceptor.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/ExceptionInterceptor.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/MultipleCallNextInterceptor.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/StatefulInterceptor.php diff --git a/composer.json b/composer.json index e2fa6664d..408a8daf7 100644 --- a/composer.json +++ b/composer.json @@ -159,7 +159,7 @@ "Spiral\\Tests\\Cookies\\": "src/Cookies/tests", "Spiral\\Tests\\Core\\": [ "src/Core/tests", - "src/Hmvc/tests" + "src/Hmvc/tests/Core" ], "Spiral\\Tests\\Csrf\\": "src/Csrf/tests", "Spiral\\Tests\\Debug\\": "src/Debug/tests", @@ -172,6 +172,7 @@ "Spiral\\Tests\\Filters\\": "src/Filters/tests", "Spiral\\Tests\\Framework\\": "tests/Framework", "Spiral\\Tests\\Http\\": "src/Http/tests", + "Spiral\\Tests\\Interceptors\\": "src/Hmvc/tests/Interceptors", "Spiral\\Tests\\Logger\\": "src/Logger/tests", "Spiral\\Tests\\Mailer\\": "src/Mailer/tests", "Spiral\\Tests\\Models\\": "src/Models/tests", diff --git a/src/Hmvc/composer.json b/src/Hmvc/composer.json index 1cc278ca8..83c22d16e 100644 --- a/src/Hmvc/composer.json +++ b/src/Hmvc/composer.json @@ -43,7 +43,8 @@ }, "autoload-dev": { "psr-4": { - "Spiral\\Tests\\Core\\": "tests" + "Spiral\\Tests\\Core\\": "tests/Core", + "Spiral\\Tests\\Interceptors\\": "tests/Interceptors" } }, "extra": { diff --git a/src/Hmvc/tests/CoreTest.php b/src/Hmvc/tests/Core/CoreTest.php similarity index 100% rename from src/Hmvc/tests/CoreTest.php rename to src/Hmvc/tests/Core/CoreTest.php diff --git a/src/Hmvc/tests/DemoInterceptor.php b/src/Hmvc/tests/Core/DemoInterceptor.php similarity index 100% rename from src/Hmvc/tests/DemoInterceptor.php rename to src/Hmvc/tests/Core/DemoInterceptor.php diff --git a/src/Hmvc/tests/Fixtures/CleanController.php b/src/Hmvc/tests/Core/Fixtures/CleanController.php similarity index 100% rename from src/Hmvc/tests/Fixtures/CleanController.php rename to src/Hmvc/tests/Core/Fixtures/CleanController.php diff --git a/src/Hmvc/tests/Fixtures/DummyController.php b/src/Hmvc/tests/Core/Fixtures/DummyController.php similarity index 100% rename from src/Hmvc/tests/Fixtures/DummyController.php rename to src/Hmvc/tests/Core/Fixtures/DummyController.php diff --git a/src/Hmvc/tests/Fixtures/SampleCore.php b/src/Hmvc/tests/Core/Fixtures/SampleCore.php similarity index 100% rename from src/Hmvc/tests/Fixtures/SampleCore.php rename to src/Hmvc/tests/Core/Fixtures/SampleCore.php diff --git a/src/Hmvc/tests/InterceptableCoreTest.php b/src/Hmvc/tests/Core/InterceptableCoreTest.php similarity index 100% rename from src/Hmvc/tests/InterceptableCoreTest.php rename to src/Hmvc/tests/Core/InterceptableCoreTest.php diff --git a/src/Hmvc/tests/InterceptorPipelineTest.php b/src/Hmvc/tests/Core/InterceptorPipelineTest.php similarity index 100% rename from src/Hmvc/tests/InterceptorPipelineTest.php rename to src/Hmvc/tests/Core/InterceptorPipelineTest.php diff --git a/src/Hmvc/tests/Unit/InterceptorPipelineTest.php b/src/Hmvc/tests/Core/Unit/InterceptorPipelineTest.php similarity index 82% rename from src/Hmvc/tests/Unit/InterceptorPipelineTest.php rename to src/Hmvc/tests/Core/Unit/InterceptorPipelineTest.php index 57e46e583..d994b52d4 100644 --- a/src/Hmvc/tests/Unit/InterceptorPipelineTest.php +++ b/src/Hmvc/tests/Core/Unit/InterceptorPipelineTest.php @@ -8,6 +8,7 @@ use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\Context\CallContext; use Spiral\Interceptors\Context\Target; use Spiral\Interceptors\HandlerInterface; use Spiral\Interceptors\InterceptorInterface; @@ -16,6 +17,7 @@ use Spiral\Tests\Core\Unit\Stub\ExceptionInterceptor; use Spiral\Tests\Core\Unit\Stub\Legacy\LegacyChangerInterceptor; use Spiral\Tests\Core\Unit\Stub\Legacy\LegacyStatefulInterceptor; +use Spiral\Tests\Core\Unit\Stub\MultipleCallNextInterceptor; use Spiral\Tests\Core\Unit\Stub\StatefulInterceptor; final class InterceptorPipelineTest extends TestCase @@ -35,7 +37,7 @@ public function testHandleWithoutHandler(): void self::expectExceptionMessage('Unable to invoke pipeline without last handler.'); - $pipeline->handle(new \Spiral\Interceptors\Context\CallContext(Target::fromPathArray(['controller', 'action']))); + $pipeline->handle(new CallContext(Target::fromPathArray(['controller', 'action']))); } public function testCrossCompatibility(): void @@ -97,6 +99,26 @@ public function testAttributesCompatibilityAttributes(): void } } + /** + * Multiple call of same the handler inside the pipeline must invoke the same interceptor. + */ + public function testCallHandlerTwice(): void + { + $mock = self::createMock(InterceptorInterface::class); + $mock->expects(self::exactly(2)) + ->method('intercept') + ->willReturn('foo', 'bar'); + + $pipeline = $this->createPipeline([ + new MultipleCallNextInterceptor(2), + $mock, + new ExceptionInterceptor(), + ], self::createMock(HandlerInterface::class)); + + $result = $pipeline->callAction('controller', 'action'); + self::assertSame(['foo', 'bar'], $result); + } + /** * @param array $interceptors */ diff --git a/src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php b/src/Hmvc/tests/Core/Unit/Stub/AddAttributeInterceptor.php similarity index 100% rename from src/Hmvc/tests/Unit/Stub/AddAttributeInterceptor.php rename to src/Hmvc/tests/Core/Unit/Stub/AddAttributeInterceptor.php diff --git a/src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php b/src/Hmvc/tests/Core/Unit/Stub/ExceptionInterceptor.php similarity index 100% rename from src/Hmvc/tests/Unit/Stub/ExceptionInterceptor.php rename to src/Hmvc/tests/Core/Unit/Stub/ExceptionInterceptor.php diff --git a/src/Hmvc/tests/Unit/Stub/Legacy/LegacyChangerInterceptor.php b/src/Hmvc/tests/Core/Unit/Stub/Legacy/LegacyChangerInterceptor.php similarity index 100% rename from src/Hmvc/tests/Unit/Stub/Legacy/LegacyChangerInterceptor.php rename to src/Hmvc/tests/Core/Unit/Stub/Legacy/LegacyChangerInterceptor.php diff --git a/src/Hmvc/tests/Unit/Stub/Legacy/LegacyStatefulInterceptor.php b/src/Hmvc/tests/Core/Unit/Stub/Legacy/LegacyStatefulInterceptor.php similarity index 100% rename from src/Hmvc/tests/Unit/Stub/Legacy/LegacyStatefulInterceptor.php rename to src/Hmvc/tests/Core/Unit/Stub/Legacy/LegacyStatefulInterceptor.php diff --git a/src/Hmvc/tests/Core/Unit/Stub/MultipleCallNextInterceptor.php b/src/Hmvc/tests/Core/Unit/Stub/MultipleCallNextInterceptor.php new file mode 100644 index 000000000..c70bd1ff4 --- /dev/null +++ b/src/Hmvc/tests/Core/Unit/Stub/MultipleCallNextInterceptor.php @@ -0,0 +1,29 @@ +result = []; + for ($i = 0; $i < $this->counter; ++$i) { + $this->result[] = $handler->handle($context); + } + + return $this->result; + } +} diff --git a/src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php b/src/Hmvc/tests/Core/Unit/Stub/StatefulInterceptor.php similarity index 100% rename from src/Hmvc/tests/Unit/Stub/StatefulInterceptor.php rename to src/Hmvc/tests/Core/Unit/Stub/StatefulInterceptor.php diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php new file mode 100644 index 000000000..47f6be9aa --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php @@ -0,0 +1,112 @@ +createPathContext(['test', 'test2']); + $interceptor = new class implements InterceptorInterface { + public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed + { + return null; + } + }; + $dispatcher = $this->createMock(EventDispatcherInterface::class); + $dispatcher + ->expects(self::once()) + ->method('dispatch') + ->with( + new \Spiral\Interceptors\Event\InterceptorCalling( + $context, + $interceptor, + ) + ); + $pipeline = $this->createPipeline(interceptors: [$interceptor], dispatcher: $dispatcher); + + $pipeline->withHandler( + new class implements HandlerInterface { + public function handle(CallContext $context): mixed + { + return null; + } + } + )->handle($context, []); + } + + public function testCallActionWithoutCore(): void + { + $pipeline = $this->createPipeline(); + + self::expectExceptionMessage('Unable to invoke pipeline without last handler.'); + + $pipeline->handle($this->createPathContext(['controller', 'action'])); + } + + public function testHandleWithoutHandler(): void + { + $pipeline = $this->createPipeline(); + + self::expectExceptionMessage('Unable to invoke pipeline without last handler.'); + + $pipeline->handle(new CallContext(Target::fromPathArray(['controller', 'action']))); + } + + /** + * Multiple call of same the handler inside the pipeline must invoke the same interceptor. + */ + public function testCallHandlerTwice(): void + { + $mock = self::createMock(InterceptorInterface::class); + $mock->expects(self::exactly(2)) + ->method('intercept') + ->willReturn('foo', 'bar'); + + $pipeline = $this->createPipeline([ + new MultipleCallNextInterceptor(2), + $mock, + new ExceptionInterceptor(), + ], self::createMock(HandlerInterface::class)); + + $result = $pipeline->handle($this->createPathContext(['controller', 'action'])); + self::assertSame(['foo', 'bar'], $result); + } + + /** + * @param array $interceptors + */ + private function createPipeline( + array $interceptors = [], + HandlerInterface|null $lastHandler = null, + EventDispatcherInterface|null $dispatcher = null, + ): InterceptorPipeline { + $pipeline = new InterceptorPipeline($dispatcher); + + $lastHandler instanceof HandlerInterface and $pipeline = $pipeline->withHandler($lastHandler); + + foreach ($interceptors as $interceptor) { + $pipeline->addInterceptor($interceptor); + } + + return $pipeline; + } + + public function createPathContext(array $path = []): CallContext + { + return new CallContext(Target::fromPathArray($path)); + } +} diff --git a/src/Hmvc/tests/Interceptors/Unit/Stub/AddAttributeInterceptor.php b/src/Hmvc/tests/Interceptors/Unit/Stub/AddAttributeInterceptor.php new file mode 100644 index 000000000..943e35a52 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Stub/AddAttributeInterceptor.php @@ -0,0 +1,23 @@ +handle($context->withAttribute($this->attribute, $this->value)); + } +} diff --git a/src/Hmvc/tests/Interceptors/Unit/Stub/ExceptionInterceptor.php b/src/Hmvc/tests/Interceptors/Unit/Stub/ExceptionInterceptor.php new file mode 100644 index 000000000..c8aa2e6f1 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Stub/ExceptionInterceptor.php @@ -0,0 +1,17 @@ +result = []; + for ($i = 0; $i < $this->counter; ++$i) { + $this->result[] = $handler->handle($context); + } + + return $this->result; + } +} diff --git a/src/Hmvc/tests/Interceptors/Unit/Stub/StatefulInterceptor.php b/src/Hmvc/tests/Interceptors/Unit/Stub/StatefulInterceptor.php new file mode 100644 index 000000000..5e75a1010 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Stub/StatefulInterceptor.php @@ -0,0 +1,23 @@ +context = $context; + $this->next = $handler; + return $this->result = $handler->handle($context); + } +} From 0d9dc72e3ea8520cafee67a6b439bae7091c3ef0 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 23 Apr 2024 15:29:56 +0400 Subject: [PATCH 65/84] Update phpunit config; add tests for AttributedTrait --- src/Hmvc/phpunit.xml | 42 ++++++------- src/Hmvc/src/AnInterceptor.php | 0 .../Unit/Context/AttributedTraitTest.php | 62 +++++++++++++++++++ .../Interceptors/Unit/Stub/AttributedStub.php | 13 ++++ 4 files changed, 96 insertions(+), 21 deletions(-) create mode 100644 src/Hmvc/src/AnInterceptor.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Context/AttributedTraitTest.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/AttributedStub.php diff --git a/src/Hmvc/phpunit.xml b/src/Hmvc/phpunit.xml index fbd70dd46..765a2e98c 100644 --- a/src/Hmvc/phpunit.xml +++ b/src/Hmvc/phpunit.xml @@ -1,28 +1,28 @@ - - - - tests - - - - - src - - - - - - + stderr="true" + cacheDirectory=".phpunit.cache" + backupStaticProperties="false" +> + + + tests + + + + + + + + + src + + diff --git a/src/Hmvc/src/AnInterceptor.php b/src/Hmvc/src/AnInterceptor.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/AttributedTraitTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/AttributedTraitTest.php new file mode 100644 index 000000000..e2d5dbe80 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Context/AttributedTraitTest.php @@ -0,0 +1,62 @@ +withAttribute('key', 'value'); + + self::assertSame('value', $dto->getAttribute('key')); + self::assertNull($dto->getAttribute('non-exist-key')); + // default value + self::assertSame(42, $dto->getAttribute('non-exist-key', 42)); + } + + public function testWithAttribute(): void + { + $dto = new AttributedStub(); + + $new = $dto->withAttribute('key', 'value'); + + self::assertSame('value', $new->getAttribute('key')); + // Immutability + self::assertNotSame($dto, $new); + self::assertNotSame('value', $dto->getAttribute('key')); + } + + public function testWithAttributes(): void + { + $dto = new AttributedStub(); + + $new = $dto + ->withAttribute('key', 'value') + ->withAttribute('key2', 'value2'); + + self::assertSame([ + 'key' => 'value', + 'key2' => 'value2', + ], $new->getAttributes()); + } + + public function testWithoutAttributes(): void + { + $dto = (new AttributedStub()) + ->withAttribute('key', 'value') + ->withAttribute('key2', 'value2'); + + $new = $dto->withoutAttribute('key'); + + self::assertNull($new->getAttribute('key')); + self::assertSame([ + 'key2' => 'value2', + ], $new->getAttributes()); + } +} diff --git a/src/Hmvc/tests/Interceptors/Unit/Stub/AttributedStub.php b/src/Hmvc/tests/Interceptors/Unit/Stub/AttributedStub.php new file mode 100644 index 000000000..b8fcac9b9 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Stub/AttributedStub.php @@ -0,0 +1,13 @@ + Date: Tue, 23 Apr 2024 16:21:28 +0400 Subject: [PATCH 66/84] Add tests for Target --- .../Interceptors/Unit/Context/TargetTest.php | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php new file mode 100644 index 000000000..5042db617 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -0,0 +1,111 @@ +getReflection()); + self::assertSame('print_r', (string)$target); + } + + public function testCreateFromReflectionMethod(): void + { + $reflection = new \ReflectionMethod($this, __FUNCTION__); + + $target = Target::fromReflection($reflection); + + self::assertSame($reflection, $target->getReflection()); + self::assertSame(__FUNCTION__, (string)$target); + } + + public function testWithReflectionFunction(): void + { + $reflection = new \ReflectionFunction('print_r'); + + $target = Target::fromPathArray(['foo', 'bar']); + $target2 = $target->withReflection($reflection); + + // Immutability + self::assertNotSame($target, $target2); + // First target is not changed + self::assertSame(['foo', 'bar'], $target->getPath()); + self::assertNull($target->getReflection()); + self::assertSame('foo.bar', (string)$target); + // Second target is changed + self::assertSame(['foo', 'bar'], $target2->getPath()); + self::assertSame($reflection, $target2->getReflection()); + // Reflection does'n affect the string representation if path is set + self::assertSame('foo.bar', (string)$target); + } + + public function testCreateFromPathStringWithPath(): void + { + $str = 'foo.bar.baz'; + $target = Target::fromPathString($str); + $target2 = $target->withPath(['bar', 'baz']); + + // Immutability + self::assertNotSame($target, $target2); + self::assertSame(['bar', 'baz'], $target2->getPath()); + self::assertSame('bar.baz', (string)$target2); + // First target is not changed + self::assertSame(['foo', 'bar', 'baz'], $target->getPath()); + self::assertSame($str, (string)$target); + } + + public static function providePathChunks(): iterable + { + yield [['Foo', 'Bar', 'baz'], '.']; + yield [['Foo', 'Bar', 'baz', 'fiz.baz'], '/']; + yield [['Foo'], ' ']; + yield [['Foo', '', ''], '-']; + } + + #[DataProvider('providePathChunks')] + public function testCreateFromPathString(array $chunks, string $separator): void + { + $str = \implode($separator, $chunks); + $target = Target::fromPathString($str, $separator); + + self::assertSame($chunks, $target->getPath()); + self::assertSame($str, (string)$target); + } + + #[DataProvider('providePathChunks')] + public function testCreateFromPathArray(array $chunks, string $separator): void + { + $str = \implode($separator, $chunks); + $target = Target::fromPathArray($chunks, $separator); + + self::assertSame($chunks, $target->getPath()); + self::assertSame($str, (string)$target); + } + + public function testCreateFromPathStringDefaultSeparator(): void + { + $str = 'foo.bar.baz'; + $target = Target::fromPathString($str); + + self::assertSame(['foo', 'bar', 'baz'], $target->getPath()); + self::assertSame($str, (string)$target); + } + + public function testPrivateConstructor(): void + { + $this->expectException(\Error::class); + + new \Spiral\Interceptors\Context\Target(); + } +} From d4fd254ac69cf04fbab0d4cc08dcbe9e71362ac0 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 23 Apr 2024 18:56:39 +0400 Subject: [PATCH 67/84] Add tests for ReflectionHandler --- .../Interceptors/Context/TargetInterface.php | 3 + .../Handler/ReflectionHandler.php | 2 +- .../Unit/Handler/InterceptorPipelineTest.php | 15 ++ .../Unit/Handler/ReflectionHandlerTest.php | 135 ++++++++++++++++++ .../Interceptors/Unit/Stub/TestService.php | 15 ++ 5 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/TestService.php diff --git a/src/Hmvc/src/Interceptors/Context/TargetInterface.php b/src/Hmvc/src/Interceptors/Context/TargetInterface.php index eafea391f..c2dfd16d9 100644 --- a/src/Hmvc/src/Interceptors/Context/TargetInterface.php +++ b/src/Hmvc/src/Interceptors/Context/TargetInterface.php @@ -22,6 +22,9 @@ public function getPath(): array; */ public function withPath(array $path): static; + /** + * @psalm-pure + */ public function getReflection(): ?\ReflectionFunctionAbstract; /** diff --git a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php index b01181d72..38d23d85e 100644 --- a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php +++ b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php @@ -53,7 +53,7 @@ public function handle(CallContext $context): mixed } if ($method instanceof \ReflectionFunction) { - $method->invokeArgs( + return $method->invokeArgs( $this->resolveArguments($method, $context) ); } diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php index 47f6be9aa..5ee31109a 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/InterceptorPipelineTest.php @@ -66,6 +66,21 @@ public function testHandleWithoutHandler(): void $pipeline->handle(new CallContext(Target::fromPathArray(['controller', 'action']))); } + public function testHandleWithHandler(): void + { + $ctx = new CallContext(Target::fromPathArray(['controller', 'action'])); + $mock = self::createMock(HandlerInterface::class); + $mock->expects(self::exactly(2)) + ->method('handle') + ->with($ctx) + ->willReturn('test1', 'test2'); + $pipeline = $this->createPipeline([new MultipleCallNextInterceptor(2)], $mock); + + $result = $pipeline->handle($ctx); + + self::assertSame(['test1', 'test2'], $result); + } + /** * Multiple call of same the handler inside the pipeline must invoke the same interceptor. */ diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php new file mode 100644 index 000000000..b5424217f --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php @@ -0,0 +1,135 @@ +expects(self::once()) + ->method('get') + ->with(ResolverInterface::class) + ->willReturn($c); + $handler = new ReflectionHandler($container, false); + // Call Context + $ctx = new CallContext(Target::fromReflection(new \ReflectionFunction('strtoupper'))); + $ctx = $ctx->withArguments(['hello']); + + $result = $handler->handle($ctx); + + self::assertSame('HELLO', $result); + } + + public function testHandleWrongReflectionFunction(): void + { + $handler = $this->createHandler(); + // Call Context + $ctx = new CallContext(Target::fromReflection(new class extends \ReflectionFunctionAbstract { + /** @psalm-immutable */ + public function getName(): string + { + return 'testReflection'; + } + + public function __toString(): string + { + return 'really?'; + } + + public static function export(): void + { + // do nothing + } + })); + + self::expectException(TargetCallException::class); + self::expectExceptionMessageMatches('/Action not found for target `testReflection`/'); + + $handler->handle($ctx); + } + + public function testWithoutResolvingFromPathAndReflection(): void + { + $container = self::createMock(ContainerInterface::class); + + $handler = new ReflectionHandler($container, false); + + self::expectException(TargetCallException::class); + self::expectExceptionMessageMatches('/Reflection not provided for target/'); + + $handler->handle(new CallContext(Target::fromPathString('foo'))); + } + + public function testWithoutReflectionWithResolvingFromPathWithIncorrectPath(): void + { + $handler = $this->createHandler(); + + self::expectException(TargetCallException::class); + self::expectExceptionMessageMatches('/Invalid target path to resolve reflection/'); + + $handler->handle(new CallContext(Target::fromPathArray(['foo', 'bar', 'baz']))); + } + + public function testWithoutReflectionWithResolvingFromPathWithWrongPath(): void + { + $handler = $this->createHandler(); + + self::expectException(TargetCallException::class); + self::expectExceptionMessageMatches('/Invalid action/'); + + $handler->handle(new CallContext(Target::fromPathArray([TestService::class, 'nonExistingMethod']))); + } + + public function testWithoutReflectionWithResolvingFromPath(): void + { + $handler = $this->createHandler([ + TestService::class => $service = new TestService(), + ]); + + self::assertSame(0, $service->counter); + + $handler->handle(new CallContext(Target::fromPathArray([TestService::class, 'increment']))); + self::assertSame(1, $service->counter); + } + + public function testUsingResolver(): void + { + $handler = $this->createHandler(); + $ctx = new CallContext( + Target::fromReflection(new \ReflectionFunction(fn (string $value):string => \strtoupper($value))) + ); + $ctx = $ctx->withArguments(['word' => 'world!', 'value' => 'hello']); + + $result = $handler->handle($ctx); + + self::assertSame('HELLO', $result); + } + + public function createHandler(array $definitions = [], bool $resolveFromPath = true): ReflectionHandler + { + $container = new Container(); + foreach ($definitions as $id => $definition) { + $container->bind($id, $definition); + } + + return new ReflectionHandler( + $container, + $resolveFromPath, + ); + } +} diff --git a/src/Hmvc/tests/Interceptors/Unit/Stub/TestService.php b/src/Hmvc/tests/Interceptors/Unit/Stub/TestService.php new file mode 100644 index 000000000..624ce7862 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Stub/TestService.php @@ -0,0 +1,15 @@ +counter; + } +} From 830d2b622f3e58c3abec977d78094b429fd19ec7 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 23 Apr 2024 19:16:37 +0400 Subject: [PATCH 68/84] Add tests for ActionResolver; cleanup; fix psalm issue in InterceptorPipeline --- .../Handler/InterceptorPipeline.php | 2 +- .../Interceptors/Internal/ActionResolver.php | 35 ----------- .../Unit/Internal/ActionResolverTest.php | 61 +++++++++++++++++++ .../Interceptors/Unit/Stub/TestService.php | 10 +++ 4 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 src/Hmvc/tests/Interceptors/Unit/Internal/ActionResolverTest.php diff --git a/src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php b/src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php index a47f55fec..81f208abe 100644 --- a/src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php +++ b/src/Hmvc/src/Interceptors/Handler/InterceptorPipeline.php @@ -20,7 +20,7 @@ final class InterceptorPipeline implements HandlerInterface { private ?HandlerInterface $handler = null; - /** @var InterceptorInterface */ + /** @var list */ private array $interceptors = []; private int $position = 0; diff --git a/src/Hmvc/src/Interceptors/Internal/ActionResolver.php b/src/Hmvc/src/Interceptors/Internal/ActionResolver.php index 7fb59adf2..127408629 100644 --- a/src/Hmvc/src/Interceptors/Internal/ActionResolver.php +++ b/src/Hmvc/src/Interceptors/Internal/ActionResolver.php @@ -4,10 +4,6 @@ namespace Spiral\Interceptors\Internal; -use Psr\Container\ContainerExceptionInterface; -use Spiral\Core\Exception\Resolver\ArgumentResolvingException; -use Spiral\Core\Exception\Resolver\InvalidArgumentException; -use Spiral\Core\ResolverInterface; use Spiral\Interceptors\Exception\TargetCallException; /** @@ -69,35 +65,4 @@ public static function validateControllerMethod(\ReflectionMethod $method, mixed ); } } - - /** - * @throws TargetCallException - * @throws \Throwable - */ - public static function resolveArguments( - ResolverInterface $resolver, - \ReflectionMethod $method, - array $arguments, - ): array { - try { - return $resolver->resolveArguments($method, $arguments); - } catch (ArgumentResolvingException|InvalidArgumentException $e) { - throw new TargetCallException( - \sprintf( - 'Missing/invalid parameter %s of `%s`->`%s`.', - $e->getParameter(), - $method->getDeclaringClass()->getName(), - $method->getName(), - ), - TargetCallException::BAD_ARGUMENT, - $e, - ); - } catch (ContainerExceptionInterface $e) { - throw new TargetCallException( - $e->getMessage(), - TargetCallException::ERROR, - $e, - ); - } - } } diff --git a/src/Hmvc/tests/Interceptors/Unit/Internal/ActionResolverTest.php b/src/Hmvc/tests/Interceptors/Unit/Internal/ActionResolverTest.php new file mode 100644 index 000000000..762b8edeb --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Internal/ActionResolverTest.php @@ -0,0 +1,61 @@ +counter; } + + public static function toUpperCase(string $value): string + { + return \strtoupper($value); + } + + protected function toLowerCase(string $value): string + { + return \strtolower($value); + } } From 394443efbbf15ba99eb664910976ed163e0befad Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 23 Apr 2024 19:21:12 +0400 Subject: [PATCH 69/84] Fix psalm issues --- src/Hmvc/src/Interceptors/Exception/InterceptorException.php | 2 +- src/Hmvc/src/Interceptors/Exception/TargetCallException.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Hmvc/src/Interceptors/Exception/InterceptorException.php b/src/Hmvc/src/Interceptors/Exception/InterceptorException.php index 27bbc1452..38a476aff 100644 --- a/src/Hmvc/src/Interceptors/Exception/InterceptorException.php +++ b/src/Hmvc/src/Interceptors/Exception/InterceptorException.php @@ -8,4 +8,4 @@ class InterceptorException extends \RuntimeException { } -\class_alias(InterceptorException::class, 'Spiral\Core\Exception\InterceptorException'); +\class_alias(InterceptorException::class, \Spiral\Core\Exception\InterceptorException::class); diff --git a/src/Hmvc/src/Interceptors/Exception/TargetCallException.php b/src/Hmvc/src/Interceptors/Exception/TargetCallException.php index f9d309935..a0172ddf8 100644 --- a/src/Hmvc/src/Interceptors/Exception/TargetCallException.php +++ b/src/Hmvc/src/Interceptors/Exception/TargetCallException.php @@ -4,6 +4,8 @@ namespace Spiral\Interceptors\Exception; +use Spiral\Core\Exception\ControllerException; + /** * Unable to perform user action or find controller. */ @@ -21,4 +23,4 @@ class TargetCallException extends \RuntimeException public const INVALID_CONTROLLER = 16; } -\class_alias(TargetCallException::class, 'Spiral\Core\Exception\ControllerException'); +\class_alias(TargetCallException::class, ControllerException::class); From 2f0d4f86b38f1de2b81571a8b71b519962fac1cd Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 25 Apr 2024 00:53:16 +0400 Subject: [PATCH 70/84] Integrate new interceptors part 1 --- src/Console/src/Command.php | 31 +++++--- src/Console/src/Config/ConsoleConfig.php | 3 +- src/Console/src/Traits/LazyTrait.php | 4 +- .../src/Bootloader/EventsBootloader.php | 33 +++++--- src/Events/src/Config/EventsConfig.php | 5 +- src/Filters/src/Config/FiltersConfig.php | 5 ++ src/Filters/src/Model/FilterProvider.php | 3 +- src/Filters/src/Model/Interceptor/Core.php | 12 ++- src/Framework/Bootloader/DomainBootloader.php | 15 ++-- .../Bootloader/Security/FiltersBootloader.php | 9 ++- src/Framework/Console/CommandLocator.php | 5 ++ src/Hmvc/src/AnInterceptor.php | 0 src/Hmvc/src/Core/AbstractCore.php | 77 +++++++++++-------- src/Hmvc/src/Interceptors/Context/Target.php | 13 +++- src/Queue/src/Bootloader/QueueBootloader.php | 16 ++-- src/Queue/src/Config/QueueConfig.php | 12 ++- src/Queue/src/Interceptor/Consume/Core.php | 14 +++- src/Queue/src/Interceptor/Consume/Handler.php | 25 ++++-- src/Queue/src/Interceptor/Push/Core.php | 14 +++- src/Queue/src/Queue.php | 16 +++- src/Queue/src/QueueManager.php | 17 ++-- .../Bootloader/DomainBootloaderTest.php | 46 +++++------ tests/app/src/Bootloader/RoutesBootloader.php | 10 +-- 23 files changed, 252 insertions(+), 133 deletions(-) delete mode 100644 src/Hmvc/src/AnInterceptor.php diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 110ad53d9..7a70ff0dd 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -19,8 +19,12 @@ use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\Exception\ScopeException; -use Spiral\Core\InterceptableCore; +use Spiral\Core\InterceptorPipeline; use Spiral\Events\EventDispatcherAwareInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -47,18 +51,18 @@ abstract class Command extends SymfonyCommand implements EventDispatcherAwareInt protected ?ContainerInterface $container = null; protected ?EventDispatcherInterface $eventDispatcher = null; - /** @var array> */ + /** @var array> */ protected array $interceptors = []; - /** {@internal} */ + /** @internal */ public function setContainer(ContainerInterface $container): void { $this->container = $container; } /** - * {@internal} - * @param array> $interceptors + * @internal + * @param array> $interceptors */ public function setInterceptors(array $interceptors): void { @@ -88,12 +92,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, $this->output)); + $arguments = ['input' => $this->input, 'output' => $this->output, 'command' => $this]; + // Executing perform method with method injection - $code = (int)$core->callAction(static::class, $method, [ - 'input' => $this->input, - 'output' => $this->output, - 'command' => $this, - ]); + $code = $core instanceof HandlerInterface + ? (int)$core->handle(new CallContext( + Target::fromReflection(new \ReflectionMethod(static::class, $method)), + $arguments, + )) + : (int)$core->callAction(static::class, $method, $arguments); $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, $this->output)); @@ -103,11 +110,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - protected function buildCore(): CoreInterface + protected function buildCore(): CoreInterface|HandlerInterface { $core = $this->container->get(CommandCore::class); - $interceptableCore = new InterceptableCore($core, $this->eventDispatcher); + $interceptableCore = (new InterceptorPipeline($this->eventDispatcher))->withCore($core); foreach ($this->interceptors as $interceptor) { $interceptableCore->addInterceptor($this->container->get($interceptor)); diff --git a/src/Console/src/Config/ConsoleConfig.php b/src/Console/src/Config/ConsoleConfig.php index 9e1963b22..6a842c383 100644 --- a/src/Console/src/Config/ConsoleConfig.php +++ b/src/Console/src/Config/ConsoleConfig.php @@ -10,6 +10,7 @@ use Spiral\Console\SequenceInterface; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\InjectableConfig; +use Spiral\Interceptors\InterceptorInterface; final class ConsoleConfig extends InjectableConfig { @@ -34,7 +35,7 @@ public function getVersion(): string } /** - * @return array> + * @return array> */ public function getInterceptors(): array { diff --git a/src/Console/src/Traits/LazyTrait.php b/src/Console/src/Traits/LazyTrait.php index 3f5f93dbc..873ae9d42 100644 --- a/src/Console/src/Traits/LazyTrait.php +++ b/src/Console/src/Traits/LazyTrait.php @@ -7,14 +7,16 @@ use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Console\Command as SpiralCommand; -use Spiral\Console\Config\ConsoleConfig; +use Spiral\Core\CoreInterceptorInterface; use Spiral\Events\EventDispatcherAwareInterface; +use Spiral\Interceptors\InterceptorInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Command\LazyCommand; trait LazyTrait { private ContainerInterface $container; + /** @var array> */ private array $interceptors = []; private ?EventDispatcherInterface $dispatcher = null; diff --git a/src/Events/src/Bootloader/EventsBootloader.php b/src/Events/src/Bootloader/EventsBootloader.php index bfaaccec1..665059cb9 100644 --- a/src/Events/src/Bootloader/EventsBootloader.php +++ b/src/Events/src/Bootloader/EventsBootloader.php @@ -4,6 +4,7 @@ namespace Spiral\Events\Bootloader; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Boot\AbstractKernel; @@ -16,7 +17,7 @@ use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\FactoryInterface; -use Spiral\Core\InterceptableCore; +use Spiral\Core\InterceptorPipeline; use Spiral\Events\AutowireListenerFactory; use Spiral\Events\Config\EventsConfig; use Spiral\Events\EventDispatcher; @@ -27,6 +28,7 @@ use Spiral\Events\Processor\AttributeProcessor; use Spiral\Events\Processor\ConfigProcessor; use Spiral\Events\Processor\ProcessorInterface; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader; /** @@ -104,27 +106,34 @@ private function initEventDispatcher( Container $container, FactoryInterface $factory ): void { - $core = new InterceptableCore($core); + $pipeline = (new InterceptorPipeline())->withCore($core); foreach ($config->getInterceptors() as $interceptor) { $interceptor = $this->autowire($interceptor, $container, $factory); - \assert($interceptor instanceof CoreInterceptorInterface); - $core->addInterceptor($interceptor); + \assert($interceptor instanceof CoreInterceptorInterface || $interceptor instanceof InterceptorInterface); + $pipeline->addInterceptor($interceptor); } $container->removeBinding(EventDispatcherInterface::class); - $container->bindSingleton(EventDispatcherInterface::class, new EventDispatcher($core)); + $container->bindSingleton(EventDispatcherInterface::class, new EventDispatcher($pipeline)); } + /** + * @template T + * + * @param class-string|Autowire|T $id + * + * @return T + * + * @throws ContainerExceptionInterface + */ private function autowire(string|object $id, ContainerInterface $container, FactoryInterface $factory): object { - if (\is_string($id)) { - $id = $container->get($id); - } elseif ($id instanceof Autowire) { - $id = $id->resolve($factory); - } - - return $id; + return match (true) { + \is_string($id) => $container->get($id), + $id instanceof Autowire => $id->resolve($factory), + default => $id, + }; } } diff --git a/src/Events/src/Config/EventsConfig.php b/src/Events/src/Config/EventsConfig.php index 897fcbcce..0e196f21c 100644 --- a/src/Events/src/Config/EventsConfig.php +++ b/src/Events/src/Config/EventsConfig.php @@ -8,11 +8,14 @@ use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\InjectableConfig; use Spiral\Events\Processor\ProcessorInterface; +use Spiral\Interceptors\InterceptorInterface; /** * @psalm-type TProcessor = ProcessorInterface|class-string|Autowire * @psalm-type TListener = class-string|EventListener - * @psalm-type TInterceptor = class-string|CoreInterceptorInterface|Autowire + * @psalm-type TLegacyInterceptor = class-string|CoreInterceptorInterface|Autowire + * @psalm-type TNewInterceptor = class-string|InterceptorInterface|Autowire + * @psalm-type TInterceptor = TLegacyInterceptor|TNewInterceptor * @property array{ * processors: TProcessor[], * listeners: array, diff --git a/src/Filters/src/Config/FiltersConfig.php b/src/Filters/src/Config/FiltersConfig.php index 82f724ec3..5155385e5 100644 --- a/src/Filters/src/Config/FiltersConfig.php +++ b/src/Filters/src/Config/FiltersConfig.php @@ -4,7 +4,9 @@ namespace Spiral\Filters\Config; +use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\InjectableConfig; +use Spiral\Interceptors\InterceptorInterface; final class FiltersConfig extends InjectableConfig { @@ -14,6 +16,9 @@ final class FiltersConfig extends InjectableConfig 'interceptors' => [], ]; + /** + * @return array> + */ public function getInterceptors(): array { return (array)($this->config['interceptors'] ?? []); diff --git a/src/Filters/src/Model/FilterProvider.php b/src/Filters/src/Model/FilterProvider.php index 65022e9e1..faea9e8ce 100644 --- a/src/Filters/src/Model/FilterProvider.php +++ b/src/Filters/src/Model/FilterProvider.php @@ -11,6 +11,7 @@ use Spiral\Filters\Model\Schema\Builder; use Spiral\Filters\Model\Schema\InputMapper; use Spiral\Filters\InputInterface; +use Spiral\Interceptors\HandlerInterface; use Spiral\Models\SchematicEntity; /** @@ -22,7 +23,7 @@ final class FilterProvider implements FilterProviderInterface public function __construct( private readonly ContainerInterface $container, private readonly ResolverInterface $resolver, - private readonly CoreInterface $core + private readonly CoreInterface|HandlerInterface $core ) { } diff --git a/src/Filters/src/Model/Interceptor/Core.php b/src/Filters/src/Model/Interceptor/Core.php index 8ce26fab1..61afa6219 100644 --- a/src/Filters/src/Model/Interceptor/Core.php +++ b/src/Filters/src/Model/Interceptor/Core.php @@ -7,11 +7,13 @@ use Spiral\Core\CoreInterface; use Spiral\Filters\Model\FilterBag; use Spiral\Filters\Model\FilterInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\HandlerInterface; /** * @psalm-type TParameters = array{filterBag: FilterBag} */ -final class Core implements CoreInterface +final class Core implements CoreInterface, HandlerInterface { /** * @param-assert TParameters $parameters @@ -22,4 +24,12 @@ public function callAction(string $controller, string $action, array $parameters return $parameters['filterBag']->filter; } + + public function handle(CallContext $context): FilterInterface + { + $args = $context->getArguments(); + \assert($args['filterBag'] instanceof FilterBag); + + return $args['filterBag']->filter; + } } diff --git a/src/Framework/Bootloader/DomainBootloader.php b/src/Framework/Bootloader/DomainBootloader.php index e6bdeff0c..5927c94b6 100644 --- a/src/Framework/Bootloader/DomainBootloader.php +++ b/src/Framework/Bootloader/DomainBootloader.php @@ -9,7 +9,9 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Core\Core; use Spiral\Core\CoreInterceptorInterface; -use Spiral\Core\InterceptableCore; +use Spiral\Core\CoreInterface; +use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\InterceptorInterface; /** * Configures global domain core (CoreInterface) with the set of interceptors to alter domain layer functionality. @@ -25,17 +27,18 @@ protected static function domainCore( Core $core, ContainerInterface $container, ?EventDispatcherInterface $dispatcher = null - ): InterceptableCore { - $interceptableCore = new InterceptableCore($core, $dispatcher); + ): CoreInterface { + $pipeline = (new InterceptorPipeline($dispatcher))->withCore($core); foreach (static::defineInterceptors() as $interceptor) { - if (!$interceptor instanceof CoreInterceptorInterface) { + if (!$interceptor instanceof CoreInterceptorInterface && !$interceptor instanceof InterceptorInterface) { $interceptor = $container->get($interceptor); } - $interceptableCore->addInterceptor($interceptor); + + $pipeline->addInterceptor($interceptor); } - return $interceptableCore; + return $pipeline; } /** diff --git a/src/Framework/Bootloader/Security/FiltersBootloader.php b/src/Framework/Bootloader/Security/FiltersBootloader.php index 915b2556c..df19643f7 100644 --- a/src/Framework/Bootloader/Security/FiltersBootloader.php +++ b/src/Framework/Bootloader/Security/FiltersBootloader.php @@ -13,7 +13,7 @@ use Spiral\Core\BinderInterface; use Spiral\Core\Container; use Spiral\Core\CoreInterceptorInterface; -use Spiral\Core\InterceptableCore; +use Spiral\Core\InterceptorPipeline; use Spiral\Filter\InputScope; use Spiral\Filters\Config\FiltersConfig; use Spiral\Filters\Model\FilterBag; @@ -94,12 +94,13 @@ private function initFilterProvider( FiltersConfig $config, ?EventDispatcherInterface $dispatcher = null ): FilterProvider { - $core = new InterceptableCore(new Core(), $dispatcher); + $pipeline = (new InterceptorPipeline($dispatcher))->withHandler(new Core()); + foreach ($config->getInterceptors() as $interceptor) { - $core->addInterceptor($container->get($interceptor)); + $pipeline->addInterceptor($container->get($interceptor)); } - return new FilterProvider($container, $container, $core); + return new FilterProvider($container, $container, $pipeline); } private function initCasterRegistry(): CasterRegistryInterface diff --git a/src/Framework/Console/CommandLocator.php b/src/Framework/Console/CommandLocator.php index d74c96d3c..2c2130e3d 100644 --- a/src/Framework/Console/CommandLocator.php +++ b/src/Framework/Console/CommandLocator.php @@ -6,6 +6,8 @@ use Psr\Container\ContainerInterface; use Spiral\Console\Traits\LazyTrait; +use Spiral\Core\CoreInterceptorInterface; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Tokenizer\ScopedClassesInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; @@ -13,6 +15,9 @@ final class CommandLocator implements LocatorInterface { use LazyTrait; + /** + * @param array> $interceptors + */ public function __construct( private readonly ScopedClassesInterface $classes, ContainerInterface $container, diff --git a/src/Hmvc/src/AnInterceptor.php b/src/Hmvc/src/AnInterceptor.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Hmvc/src/Core/AbstractCore.php b/src/Hmvc/src/Core/AbstractCore.php index d6900c70e..b4a0977d0 100644 --- a/src/Hmvc/src/Core/AbstractCore.php +++ b/src/Hmvc/src/Core/AbstractCore.php @@ -9,6 +9,8 @@ use Spiral\Core\Exception\ControllerException; use Spiral\Core\Exception\Resolver\ArgumentResolvingException; use Spiral\Core\Exception\Resolver\InvalidArgumentException; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\HandlerInterface; use Spiral\Interceptors\Internal\ActionResolver; /** @@ -18,7 +20,7 @@ * * @deprecated will be removed in Spiral v4.0 */ -abstract class AbstractCore implements CoreInterface +abstract class AbstractCore implements CoreInterface, HandlerInterface { /** @internal */ protected ResolverInterface $resolver; @@ -41,22 +43,53 @@ public function callAction(string $controller, string $action, array $parameters $method = ActionResolver::pathToReflection($controller, $action); // Validate method - if ($method->isStatic() || !$method->isPublic()) { + ActionResolver::validateControllerMethod($method); + + return $this->invoke($method, $parameters); + } + + public function handle(CallContext $context): mixed + { + $target = $context->getTarget(); + $reflection = $target->getReflection(); + return $reflection instanceof \ReflectionMethod + ? $this->invoke($reflection, $context->getArguments()) + : $this->callAction($target->getPath()[0], $target->getPath()[1], $context->getArguments()); + } + + protected function resolveArguments(\ReflectionMethod $method, array $parameters): array + { + foreach ($method->getParameters() as $parameter) { + $name = $parameter->getName(); + if ( + \array_key_exists($name, $parameters) && + $parameters[$name] === null && + $parameter->isDefaultValueAvailable() + ) { + /** @psalm-suppress MixedAssignment */ + $parameters[$name] = $parameter->getDefaultValue(); + } + } + + // getting the set of arguments should be sent to requested method + return $this->resolver->resolveArguments($method, $parameters); + } + + /** + * @throws \Throwable + */ + private function invoke(\ReflectionMethod $method, array $arguments): mixed + { + try { + $args = $this->resolveArguments($method, $arguments); + } catch (ArgumentResolvingException | InvalidArgumentException $e) { throw new ControllerException( \sprintf( - 'Invalid action `%s`->`%s`', + 'Missing/invalid parameter %s of `%s`->`%s`', + $e->getParameter(), $method->getDeclaringClass()->getName(), $method->getName(), ), - ControllerException::BAD_ACTION - ); - } - - try { - $args = $this->resolveArguments($method, $parameters); - } catch (ArgumentResolvingException|InvalidArgumentException $e) { - throw new ControllerException( - \sprintf('Missing/invalid parameter %s of `%s`->`%s`', $e->getParameter(), $controller, $action), ControllerException::BAD_ARGUMENT, $e, ); @@ -72,25 +105,7 @@ public function callAction(string $controller, string $action, array $parameters return ContainerScope::runScope( $container, /** @psalm-suppress MixedArgument */ - static fn (): mixed => $method->invokeArgs($container->get($controller), $args) + static fn (): mixed => $method->invokeArgs($container->get($method->getDeclaringClass()->getName()), $args) ); } - - protected function resolveArguments(\ReflectionMethod $method, array $parameters): array - { - foreach ($method->getParameters() as $parameter) { - $name = $parameter->getName(); - if ( - \array_key_exists($name, $parameters) && - $parameters[$name] === null && - $parameter->isDefaultValueAvailable() - ) { - /** @psalm-suppress MixedAssignment */ - $parameters[$name] = $parameter->getDefaultValue(); - } - } - - // getting the set of arguments should be sent to requested method - return $this->resolver->resolveArguments($method, $parameters); - } } diff --git a/src/Hmvc/src/Interceptors/Context/Target.php b/src/Hmvc/src/Interceptors/Context/Target.php index bfe3877ed..e8c014eeb 100644 --- a/src/Hmvc/src/Interceptors/Context/Target.php +++ b/src/Hmvc/src/Interceptors/Context/Target.php @@ -25,12 +25,12 @@ public function __toString(): string }; } - public static function fromReflection(\ReflectionFunctionAbstract $reflection): static + public static function fromReflection(\ReflectionFunctionAbstract $reflection): self { return new self(reflection: $reflection); } - public static function fromPathString(string $path, string $delimiter = '.'): static + public static function fromPathString(string $path, string $delimiter = '.'): self { /** @psalm-suppress ArgumentTypeCoercion */ return new self(path: \explode($delimiter, $path), delimiter: $delimiter); @@ -39,11 +39,18 @@ public static function fromPathString(string $path, string $delimiter = '.'): st /** * @param list $path */ - public static function fromPathArray(array $path, string $delimiter = '.'): static + public static function fromPathArray(array $path, string $delimiter = '.'): self { return new self(path: $path, delimiter: $delimiter); } + public static function fromPair(string $controller, string $action): self + { + return \class_exists($controller) + ? self::fromReflection(new \ReflectionMethod($controller, $action)) + : self::fromPathArray([$controller, $action]); + } + public function getPath(): array { return match (true) { diff --git a/src/Queue/src/Bootloader/QueueBootloader.php b/src/Queue/src/Bootloader/QueueBootloader.php index 4014e8a6f..d78c904a2 100644 --- a/src/Queue/src/Bootloader/QueueBootloader.php +++ b/src/Queue/src/Bootloader/QueueBootloader.php @@ -10,7 +10,7 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Config\ConfiguratorInterface; use Spiral\Config\Patch\Append; -use Spiral\Core\{BinderInterface, FactoryInterface, InterceptableCore}; +use Spiral\Core\{BinderInterface, FactoryInterface, InterceptableCore, InterceptorPipeline}; use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterceptorInterface; use Spiral\Queue\{JobHandlerLocatorListener, @@ -20,13 +20,17 @@ QueueRegistry, SerializerLocatorListener, SerializerRegistryInterface}; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Queue\Config\QueueConfig; use Spiral\Queue\ContainerRegistry; use Spiral\Queue\Core\QueueInjector; use Spiral\Queue\Driver\{NullDriver, SyncDriver}; use Spiral\Queue\Failed\{FailedJobHandlerInterface, LogFailedJobHandler}; use Spiral\Queue\HandlerRegistryInterface; -use Spiral\Queue\Interceptor\Consume\{Core as ConsumeCore, ErrorHandlerInterceptor, Handler, RetryPolicyInterceptor}; +use Spiral\Queue\Interceptor\Consume\Core as ConsumeCore; +use Spiral\Queue\Interceptor\Consume\ErrorHandlerInterceptor; +use Spiral\Queue\Interceptor\Consume\Handler; +use Spiral\Queue\Interceptor\Consume\RetryPolicyInterceptor; use Spiral\Telemetry\Bootloader\TelemetryBootloader; use Spiral\Telemetry\TracerFactoryInterface; use Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader; @@ -139,7 +143,7 @@ protected function initHandler( TracerFactoryInterface $tracerFactory, ?EventDispatcherInterface $dispatcher = null, ): Handler { - $core = new InterceptableCore($core, $dispatcher); + $pipeline = (new InterceptorPipeline($dispatcher))->withHandler($core); foreach ($config->getConsumeInterceptors() as $interceptor) { if (\is_string($interceptor)) { @@ -148,11 +152,11 @@ protected function initHandler( $interceptor = $interceptor->resolve($factory); } - \assert($interceptor instanceof CoreInterceptorInterface); - $core->addInterceptor($interceptor); + \assert($interceptor instanceof CoreInterceptorInterface || $interceptor instanceof InterceptorInterface); + $pipeline->addInterceptor($interceptor); } - return new Handler($core, $tracerFactory); + return new Handler($pipeline, $tracerFactory); } private function initQueueConfig(EnvironmentInterface $env): void diff --git a/src/Queue/src/Config/QueueConfig.php b/src/Queue/src/Config/QueueConfig.php index 4a0659176..30b930da1 100644 --- a/src/Queue/src/Config/QueueConfig.php +++ b/src/Queue/src/Config/QueueConfig.php @@ -5,11 +5,17 @@ namespace Spiral\Queue\Config; use Spiral\Core\Container\Autowire; -use Spiral\Core\CoreInterceptorInterface; +use Spiral\Core\CoreInterceptorInterface as LegacyInterceptor; use Spiral\Core\InjectableConfig; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Queue\Exception\InvalidArgumentException; use Spiral\Serializer\SerializerInterface; +/** + * @psalm-type TLegacyInterceptors = array|LegacyInterceptor|Autowire> + * @psalm-type TNewInterceptors = array|InterceptorInterface|Autowire> + * @psalm-type TInterceptors = TNewInterceptors|TLegacyInterceptors + */ final class QueueConfig extends InjectableConfig { public const CONFIG = 'queue'; @@ -41,7 +47,7 @@ public function getAliases(): array /** * Get consumer interceptors * - * @return array|CoreInterceptorInterface|Autowire> + * @return TInterceptors */ public function getConsumeInterceptors(): array { @@ -51,7 +57,7 @@ public function getConsumeInterceptors(): array /** * Get pusher interceptors * - * @return array|CoreInterceptorInterface|Autowire> + * @return TInterceptors */ public function getPushInterceptors(): array { diff --git a/src/Queue/src/Interceptor/Consume/Core.php b/src/Queue/src/Interceptor/Consume/Core.php index 670a3def0..ce0c12418 100644 --- a/src/Queue/src/Interceptor/Consume/Core.php +++ b/src/Queue/src/Interceptor/Consume/Core.php @@ -6,6 +6,8 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\HandlerInterface; use Spiral\Queue\Event\JobProcessed; use Spiral\Queue\Event\JobProcessing; use Spiral\Queue\HandlerRegistryInterface; @@ -20,7 +22,7 @@ * headers: array * } */ -final class Core implements CoreInterface +final class Core implements CoreInterface, HandlerInterface { public function __construct( private readonly HandlerRegistryInterface $registry, @@ -30,6 +32,7 @@ public function __construct( /** * @param-assert TParameters $parameters + * @deprecated */ public function callAction(string $controller, string $action, array $parameters = []): mixed { @@ -49,6 +52,15 @@ public function callAction(string $controller, string $action, array $parameters return null; } + public function handle(CallContext $context): mixed + { + $args = $context->getArguments(); + $controller = $context->getTarget()->getPath()[0]; + $action = $context->getTarget()->getPath()[1]; + + return $this->callAction($controller, $action, $args); + } + /** * @param class-string $event * @param-assert TParameters $parameters diff --git a/src/Queue/src/Interceptor/Consume/Handler.php b/src/Queue/src/Interceptor/Consume/Handler.php index 92c74a4db..5149987d4 100644 --- a/src/Queue/src/Interceptor/Consume/Handler.php +++ b/src/Queue/src/Interceptor/Consume/Handler.php @@ -6,6 +6,9 @@ use Spiral\Core\Container; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\HandlerInterface; use Spiral\Telemetry\NullTracerFactory; use Spiral\Telemetry\TraceKind; use Spiral\Telemetry\TracerFactoryInterface; @@ -17,12 +20,14 @@ final class Handler { private readonly TracerFactoryInterface $tracerFactory; + private readonly bool $isLegacy; public function __construct( - private readonly CoreInterface $core, + private readonly CoreInterface|HandlerInterface $core, ?TracerFactoryInterface $tracerFactory = null, ) { $this->tracerFactory = $tracerFactory ?? new NullTracerFactory(new Container()); + $this->isLegacy = !$core instanceof HandlerInterface; } public function handle( @@ -35,15 +40,19 @@ public function handle( ): mixed { $tracer = $this->tracerFactory->make($headers); + $arguments = [ + 'driver' => $driver, + 'queue' => $queue, + 'id' => $id, + 'payload' => $payload, + 'headers' => $headers, + ]; + return $tracer->trace( name: \sprintf('Job handling [%s:%s]', $name, $id), - callback: fn (): mixed => $this->core->callAction($name, 'handle', [ - 'driver' => $driver, - 'queue' => $queue, - 'id' => $id, - 'payload' => $payload, - 'headers' => $headers, - ]), + callback: $this->isLegacy + ? fn (): mixed => $this->core->callAction($name, 'handle', $arguments) + : fn (): mixed => $this->core->handle(new CallContext(Target::fromPair($name, 'handle'), $arguments)), attributes: [ 'queue.driver' => $driver, 'queue.name' => $queue, diff --git a/src/Queue/src/Interceptor/Push/Core.php b/src/Queue/src/Interceptor/Push/Core.php index 63f2bbe67..dada2541b 100644 --- a/src/Queue/src/Interceptor/Push/Core.php +++ b/src/Queue/src/Interceptor/Push/Core.php @@ -6,6 +6,8 @@ use Spiral\Core\ContainerScope; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\HandlerInterface; use Spiral\Queue\Options; use Spiral\Queue\OptionsInterface; use Spiral\Queue\QueueInterface; @@ -16,7 +18,7 @@ * @internal * @psalm-type TParameters = array{options: ?OptionsInterface, payload: mixed} */ -final class Core implements CoreInterface +final class Core implements CoreInterface, HandlerInterface { public function __construct( private readonly QueueInterface $connection, @@ -25,6 +27,7 @@ public function __construct( /** * @param-assert TParameters $parameters + * @deprecated */ public function callAction( string $controller, @@ -56,6 +59,15 @@ public function callAction( ); } + public function handle(CallContext $context): mixed + { + $args = $context->getArguments(); + $controller = $context->getTarget()->getPath()[0]; + $action = $context->getTarget()->getPath()[1]; + + return $this->callAction($controller, $action, $args); + } + private function getTracer(): TracerInterface { try { diff --git a/src/Queue/src/Queue.php b/src/Queue/src/Queue.php index ee7583d9b..2978e7ff6 100644 --- a/src/Queue/src/Queue.php +++ b/src/Queue/src/Queue.php @@ -5,6 +5,9 @@ namespace Spiral\Queue; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\HandlerInterface as InterceptorHandler; /** * This class is used to push jobs into the queue and pass them through the interceptor chain @@ -15,16 +18,23 @@ */ final class Queue implements QueueInterface { + private readonly bool $isLegacy; + public function __construct( - private readonly CoreInterface $core, + private readonly CoreInterface|InterceptorHandler $core, ) { + $this->isLegacy = !$core instanceof HandlerInterface; } public function push(string $name, mixed $payload = [], mixed $options = null): string { - return $this->core->callAction($name, 'push', [ + $arguments = [ 'payload' => $payload, 'options' => $options, - ]); + ]; + + return $this->isLegacy + ? $this->core->callAction($name, 'push', $arguments) + : $this->core->handle(new CallContext(Target::fromPair($name, 'push'), $arguments)); } } diff --git a/src/Queue/src/QueueManager.php b/src/Queue/src/QueueManager.php index 5a686f675..97b1be5b1 100644 --- a/src/Queue/src/QueueManager.php +++ b/src/Queue/src/QueueManager.php @@ -7,10 +7,11 @@ use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Core\Container\Autowire; -use Spiral\Core\CoreInterceptorInterface; +use Spiral\Core\CoreInterceptorInterface as LegacyInterceptor; use Spiral\Core\Exception\Container\ContainerException; use Spiral\Core\FactoryInterface; -use Spiral\Core\InterceptableCore; +use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Queue\Config\QueueConfig; use Spiral\Queue\Interceptor\Push\Core as PushCore; @@ -50,22 +51,18 @@ private function resolveConnection(string $name): QueueInterface try { $driver = $this->factory->make($config['driver'], $config); - - $core = new InterceptableCore( - new PushCore($driver), - $this->dispatcher - ); + $pipeline = (new InterceptorPipeline($this->dispatcher))->withHandler(new PushCore($driver)); foreach ($this->config->getPushInterceptors() as $interceptor) { if (\is_string($interceptor) || $interceptor instanceof Autowire) { $interceptor = $this->container->get($interceptor); } - \assert($interceptor instanceof CoreInterceptorInterface); - $core->addInterceptor($interceptor); + \assert($interceptor instanceof LegacyInterceptor || $interceptor instanceof InterceptorInterface); + $pipeline->addInterceptor($interceptor); } - return new Queue($core); + return new Queue($pipeline); } catch (ContainerException $e) { throw new Exception\NotSupportedDriverException( \sprintf( diff --git a/tests/Framework/Bootloader/DomainBootloaderTest.php b/tests/Framework/Bootloader/DomainBootloaderTest.php index 334e205b9..3f7a755ae 100644 --- a/tests/Framework/Bootloader/DomainBootloaderTest.php +++ b/tests/Framework/Bootloader/DomainBootloaderTest.php @@ -27,27 +27,27 @@ public function testDefineInterceptors(): void ); } - public function testDomainCore(): void - { - $bootloader = new class extends DomainBootloader { - protected static function defineInterceptors(): array - { - return [ - One::class, - new Autowire(Two::class), - new Three() - ]; - } - }; - - /** @var InterceptableCore $core */ - $core = (new \ReflectionMethod($bootloader, 'domainCore')) - ->invoke($bootloader, $this->getContainer()->get(Core::class), $this->getContainer()); - $pipeline = (new \ReflectionProperty($core, 'pipeline'))->getValue($core); - - $this->assertEquals( - [new One(), new Two(), new Three()], - (new \ReflectionProperty($pipeline, 'interceptors'))->getValue($pipeline) - ); - } + // public function testDomainCore(): void + // { + // $bootloader = new class extends DomainBootloader { + // protected static function defineInterceptors(): array + // { + // return [ + // One::class, + // new Autowire(Two::class), + // new Three() + // ]; + // } + // }; + // + // /** @var InterceptableCore $core */ + // $core = (new \ReflectionMethod($bootloader, 'domainCore')) + // ->invoke($bootloader, $this->getContainer()->get(Core::class), $this->getContainer()); + // $pipeline = (new \ReflectionProperty($core, 'pipeline'))->getValue($core); + // + // $this->assertEquals( + // [new One(), new Two(), new Three()], + // (new \ReflectionProperty($pipeline, 'interceptors'))->getValue($pipeline) + // ); + // } } diff --git a/tests/app/src/Bootloader/RoutesBootloader.php b/tests/app/src/Bootloader/RoutesBootloader.php index 0cdb43c31..300e01918 100644 --- a/tests/app/src/Bootloader/RoutesBootloader.php +++ b/tests/app/src/Bootloader/RoutesBootloader.php @@ -14,7 +14,7 @@ use Spiral\Bootloader\Http\RoutesBootloader as BaseRoutesBootloader; use Spiral\Cookies\Middleware\CookiesMiddleware; use Spiral\Core\Core; -use Spiral\Core\InterceptableCore; +use Spiral\Core\InterceptorPipeline; use Spiral\Csrf\Middleware\CsrfMiddleware; use Spiral\Debug\StateCollector\HttpCollector; use Spiral\Domain\PipelineInterceptor; @@ -178,14 +178,14 @@ protected function defineRoutes(RoutingConfigurator $routes): void ->middleware($this->middlewareGroups()['web']); } - private function getInterceptedCore(array $interceptors): InterceptableCore + private function getInterceptedCore(array $interceptors): InterceptorPipeline { - $core = new InterceptableCore($this->core); + $pipeline = (new InterceptorPipeline())->withCore($this->core); foreach ($interceptors as $interceptor) { - $core->addInterceptor(\is_object($interceptor) ? $interceptor : $this->container->get($interceptor)); + $pipeline->addInterceptor(\is_object($interceptor) ? $interceptor : $this->container->get($interceptor)); } - return $core; + return $pipeline; } } From 0b9bbd6de70213321a757140d4defc9456267d61 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 29 Apr 2024 16:55:39 +0400 Subject: [PATCH 71/84] Integrate new interceptors part 2 --- src/Events/src/EventDispatcher.php | 14 ++++++++-- src/Filters/src/Model/FilterProvider.php | 13 ++++++--- .../Consume/ErrorHandlerInterceptor.php | 27 +++++++++++++++++-- src/Queue/src/Interceptor/Consume/Handler.php | 2 +- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/Events/src/EventDispatcher.php b/src/Events/src/EventDispatcher.php index 03ea2613a..67680962d 100644 --- a/src/Events/src/EventDispatcher.php +++ b/src/Events/src/EventDispatcher.php @@ -6,16 +6,26 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\HandlerInterface; final class EventDispatcher implements EventDispatcherInterface { + private readonly bool $isLegacy; public function __construct( - private readonly CoreInterface $core + private readonly HandlerInterface|CoreInterface $core ) { + $this->isLegacy = !$core instanceof HandlerInterface; } public function dispatch(object $event): object { - return $this->core->callAction($event::class, 'dispatch', ['event' => $event]); + return $this->isLegacy + ? $this->core->callAction($event::class, 'dispatch', ['event' => $event]) + : $this->core->handle(new CallContext( + Target::fromReflection(new \ReflectionMethod($event::class, 'dispatch')), + ['event' => $event], + )); } } diff --git a/src/Filters/src/Model/FilterProvider.php b/src/Filters/src/Model/FilterProvider.php index faea9e8ce..f8f7b4780 100644 --- a/src/Filters/src/Model/FilterProvider.php +++ b/src/Filters/src/Model/FilterProvider.php @@ -11,6 +11,8 @@ use Spiral\Filters\Model\Schema\Builder; use Spiral\Filters\Model\Schema\InputMapper; use Spiral\Filters\InputInterface; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; use Spiral\Interceptors\HandlerInterface; use Spiral\Models\SchematicEntity; @@ -20,11 +22,13 @@ */ final class FilterProvider implements FilterProviderInterface { + private readonly bool $isLegacy; public function __construct( private readonly ContainerInterface $container, private readonly ResolverInterface $resolver, - private readonly CoreInterface|HandlerInterface $core + private readonly HandlerInterface|CoreInterface $core ) { + $this->isLegacy = !$core instanceof HandlerInterface; } public function createFilter(string $name, InputInterface $input): FilterInterface @@ -57,9 +61,12 @@ public function createFilter(string $name, InputInterface $input): FilterInterfa $errors = \array_merge($errors, $inputErrors); $entity = new SchematicEntity($data, $schema); - return $this->core->callAction($name, 'handle', [ + $args = [ 'filterBag' => new FilterBag($filter, $entity, $schema, $errors), - ]); + ]; + return $this->isLegacy + ? $this->core->callAction($name, 'handle', $args) + : $this->core->handle(new CallContext(Target::fromPair($name, 'handle'), $args)); } private function createFilterInstance(string $name): FilterInterface diff --git a/src/Queue/src/Interceptor/Consume/ErrorHandlerInterceptor.php b/src/Queue/src/Interceptor/Consume/ErrorHandlerInterceptor.php index 0443d1e7a..967a92b49 100644 --- a/src/Queue/src/Interceptor/Consume/ErrorHandlerInterceptor.php +++ b/src/Queue/src/Interceptor/Consume/ErrorHandlerInterceptor.php @@ -4,12 +4,15 @@ namespace Spiral\Queue\Interceptor\Consume; -use Spiral\Core\CoreInterceptorInterface; +use Spiral\Core\CoreInterceptorInterface as LegacyInterceptor; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\Context\CallContextInterface; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; use Spiral\Queue\Exception\StateException; use Spiral\Queue\Failed\FailedJobHandlerInterface; -final class ErrorHandlerInterceptor implements CoreInterceptorInterface +final class ErrorHandlerInterceptor implements LegacyInterceptor, InterceptorInterface { public function __construct( private readonly FailedJobHandlerInterface $handler @@ -35,4 +38,24 @@ public function process(string $name, string $action, array $parameters, CoreInt throw $e; } } + + public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed + { + try { + return $handler->handle($context); + } catch (\Throwable $e) { + $args = $context->getArguments(); + if (!$e instanceof StateException) { + $this->handler->handle( + $args['driver'], + $args['queue'], + $context->getTarget()->getPath()[0], + $args['payload'], + $e, + ); + } + + throw $e; + } + } } diff --git a/src/Queue/src/Interceptor/Consume/Handler.php b/src/Queue/src/Interceptor/Consume/Handler.php index 5149987d4..b09a94a3c 100644 --- a/src/Queue/src/Interceptor/Consume/Handler.php +++ b/src/Queue/src/Interceptor/Consume/Handler.php @@ -23,7 +23,7 @@ final class Handler private readonly bool $isLegacy; public function __construct( - private readonly CoreInterface|HandlerInterface $core, + private readonly HandlerInterface|CoreInterface $core, ?TracerFactoryInterface $tracerFactory = null, ) { $this->tracerFactory = $tracerFactory ?? new NullTracerFactory(new Container()); From f4002f2ee6d2f466f1ad79a1d01d2f3fc286010f Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 29 Apr 2024 20:11:08 +0400 Subject: [PATCH 72/84] Integrate new interceptors part 3 (router) --- src/Framework/Bootloader/DomainBootloader.php | 3 +- .../Bootloader/Http/RouterBootloader.php | 2 + src/Router/src/CoreHandler.php | 38 ++++++++++++------- .../Configurator/ImportConfigurator.php | 3 +- .../Loader/Configurator/RouteConfigurator.php | 5 ++- src/Router/src/RouteGroup.php | 7 ++-- src/Router/src/Target/AbstractTarget.php | 22 +++++++++-- .../src/Declaration/BootloaderDeclaration.php | 6 +-- 8 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/Framework/Bootloader/DomainBootloader.php b/src/Framework/Bootloader/DomainBootloader.php index 5927c94b6..027f2be0c 100644 --- a/src/Framework/Bootloader/DomainBootloader.php +++ b/src/Framework/Bootloader/DomainBootloader.php @@ -11,6 +11,7 @@ use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\HandlerInterface; use Spiral\Interceptors\InterceptorInterface; /** @@ -27,7 +28,7 @@ protected static function domainCore( Core $core, ContainerInterface $container, ?EventDispatcherInterface $dispatcher = null - ): CoreInterface { + ): CoreInterface&HandlerInterface { $pipeline = (new InterceptorPipeline($dispatcher))->withCore($core); foreach (static::defineInterceptors() as $interceptor) { diff --git a/src/Framework/Bootloader/Http/RouterBootloader.php b/src/Framework/Bootloader/Http/RouterBootloader.php index 1c49a8446..0d872b3ea 100644 --- a/src/Framework/Bootloader/Http/RouterBootloader.php +++ b/src/Framework/Bootloader/Http/RouterBootloader.php @@ -16,6 +16,7 @@ use Spiral\Core\Exception\ScopeException; use Spiral\Framework\Kernel; use Spiral\Http\Config\HttpConfig; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\GroupRegistry; use Spiral\Router\Loader\Configurator\RoutingConfigurator; use Spiral\Router\Loader\DelegatingLoader; @@ -40,6 +41,7 @@ final class RouterBootloader extends Bootloader ]; protected const SINGLETONS = [ + HandlerInterface::class => Core::class, CoreInterface::class => Core::class, RouterInterface::class => [self::class, 'router'], RouteInterface::class => [self::class, 'route'], diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index 7eac6c1d4..0d321e4d4 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -9,15 +9,19 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface; use Spiral\Core\CoreInterface; -use Spiral\Core\Exception\ControllerException; use Spiral\Core\ScopeInterface; use Spiral\Http\Exception\ClientException; use Spiral\Http\Exception\ClientException\BadRequestException; use Spiral\Http\Exception\ClientException\ForbiddenException; use Spiral\Http\Exception\ClientException\NotFoundException; +use Spiral\Http\Exception\ClientException\ServerErrorException; use Spiral\Http\Exception\ClientException\UnauthorizedException; use Spiral\Http\Stream\GeneratorStream; use Spiral\Http\Traits\JsonTrait; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\Exception\TargetCallException; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\Exception\HandlerException; use Spiral\Telemetry\NullTracer; use Spiral\Telemetry\TracerInterface; @@ -37,13 +41,16 @@ final class CoreHandler implements RequestHandlerInterface /** @readonly */ private ?array $parameters = null; + private bool $isLegacyPipeline; + public function __construct( - private readonly CoreInterface $core, + private readonly HandlerInterface|CoreInterface $core, private readonly ScopeInterface $scope, private readonly ResponseFactoryInterface $responseFactory, ?TracerInterface $tracer = null ) { $this->tracer = $tracer ?? new NullTracer($scope); + $this->isLegacyPipeline = !$core instanceof HandlerInterface; } /** @@ -102,11 +109,15 @@ public function handle(Request $request): Response ], fn (): mixed => $this->tracer->trace( name: 'Controller [' . $controller . ':' . $action . ']', - callback: fn (): mixed => $this->core->callAction( - controller: $controller, - action: $action, - parameters: $parameters, - ), + callback: $this->isLegacyPipeline + ? fn (): mixed => $this->core->callAction( + controller: $controller, + action: $action, + parameters: $parameters, + ) + : fn (): mixed => $this->core->handle(new CallContext( + Target::fromPair($controller, $action), $parameters) + ), attributes: [ 'route.controller' => $this->controller, 'route.action' => $action, @@ -114,7 +125,7 @@ public function handle(Request $request): Response ] ) ); - } catch (ControllerException $e) { + } catch (TargetCallException $e) { \ob_get_clean(); throw $this->mapException($e); } catch (\Throwable $e) { @@ -169,13 +180,14 @@ private function wrapResponse(Response $response, mixed $result = null, string $ /** * Converts core specific ControllerException into HTTP ClientException. */ - private function mapException(ControllerException $exception): ClientException + private function mapException(TargetCallException $exception): ClientException { return match ($exception->getCode()) { - ControllerException::BAD_ACTION, - ControllerException::NOT_FOUND => new NotFoundException('Not found', $exception), - ControllerException::FORBIDDEN => new ForbiddenException('Forbidden', $exception), - ControllerException::UNAUTHORIZED => new UnauthorizedException('Unauthorized', $exception), + TargetCallException::BAD_ACTION, + TargetCallException::NOT_FOUND => new NotFoundException('Not found', $exception), + TargetCallException::FORBIDDEN => new ForbiddenException('Forbidden', $exception), + TargetCallException::UNAUTHORIZED => new UnauthorizedException('Unauthorized', $exception), + TargetCallException::INVALID_CONTROLLER => new ServerErrorException('Server error', $exception), default => new BadRequestException('Bad request', $exception), }; } diff --git a/src/Router/src/Loader/Configurator/ImportConfigurator.php b/src/Router/src/Loader/Configurator/ImportConfigurator.php index 61d53577c..8f062638b 100644 --- a/src/Router/src/Loader/Configurator/ImportConfigurator.php +++ b/src/Router/src/Loader/Configurator/ImportConfigurator.php @@ -6,6 +6,7 @@ use Psr\Http\Server\MiddlewareInterface; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\RouteCollection; final class ImportConfigurator @@ -77,7 +78,7 @@ public function namePrefix(string $prefix): self return $this; } - public function core(CoreInterface $core): self + public function core(HandlerInterface|CoreInterface $core): self { foreach ($this->routes->all() as $configurator) { $configurator->core($core); diff --git a/src/Router/src/Loader/Configurator/RouteConfigurator.php b/src/Router/src/Loader/Configurator/RouteConfigurator.php index 12e38d4a1..b6ed22adc 100644 --- a/src/Router/src/Loader/Configurator/RouteConfigurator.php +++ b/src/Router/src/Loader/Configurator/RouteConfigurator.php @@ -7,6 +7,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\Exception\TargetException; use Spiral\Router\RouteCollection; use Spiral\Router\Target\Action; @@ -21,7 +22,7 @@ final class RouteConfigurator private ?string $group = null; private ?array $methods = null; private string $prefix = ''; - private ?CoreInterface $core = null; + private HandlerInterface|CoreInterface|null $core = null; private ?array $middleware = null; /** @var null|string|callable|RequestHandlerInterface|TargetInterface */ @@ -133,7 +134,7 @@ public function prefix(string $prefix): self return $this; } - public function core(CoreInterface $core): self + public function core(HandlerInterface|CoreInterface $core): self { $this->core = $core; diff --git a/src/Router/src/RouteGroup.php b/src/Router/src/RouteGroup.php index bee98bf3d..0ef03843c 100644 --- a/src/Router/src/RouteGroup.php +++ b/src/Router/src/RouteGroup.php @@ -9,6 +9,7 @@ use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterface; use Spiral\Core\FactoryInterface; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\Target\AbstractTarget; /** @@ -27,7 +28,7 @@ final class RouteGroup /** @var array */ private array $middleware = []; - private Autowire|CoreInterface|string|null $core = null; + private Autowire|HandlerInterface|CoreInterface|string|null $core = null; public function __construct( /** @deprecated since v3.3.0 */ @@ -67,7 +68,7 @@ public function setNamePrefix(string $prefix): self return $this; } - public function setCore(Autowire|CoreInterface|string $core): self + public function setCore(Autowire|CoreInterface|HandlerInterface|string $core): self { $this->core = $core; @@ -93,7 +94,7 @@ public function register(RouterInterface $router, FactoryInterface $factory): vo { foreach ($this->routes as $name => $route) { if ($this->core !== null) { - if (!$this->core instanceof CoreInterface) { + if (!$this->core instanceof CoreInterface && !$this->core instanceof HandlerInterface) { $this->core = $factory->make($this->core); } diff --git a/src/Router/src/Target/AbstractTarget.php b/src/Router/src/Target/AbstractTarget.php index 52b0fdb81..8d2231a22 100644 --- a/src/Router/src/Target/AbstractTarget.php +++ b/src/Router/src/Target/AbstractTarget.php @@ -10,6 +10,7 @@ use Psr\Http\Server\RequestHandlerInterface as Handler; use Spiral\Core\CoreInterface; use Spiral\Core\ScopeInterface; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\CoreHandler; use Spiral\Router\Exception\TargetException; use Spiral\Router\TargetInterface; @@ -24,7 +25,7 @@ abstract class AbstractTarget implements TargetInterface // Automatically prepend HTTP verb to all action names. public const RESTFUL = 1; - private ?CoreInterface $core = null; + private HandlerInterface|CoreInterface|null $pipeline = null; private ?CoreHandler $handler = null; private bool $verbActions; @@ -49,11 +50,24 @@ public function getConstrains(): array /** * @mutation-free + * @deprecated Use {@see withHandler()} instead. */ - public function withCore(CoreInterface $core): TargetInterface + public function withCore(HandlerInterface|CoreInterface $core): TargetInterface { $target = clone $this; - $target->core = $core; + $target->pipeline = $core; + $target->handler = null; + + return $target; + } + + /** + * @mutation-free + */ + public function withHandler(HandlerInterface $handler): TargetInterface + { + $target = clone $this; + $target->pipeline = $handler; $target->handler = null; return $target; @@ -77,7 +91,7 @@ protected function coreHandler(ContainerInterface $container): CoreHandler try { // construct on demand $this->handler = new CoreHandler( - $this->core ?? $container->get(CoreInterface::class), + $this->pipeline ?? $container->get(HandlerInterface::class), $container->get(ScopeInterface::class), $container->get(ResponseFactoryInterface::class), $container->get(TracerInterface::class) diff --git a/src/Scaffolder/src/Declaration/BootloaderDeclaration.php b/src/Scaffolder/src/Declaration/BootloaderDeclaration.php index 57d188a6e..136ed39fc 100644 --- a/src/Scaffolder/src/Declaration/BootloaderDeclaration.php +++ b/src/Scaffolder/src/Declaration/BootloaderDeclaration.php @@ -9,7 +9,7 @@ use Spiral\Boot\BootloadManager\Methods; use Spiral\Boot\KernelInterface; use Spiral\Bootloader\DomainBootloader; -use Spiral\Core\CoreInterface; +use Spiral\Interceptors\HandlerInterface; use Spiral\Scaffolder\Config\ScaffolderConfig; class BootloaderDeclaration extends AbstractDeclaration implements HasInstructions @@ -45,9 +45,9 @@ public function declare(): void if ($this->isDomain) { $this->class->addConstant('INTERCEPTORS', [])->setProtected(); - $this->namespace->addUse(CoreInterface::class); + $this->namespace->addUse(HandlerInterface::class); $this->class->getConstant('SINGLETONS')->setValue([ - new Literal('CoreInterface::class => [self::class, \'domainCore\']'), + new Literal('HandlerInterface::class => [self::class, \'domainCore\']'), ]); } From 26e79eec2867935693bb4b69528c62721bb3552b Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 29 Apr 2024 20:48:24 +0400 Subject: [PATCH 73/84] Fix tests --- src/Console/src/StaticLocator.php | 3 +- .../src/Bootloader/EventsBootloader.php | 7 +-- src/Hmvc/src/Interceptors/Context/Target.php | 2 +- src/Router/src/CoreHandler.php | 7 ++- src/Router/tests/BaseTestCase.php | 2 + src/Router/tests/RouteGroupTest.php | 4 +- .../tests/Command/BootloaderTest.php | 5 +- src/Scaffolder/tests/Command/CommandTest.php | 2 +- src/Scaffolder/tests/Command/ConfigTest.php | 2 +- .../tests/Command/ControllerTest.php | 2 +- .../tests/Command/FilterCommandTest.php | 2 +- .../tests/Command/JobHandlerTest.php | 2 +- .../tests/Command/MiddlewareTest.php | 2 +- .../Bootloader/DomainBootloaderTest.php | 48 ++++++++++--------- tests/app/src/Bootloader/AppBootloader.php | 4 +- 15 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/Console/src/StaticLocator.php b/src/Console/src/StaticLocator.php index e12eb9ca9..c023d1ae7 100644 --- a/src/Console/src/StaticLocator.php +++ b/src/Console/src/StaticLocator.php @@ -8,6 +8,7 @@ use Spiral\Console\Traits\LazyTrait; use Spiral\Core\Container; use Spiral\Core\CoreInterceptorInterface; +use Spiral\Interceptors\InterceptorInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; final class StaticLocator implements LocatorInterface @@ -16,7 +17,7 @@ final class StaticLocator implements LocatorInterface /** * @param array> $commands - * @param array> $interceptors + * @param array> $interceptors */ public function __construct( private readonly array $commands, diff --git a/src/Events/src/Bootloader/EventsBootloader.php b/src/Events/src/Bootloader/EventsBootloader.php index 665059cb9..8d235f8b6 100644 --- a/src/Events/src/Bootloader/EventsBootloader.php +++ b/src/Events/src/Bootloader/EventsBootloader.php @@ -95,8 +95,9 @@ public function boot( /** * @param TInterceptor $interceptor */ - public function addInterceptor(string|CoreInterceptorInterface|Container\Autowire $interceptor): void - { + public function addInterceptor( + string|InterceptorInterface|CoreInterceptorInterface|Container\Autowire $interceptor, + ): void { $this->configs->modify(EventsConfig::CONFIG, new Append('interceptors', null, $interceptor)); } @@ -120,7 +121,7 @@ private function initEventDispatcher( } /** - * @template T + * @template T of object * * @param class-string|Autowire|T $id * diff --git a/src/Hmvc/src/Interceptors/Context/Target.php b/src/Hmvc/src/Interceptors/Context/Target.php index e8c014eeb..850439b93 100644 --- a/src/Hmvc/src/Interceptors/Context/Target.php +++ b/src/Hmvc/src/Interceptors/Context/Target.php @@ -46,7 +46,7 @@ public static function fromPathArray(array $path, string $delimiter = '.'): self public static function fromPair(string $controller, string $action): self { - return \class_exists($controller) + return \method_exists($controller, $action) ? self::fromReflection(new \ReflectionMethod($controller, $action)) : self::fromPathArray([$controller, $action]); } diff --git a/src/Router/src/CoreHandler.php b/src/Router/src/CoreHandler.php index 0d321e4d4..e61f8cb6c 100644 --- a/src/Router/src/CoreHandler.php +++ b/src/Router/src/CoreHandler.php @@ -115,8 +115,11 @@ public function handle(Request $request): Response action: $action, parameters: $parameters, ) - : fn (): mixed => $this->core->handle(new CallContext( - Target::fromPair($controller, $action), $parameters) + : fn (): mixed => $this->core->handle( + new CallContext( + Target::fromPair($controller, $action), + $parameters, + ), ), attributes: [ 'route.controller' => $this->controller, diff --git a/src/Router/tests/BaseTestCase.php b/src/Router/tests/BaseTestCase.php index 2c8ced74e..1654e11a4 100644 --- a/src/Router/tests/BaseTestCase.php +++ b/src/Router/tests/BaseTestCase.php @@ -13,6 +13,7 @@ use Spiral\Core\Container\Autowire; use Spiral\Core\CoreInterface; use Spiral\Http\Config\HttpConfig; +use Spiral\Interceptors\HandlerInterface; use Spiral\Router\GroupRegistry; use Spiral\Router\Loader\Configurator\RoutingConfigurator; use Spiral\Router\Loader\DelegatingLoader; @@ -87,6 +88,7 @@ private function initContainer(): void ) ); + $this->container->bind(HandlerInterface::class, Core::class); $this->container->bind(CoreInterface::class, Core::class); $this->container->bindSingleton(GroupRegistry::class, GroupRegistry::class); $this->container->bindSingleton(RoutingConfigurator::class, RoutingConfigurator::class); diff --git a/src/Router/tests/RouteGroupTest.php b/src/Router/tests/RouteGroupTest.php index c0e8679ac..c4bb000e2 100644 --- a/src/Router/tests/RouteGroupTest.php +++ b/src/Router/tests/RouteGroupTest.php @@ -45,7 +45,7 @@ public function testCoreString(): void $this->assertSame('controller', $this->getProperty($t, 'controller')); $this->assertSame('method', $this->getProperty($t, 'action')); - $this->assertInstanceOf(RoutesTestCore::class, $this->getActionProperty($t, 'core')); + $this->assertInstanceOf(RoutesTestCore::class, $this->getActionProperty($t, 'pipeline')); } public function testCoreObject(): void @@ -63,7 +63,7 @@ public function testCoreObject(): void $this->assertSame('controller', $this->getProperty($t, 'controller')); $this->assertSame('method', $this->getProperty($t, 'action')); - $this->assertInstanceOf(RoutesTestCore::class, $this->getActionProperty($t, 'core')); + $this->assertInstanceOf(RoutesTestCore::class, $this->getActionProperty($t, 'pipeline')); } public function testGroupHasRoute(): void diff --git a/src/Scaffolder/tests/Command/BootloaderTest.php b/src/Scaffolder/tests/Command/BootloaderTest.php index 83e00cbad..9014d70b9 100644 --- a/src/Scaffolder/tests/Command/BootloaderTest.php +++ b/src/Scaffolder/tests/Command/BootloaderTest.php @@ -7,6 +7,7 @@ use ReflectionClass; use ReflectionException; use Spiral\Core\CoreInterface; +use Spiral\Interceptors\HandlerInterface; use Throwable; final class BootloaderTest extends AbstractCommandTestCase @@ -97,7 +98,7 @@ public function testScaffoldForDomainBootloader(): void $this->assertTrue($reflection->hasConstant('SINGLETONS')); $this->assertEquals([ - CoreInterface::class => ['Spiral\Tests\Scaffolder\App\Bootloader\SampleDomainBootloader', 'domainCore'], + HandlerInterface::class => ['Spiral\Tests\Scaffolder\App\Bootloader\SampleDomainBootloader', 'domainCore'], ], $reflection->getConstant('SINGLETONS')); } @@ -112,7 +113,7 @@ public function testShowInstructionAfterInstallation(): void $output = $result->getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<getOutput()->fetch(); - $this->assertSame( + $this->assertStringEqualsStringIgnoringLineEndings( <<invoke($bootloader, $this->getContainer()->get(Core::class), $this->getContainer()); - // $pipeline = (new \ReflectionProperty($core, 'pipeline'))->getValue($core); - // - // $this->assertEquals( - // [new One(), new Two(), new Three()], - // (new \ReflectionProperty($pipeline, 'interceptors'))->getValue($pipeline) - // ); - // } + public function testDomainCore(): void + { + $bootloader = new class extends DomainBootloader { + protected static function defineInterceptors(): array + { + return [ + One::class, + new Autowire(Two::class), + new Three() + ]; + } + }; + + /** @var InterceptorPipeline $pipeline */ + $pipeline = (new \ReflectionMethod($bootloader, 'domainCore')) + ->invoke($bootloader, $this->getContainer()->get(Core::class), $this->getContainer()); + + $interceptors = (new \ReflectionProperty($pipeline, 'interceptors'))->getValue($pipeline); + + $this->assertEquals( + [new One(), new Two(), new Three()], + $interceptors, + ); + } } diff --git a/tests/app/src/Bootloader/AppBootloader.php b/tests/app/src/Bootloader/AppBootloader.php index 09508fde4..069ce0dbb 100644 --- a/tests/app/src/Bootloader/AppBootloader.php +++ b/tests/app/src/Bootloader/AppBootloader.php @@ -9,13 +9,15 @@ use Spiral\Bootloader\Http\JsonPayloadsBootloader; use Spiral\Core\CoreInterface; use Spiral\Domain\GuardInterceptor; +use Spiral\Interceptors\HandlerInterface; use Spiral\Security\PermissionsInterface; use Spiral\Views\Bootloader\ViewsBootloader; class AppBootloader extends DomainBootloader { protected const SINGLETONS = [ - CoreInterface::class => [self::class, 'domainCore'] + HandlerInterface::class => [self::class, 'domainCore'], + CoreInterface::class => [self::class, 'domainCore'], ]; protected const INTERCEPTORS = [ From d814c5768187a33d4bf5603c41735e3eea060385 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 29 Apr 2024 22:21:57 +0400 Subject: [PATCH 74/84] Cover `Target::fromPair()` method; deprecate `\Spiral\Domain\PipelineInterceptor` --- src/Framework/Domain/PipelineInterceptor.php | 3 +++ .../Interceptors/Unit/Context/TargetTest.php | 26 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Framework/Domain/PipelineInterceptor.php b/src/Framework/Domain/PipelineInterceptor.php index f8fbe4b19..6443bafce 100644 --- a/src/Framework/Domain/PipelineInterceptor.php +++ b/src/Framework/Domain/PipelineInterceptor.php @@ -13,6 +13,9 @@ use Spiral\Core\InterceptorPipeline; use Spiral\Domain\Annotation\Pipeline; +/** + * @deprecated Will be removed in future releases. + */ class PipelineInterceptor implements CoreInterceptorInterface { private array $cache = []; diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index 5042db617..890eeed98 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Spiral\Interceptors\Context\Target; +use Spiral\Tests\Interceptors\Unit\Stub\TestService; class TargetTest extends TestCase { @@ -93,6 +94,29 @@ public function testCreateFromPathArray(array $chunks, string $separator): void self::assertSame($str, (string)$target); } + public static function providePairs(): iterable + { + yield 'static method' => [TestService::class, 'toUpperCase', true]; + yield 'public method' => [TestService::class, 'increment', true]; + yield 'protected method' => [TestService::class, 'toLowerCase', true]; + yield 'not existing' => [TestService::class, 'noExistingMethod', false]; + yield 'not a class' => ['Spiral\Tests\Interceptors\Unit\Stub\FooBarBaz', 'noExistingMethod', false]; + } + + #[DataProvider('providePairs')] + public function testCreateFromPair(string $controller, string $action, bool $hasReflection): void + { + $target = Target::fromPair($controller, $action); + + self::assertSame([$controller, $action], $target->getPath()); + $reflection = $target->getReflection(); + self::assertSame($hasReflection, $reflection !== null); + if ($hasReflection) { + self::assertInstanceOf(\ReflectionMethod::class, $reflection); + self::assertSame($action, $reflection->getName()); + } + } + public function testCreateFromPathStringDefaultSeparator(): void { $str = 'foo.bar.baz'; @@ -106,6 +130,6 @@ public function testPrivateConstructor(): void { $this->expectException(\Error::class); - new \Spiral\Interceptors\Context\Target(); + new Target(); } } From 66741660560289fa67321b37bc865eec4a0bd7da Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 30 Apr 2024 13:54:59 +0400 Subject: [PATCH 75/84] Fix interceptor Target::fromPair : parent class is put into the path immediately because ReflectionMethod may contain parent class instead of target --- src/Hmvc/src/Core/AbstractCore.php | 10 ++++---- src/Hmvc/src/Interceptors/Context/Target.php | 3 ++- .../Interceptors/Context/TargetInterface.php | 6 +++++ .../Handler/ReflectionHandler.php | 6 ++--- src/Hmvc/tests/Core/CoreTest.php | 24 ++++++++++++++++++ .../Core/Fixtures/AbstractTestService.php | 13 ++++++++++ src/Hmvc/tests/Core/Fixtures/TestService.php | 25 +++++++++++++++++++ .../Interceptors/Unit/Context/TargetTest.php | 1 + .../Unit/Handler/ReflectionHandlerTest.php | 13 ++++++++++ .../Unit/Stub/AbstractTestService.php | 13 ++++++++++ .../Interceptors/Unit/Stub/TestService.php | 2 +- 11 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src/Hmvc/tests/Core/Fixtures/AbstractTestService.php create mode 100644 src/Hmvc/tests/Core/Fixtures/TestService.php create mode 100644 src/Hmvc/tests/Interceptors/Unit/Stub/AbstractTestService.php diff --git a/src/Hmvc/src/Core/AbstractCore.php b/src/Hmvc/src/Core/AbstractCore.php index 3a914d147..6c770a26e 100644 --- a/src/Hmvc/src/Core/AbstractCore.php +++ b/src/Hmvc/src/Core/AbstractCore.php @@ -49,7 +49,7 @@ public function callAction(string $controller, string $action, array $parameters // Validate method ActionResolver::validateControllerMethod($method); - return $this->invoke($method, $parameters); + return $this->invoke($controller, $method, $parameters); } public function handle(CallContext $context): mixed @@ -57,7 +57,7 @@ public function handle(CallContext $context): mixed $target = $context->getTarget(); $reflection = $target->getReflection(); return $reflection instanceof \ReflectionMethod - ? $this->invoke($reflection, $context->getArguments()) + ? $this->invoke($target->getPath()[0], $reflection, $context->getArguments()) : $this->callAction($target->getPath()[0], $target->getPath()[1], $context->getArguments()); } @@ -82,7 +82,7 @@ protected function resolveArguments(\ReflectionMethod $method, array $parameters /** * @throws \Throwable */ - private function invoke(\ReflectionMethod $method, array $arguments): mixed + private function invoke(string $class, \ReflectionMethod $method, array $arguments): mixed { try { $args = $this->resolveArguments($method, $arguments); @@ -91,7 +91,7 @@ private function invoke(\ReflectionMethod $method, array $arguments): mixed \sprintf( 'Missing/invalid parameter %s of `%s`->`%s`', $e->getParameter(), - $method->getDeclaringClass()->getName(), + $class, $method->getName(), ), ControllerException::BAD_ARGUMENT, @@ -105,6 +105,6 @@ private function invoke(\ReflectionMethod $method, array $arguments): mixed ); } - return $method->invokeArgs($this->container->get($controller), $args); + return $method->invokeArgs($this->container->get($class), $args); } } diff --git a/src/Hmvc/src/Interceptors/Context/Target.php b/src/Hmvc/src/Interceptors/Context/Target.php index 850439b93..40180cd03 100644 --- a/src/Hmvc/src/Interceptors/Context/Target.php +++ b/src/Hmvc/src/Interceptors/Context/Target.php @@ -46,9 +46,10 @@ public static function fromPathArray(array $path, string $delimiter = '.'): self public static function fromPair(string $controller, string $action): self { - return \method_exists($controller, $action) + $target = \method_exists($controller, $action) ? self::fromReflection(new \ReflectionMethod($controller, $action)) : self::fromPathArray([$controller, $action]); + return $target->withPath([$controller, $action]); } public function getPath(): array diff --git a/src/Hmvc/src/Interceptors/Context/TargetInterface.php b/src/Hmvc/src/Interceptors/Context/TargetInterface.php index c2dfd16d9..a5c49bbac 100644 --- a/src/Hmvc/src/Interceptors/Context/TargetInterface.php +++ b/src/Hmvc/src/Interceptors/Context/TargetInterface.php @@ -23,6 +23,12 @@ public function getPath(): array; public function withPath(array $path): static; /** + * Returns the reflection of the target. + * + * It may be {@see \ReflectionFunction} or {@see \ReflectionMethod}. + * Note: {@see \ReflectionMethod::getDeclaringClass()} may return a parent class, but not the class used + * when the target was created. Use {@see Target::getPath()} to get the original target class. + * * @psalm-pure */ public function getReflection(): ?\ReflectionFunctionAbstract; diff --git a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php index 38d23d85e..20511d6f8 100644 --- a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php +++ b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php @@ -34,13 +34,13 @@ public function handle(CallContext $context): mixed { // Resolve controller method $method = $context->getTarget()->getReflection(); + $path = $context->getTarget()->getPath(); if ($method === null) { $this->resolveFromPath or throw new TargetCallException( "Reflection not provided for target `{$context->getTarget()}`.", TargetCallException::NOT_FOUND, ); - $path = $context->getTarget()->getPath(); if (\count($path) !== 2) { throw new TargetCallException( "Invalid target path to resolve reflection for `{$context->getTarget()}`." @@ -49,7 +49,7 @@ public function handle(CallContext $context): mixed ); } - $method = ActionResolver::pathToReflection(\reset($path), \end($path)); + $method = ActionResolver::pathToReflection(\reset($path), \next($path)); } if ($method instanceof \ReflectionFunction) { @@ -62,7 +62,7 @@ public function handle(CallContext $context): mixed throw new TargetCallException("Action not found for target `{$context->getTarget()}`."); } - $controller = $this->container->get($method->getDeclaringClass()->getName()); + $controller = $this->container->get($path[0]); // Validate method and controller ActionResolver::validateControllerMethod($method, $controller); diff --git a/src/Hmvc/tests/Core/CoreTest.php b/src/Hmvc/tests/Core/CoreTest.php index 6cf9ec85b..b48eb210d 100644 --- a/src/Hmvc/tests/Core/CoreTest.php +++ b/src/Hmvc/tests/Core/CoreTest.php @@ -5,11 +5,14 @@ namespace Spiral\Tests\Core; use Spiral\Core\Exception\ControllerException; +use Spiral\Interceptors\Context\CallContext; +use Spiral\Interceptors\Context\Target; use Spiral\Testing\Attribute\TestScope; use Spiral\Testing\TestCase; use Spiral\Tests\Core\Fixtures\CleanController; use Spiral\Tests\Core\Fixtures\DummyController; use Spiral\Tests\Core\Fixtures\SampleCore; +use Spiral\Tests\Core\Fixtures\TestService; #[TestScope('http')] final class CoreTest extends TestCase @@ -189,4 +192,25 @@ public function testMissingDependency(): void [] )); } + + public function testCallActionReflectionMethodFromExtendedAbstractClass(): void + { + $handler = new SampleCore($this->getContainer()); + + $result = $handler->callAction(TestService::class, 'parentMethod', ['HELLO']); + + self::assertSame('hello', $result); + } + + public function testHandleReflectionMethodFromExtendedAbstractClass(): void + { + $handler = new SampleCore($this->getContainer()); + // Call Context + $ctx = (new CallContext(Target::fromPair(TestService::class, 'parentMethod'))) + ->withArguments(['HELLO']); + + $result = $handler->handle($ctx); + + self::assertSame('hello', $result); + } } diff --git a/src/Hmvc/tests/Core/Fixtures/AbstractTestService.php b/src/Hmvc/tests/Core/Fixtures/AbstractTestService.php new file mode 100644 index 000000000..2b32fbb85 --- /dev/null +++ b/src/Hmvc/tests/Core/Fixtures/AbstractTestService.php @@ -0,0 +1,13 @@ +counter; + } + + public static function toUpperCase(string $value): string + { + return \strtoupper($value); + } + + protected function toLowerCase(string $value): string + { + return \strtolower($value); + } +} diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index 890eeed98..1f8c8c653 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -100,6 +100,7 @@ public static function providePairs(): iterable yield 'public method' => [TestService::class, 'increment', true]; yield 'protected method' => [TestService::class, 'toLowerCase', true]; yield 'not existing' => [TestService::class, 'noExistingMethod', false]; + yield 'parent method' => [TestService::class, 'parentMethod', true]; yield 'not a class' => ['Spiral\Tests\Interceptors\Unit\Stub\FooBarBaz', 'noExistingMethod', false]; } diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php index b5424217f..c19172dd9 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php @@ -16,6 +16,19 @@ final class ReflectionHandlerTest extends TestCase { + public function testHandleReflectionMethodFromExtendedAbstractClass(): void + { + $c = new Container(); + $handler = new ReflectionHandler($c, false); + // Call Context + $ctx = (new CallContext(Target::fromPair(TestService::class, 'parentMethod'))) + ->withArguments(['HELLO']); + + $result = $handler->handle($ctx); + + self::assertSame('hello', $result); + } + public function testHandleReflectionFunction(): void { $c = new Container(); diff --git a/src/Hmvc/tests/Interceptors/Unit/Stub/AbstractTestService.php b/src/Hmvc/tests/Interceptors/Unit/Stub/AbstractTestService.php new file mode 100644 index 000000000..41c269151 --- /dev/null +++ b/src/Hmvc/tests/Interceptors/Unit/Stub/AbstractTestService.php @@ -0,0 +1,13 @@ + Date: Tue, 30 Apr 2024 21:43:30 +0400 Subject: [PATCH 76/84] Fix integration of Interceptors and Scopes into the Console component --- src/Console/src/Command.php | 46 +++++++++----------------- src/Console/src/CommandCoreFactory.php | 14 +++++--- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 23974a678..95e03d266 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -95,7 +95,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, $this->output)); - $arguments = ['input' => $this->input, 'output' => $this->output, 'command' => $this]; // Executing perform method with method injection $code = $this->container->get(ScopeInterface::class) @@ -106,24 +105,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int InputInterface::class => $input, OutputInterface::class => $output, ], - autowire: true, - ), - fn () => $this->buildCore()->callAction( - static::class, - $method, - [ - 'input' => $this->input, - 'output' => $this->output, - 'command' => $this, - ], + autowire: false, ), + function () use ($method) { + $core = $this->buildCore(); + $arguments = ['input' => $this->input, 'output' => $this->output, 'command' => $this]; + + return $core instanceof HandlerInterface + ? (int)$core->handle(new CallContext( + Target::fromReflection(new \ReflectionMethod(static::class, $method)), + $arguments, + )) + : (int)$core->callAction(static::class, $method, $arguments); + }, ); - // $code = $core instanceof HandlerInterface - // ? (int)$core->handle(new CallContext( - // Target::fromReflection(new \ReflectionMethod(static::class, $method)), - // $arguments, - // )) - // : (int)$core->callAction(static::class, $method, $arguments); $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, $this->output)); @@ -138,20 +133,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ protected function buildCore(): CoreInterface|HandlerInterface { - $factory = ContainerScope::getContainer()->get(CommandCoreFactory::class); - - return $factory->make($this->interceptors, $this->eventDispatcher); - - // $core = $this->container->get(CommandCore::class); - // - // $interceptableCore = (new InterceptorPipeline($this->eventDispatcher))->withCore($core); - // - // foreach ($this->interceptors as $interceptor) { - // $interceptableCore->addInterceptor($this->container->get($interceptor)); - // } - // $interceptableCore->addInterceptor($this->container->get(AttributeInterceptor::class)); - // - // return $interceptableCore; + return ContainerScope::getContainer() + ->get(CommandCoreFactory::class) + ->make($this->interceptors, $this->eventDispatcher); } protected function prepareInput(InputInterface $input): InputInterface diff --git a/src/Console/src/CommandCoreFactory.php b/src/Console/src/CommandCoreFactory.php index 0bfd70415..15a70261c 100644 --- a/src/Console/src/CommandCoreFactory.php +++ b/src/Console/src/CommandCoreFactory.php @@ -10,7 +10,9 @@ use Spiral\Core\Attribute\Scope; use Spiral\Core\CoreInterceptorInterface; use Spiral\Core\CoreInterface; -use Spiral\Core\InterceptableCore; +use Spiral\Core\InterceptorPipeline; +use Spiral\Interceptors\HandlerInterface; +use Spiral\Interceptors\InterceptorInterface; #[Scope('console.command')] final class CommandCoreFactory @@ -21,14 +23,16 @@ public function __construct( } /** - * @param array> $interceptors + * @param array> $interceptors */ - public function make(array $interceptors, ?EventDispatcherInterface $eventDispatcher = null): CoreInterface - { + public function make( + array $interceptors, + ?EventDispatcherInterface $eventDispatcher = null, + ): CoreInterface|HandlerInterface { /** @var CommandCore $core */ $core = $this->container->get(CommandCore::class); - $interceptableCore = new InterceptableCore($core, $eventDispatcher); + $interceptableCore = (new InterceptorPipeline($eventDispatcher))->withCore($core); foreach ($interceptors as $interceptor) { $interceptableCore->addInterceptor($this->container->get($interceptor)); From 568f4fbe28c2dddecc151cdb70697fd4af76aed2 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 2 May 2024 13:44:51 +0300 Subject: [PATCH 77/84] Add TestableKernel trait --- tests/app/src/TestApp.php | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/tests/app/src/TestApp.php b/tests/app/src/TestApp.php index 99575c3ce..d8087df1e 100644 --- a/tests/app/src/TestApp.php +++ b/tests/app/src/TestApp.php @@ -8,14 +8,16 @@ use Spiral\App\Bootloader\AuthBootloader; use Spiral\App\Bootloader\RoutesBootloader; use Spiral\Bootloader; -use Spiral\Core\Container; use Spiral\Framework\Kernel; use Spiral\Nyholm\Bootloader\NyholmBootloader; use Spiral\Stempler\Bootloader\StemplerBootloader; +use Spiral\Testing\Traits\TestableKernel; use Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader; class TestApp extends Kernel implements \Spiral\Testing\TestableKernelInterface { + use TestableKernel; + private array $disabledBootloaders = []; public const LOAD = [ @@ -110,21 +112,6 @@ protected function defineBootloaders(): array }); } - public function getContainer(): Container - { - return $this->container; - } - - public function getRegisteredDispatchers(): array - { - return $this->dispatchers; - } - - public function getRegisteredBootloaders(): array - { - return $this->bootloader->getClasses(); - } - /** * @param class-string<\Spiral\Boot\Bootloader\Bootloader> ...$bootloader */ From f6d01ec891e925acd1140a62f129d67980158cf8 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 7 May 2024 10:18:15 +0300 Subject: [PATCH 78/84] Add stable version of spiral/testing --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8ce3fd41b..f856b9a3a 100644 --- a/composer.json +++ b/composer.json @@ -136,7 +136,7 @@ "rector/rector": "0.18.1", "spiral/code-style": "^1.1", "spiral/nyholm-bridge": "^1.2", - "spiral/testing": "dev-feature/scopes as 2.8.0", + "spiral/testing": "^2.8", "spiral/validator": "^1.3", "google/protobuf": "^3.25", "symplify/monorepo-builder": "^10.2.7", From 218a4723f358de8d578e3166a6f89c6c8f532b59 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 7 May 2024 13:43:37 +0400 Subject: [PATCH 79/84] Explode Target::fromReflection to ::fromReflectionFunction and ::fromReflectionMethod --- src/Console/src/Command.php | 2 +- src/Events/src/EventDispatcher.php | 2 +- src/Hmvc/src/Interceptors/Context/Target.php | 71 ++++++++++++++++++- .../Interceptors/Context/TargetInterface.php | 2 + .../Interceptors/Unit/Context/TargetTest.php | 4 +- .../Unit/Handler/ReflectionHandlerTest.php | 40 ++++++----- 6 files changed, 96 insertions(+), 25 deletions(-) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index 95e03d266..fe11cf881 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -113,7 +113,7 @@ function () use ($method) { return $core instanceof HandlerInterface ? (int)$core->handle(new CallContext( - Target::fromReflection(new \ReflectionMethod(static::class, $method)), + Target::fromReflectionMethod(new \ReflectionMethod(static::class, $method)), $arguments, )) : (int)$core->callAction(static::class, $method, $arguments); diff --git a/src/Events/src/EventDispatcher.php b/src/Events/src/EventDispatcher.php index 67680962d..853f1722d 100644 --- a/src/Events/src/EventDispatcher.php +++ b/src/Events/src/EventDispatcher.php @@ -24,7 +24,7 @@ public function dispatch(object $event): object return $this->isLegacy ? $this->core->callAction($event::class, 'dispatch', ['event' => $event]) : $this->core->handle(new CallContext( - Target::fromReflection(new \ReflectionMethod($event::class, 'dispatch')), + Target::fromReflectionMethod(new \ReflectionMethod($event::class, 'dispatch')), ['event' => $event], )); } diff --git a/src/Hmvc/src/Interceptors/Context/Target.php b/src/Hmvc/src/Interceptors/Context/Target.php index 40180cd03..46047891e 100644 --- a/src/Hmvc/src/Interceptors/Context/Target.php +++ b/src/Hmvc/src/Interceptors/Context/Target.php @@ -4,6 +4,9 @@ namespace Spiral\Interceptors\Context; +/** + * @template TController + */ final class Target implements TargetInterface { /** @@ -13,6 +16,7 @@ final class Target implements TargetInterface private function __construct( private ?array $path = null, private ?\ReflectionFunctionAbstract $reflection = null, + private ?object $object = null, private readonly string $delimiter = '.', ) { } @@ -25,11 +29,49 @@ public function __toString(): string }; } - public static function fromReflection(\ReflectionFunctionAbstract $reflection): self + /** + * Create a target from a method reflection. + * + * @template T of object + * + * @param \ReflectionMethod $reflection + * @param class-string|T $classOrObject The original class name or object. + * It's required because the reflection may be referring to a parent class method. + * THe path will contain the original class name and the method name. + * + * @return self + */ + public static function fromReflectionMethod( + \ReflectionFunctionAbstract $reflection, + string|object $classOrObject, + ): self { + return \is_object($classOrObject) + ? new self( + path: [$classOrObject::class, $reflection->getName()], + reflection: $reflection, + object: $classOrObject, + ) + : new self( + path: [$classOrObject, $reflection->getName()], + reflection: $reflection, + ); + } + + /** + * Create a target from a function reflection. + * + * @return self + */ + public static function fromReflectionFunction(\ReflectionFunction $reflection): self { return new self(reflection: $reflection); } + /** + * Create a target from a path string without reflection. + * + * @return self + */ public static function fromPathString(string $path, string $delimiter = '.'): self { /** @psalm-suppress ArgumentTypeCoercion */ @@ -37,21 +79,38 @@ public static function fromPathString(string $path, string $delimiter = '.'): se } /** + * Create a target from a path array without reflection. + * * @param list $path + * @return self */ public static function fromPathArray(array $path, string $delimiter = '.'): self { return new self(path: $path, delimiter: $delimiter); } + /** + * Create a target from a controller and action pair. + * If the action is a method of the controller, the reflection will be set. + * + * @template T + * + * @param non-empty-string|class-string $controller + * @param non-empty-string $action + * + * @return ($controller is class-string ? self : self) + */ public static function fromPair(string $controller, string $action): self { $target = \method_exists($controller, $action) - ? self::fromReflection(new \ReflectionMethod($controller, $action)) + ? self::fromReflectionMethod(new \ReflectionMethod($controller, $action), $controller) : self::fromPathArray([$controller, $action]); return $target->withPath([$controller, $action]); } + /** + * @return list|list{class-string, non-empty-string} + */ public function getPath(): array { return match (true) { @@ -83,4 +142,12 @@ public function withReflection(?\ReflectionFunctionAbstract $reflection): static $clone->reflection = $reflection; return $clone; } + + /** + * @return TController|null + */ + public function getObject(): ?object + { + return $this->object; + } } diff --git a/src/Hmvc/src/Interceptors/Context/TargetInterface.php b/src/Hmvc/src/Interceptors/Context/TargetInterface.php index a5c49bbac..48e2b0e77 100644 --- a/src/Hmvc/src/Interceptors/Context/TargetInterface.php +++ b/src/Hmvc/src/Interceptors/Context/TargetInterface.php @@ -37,4 +37,6 @@ public function getReflection(): ?\ReflectionFunctionAbstract; * @param \ReflectionFunctionAbstract|null $reflection Pass null to remove the reflection. */ public function withReflection(?\ReflectionFunctionAbstract $reflection): static; + + public function getObject(): ?object; } diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index 1f8c8c653..db498e858 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -15,7 +15,7 @@ public function testCreateFromReflectionFunction(): void { $reflection = new \ReflectionFunction('print_r'); - $target = Target::fromReflection($reflection); + $target = Target::fromReflectionFunction($reflection); self::assertSame($reflection, $target->getReflection()); self::assertSame('print_r', (string)$target); @@ -25,7 +25,7 @@ public function testCreateFromReflectionMethod(): void { $reflection = new \ReflectionMethod($this, __FUNCTION__); - $target = Target::fromReflection($reflection); + $target = Target::fromReflectionMethod($reflection, __CLASS__); self::assertSame($reflection, $target->getReflection()); self::assertSame(__FUNCTION__, (string)$target); diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php index c19172dd9..dc32e5ea7 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php @@ -40,7 +40,7 @@ public function testHandleReflectionFunction(): void ->willReturn($c); $handler = new ReflectionHandler($container, false); // Call Context - $ctx = new CallContext(Target::fromReflection(new \ReflectionFunction('strtoupper'))); + $ctx = new CallContext(Target::fromReflectionFunction(new \ReflectionFunction('strtoupper'))); $ctx = $ctx->withArguments(['hello']); $result = $handler->handle($ctx); @@ -52,23 +52,25 @@ public function testHandleWrongReflectionFunction(): void { $handler = $this->createHandler(); // Call Context - $ctx = new CallContext(Target::fromReflection(new class extends \ReflectionFunctionAbstract { - /** @psalm-immutable */ - public function getName(): string - { - return 'testReflection'; - } - - public function __toString(): string - { - return 'really?'; - } - - public static function export(): void - { - // do nothing - } - })); + $ctx = new CallContext( + Target::fromPathArray(['testReflection']) + ->withReflection(new class extends \ReflectionFunctionAbstract { + /** @psalm-immutable */ + public function getName(): string + { + return 'testReflection'; + } + + public function __toString(): string + { + return 'really?'; + } + + public static function export(): void + { + // do nothing + } + })); self::expectException(TargetCallException::class); self::expectExceptionMessageMatches('/Action not found for target `testReflection`/'); @@ -124,7 +126,7 @@ public function testUsingResolver(): void { $handler = $this->createHandler(); $ctx = new CallContext( - Target::fromReflection(new \ReflectionFunction(fn (string $value):string => \strtoupper($value))) + Target::fromReflectionFunction(new \ReflectionFunction(fn (string $value):string => \strtoupper($value))) ); $ctx = $ctx->withArguments(['word' => 'world!', 'value' => 'hello']); From c1a7902100f20ded08f8e89e06659358094530dd Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 7 May 2024 18:15:09 +0400 Subject: [PATCH 80/84] Fix psalm issues --- src/Hmvc/src/Interceptors/Context/Target.php | 90 +++++++++++-------- .../Interceptors/Context/TargetInterface.php | 11 +-- .../Interceptors/Unit/Context/TargetTest.php | 6 +- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/Hmvc/src/Interceptors/Context/Target.php b/src/Hmvc/src/Interceptors/Context/Target.php index 46047891e..dca106022 100644 --- a/src/Hmvc/src/Interceptors/Context/Target.php +++ b/src/Hmvc/src/Interceptors/Context/Target.php @@ -5,28 +5,27 @@ namespace Spiral\Interceptors\Context; /** - * @template TController + * @template-covariant TController of object|null + * @implements TargetInterface */ final class Target implements TargetInterface { /** * @param list $path * @param \ReflectionFunctionAbstract|null $reflection + * @param TController|null $object */ private function __construct( - private ?array $path = null, + private array $path, private ?\ReflectionFunctionAbstract $reflection = null, - private ?object $object = null, - private readonly string $delimiter = '.', + private readonly ?object $object = null, + private string $delimiter = '.', ) { } public function __toString(): string { - return match (true) { - $this->path !== null => \implode($this->delimiter, $this->path), - $this->reflection !== null => $this->reflection->getName(), - }; + return \implode($this->delimiter, $this->path); } /** @@ -39,43 +38,66 @@ public function __toString(): string * It's required because the reflection may be referring to a parent class method. * THe path will contain the original class name and the method name. * + * @psalmif + * * @return self */ public static function fromReflectionMethod( \ReflectionFunctionAbstract $reflection, string|object $classOrObject, ): self { - return \is_object($classOrObject) + /** @var self $result */ + $result = \is_object($classOrObject) ? new self( path: [$classOrObject::class, $reflection->getName()], reflection: $reflection, object: $classOrObject, + delimiter: $reflection->isStatic() ? '::' : '->', ) : new self( path: [$classOrObject, $reflection->getName()], reflection: $reflection, + delimiter: $reflection->isStatic() ? '::' : '->', ); + return $result; } /** * Create a target from a function reflection. * + * @param list $path + * * @return self */ - public static function fromReflectionFunction(\ReflectionFunction $reflection): self + public static function fromReflectionFunction(\ReflectionFunction $reflection, array $path = []): self { - return new self(reflection: $reflection); + /** @var self $result */ + $result = new self(path: $path, reflection: $reflection); + return $result; + } + + /** + * Create a target from a closure. + * + * @param list $path + * + * @return self + */ + public static function fromClosure(\Closure $closure, array $path = []): self + { + return self::fromReflectionFunction(new \ReflectionFunction($closure), $path); } /** * Create a target from a path string without reflection. * + * @param non-empty-string $delimiter + * * @return self */ public static function fromPathString(string $path, string $delimiter = '.'): self { - /** @psalm-suppress ArgumentTypeCoercion */ - return new self(path: \explode($delimiter, $path), delimiter: $delimiter); + return self::fromPathArray(\explode($delimiter, $path), $delimiter); } /** @@ -86,48 +108,43 @@ public static function fromPathString(string $path, string $delimiter = '.'): se */ public static function fromPathArray(array $path, string $delimiter = '.'): self { - return new self(path: $path, delimiter: $delimiter); + /** @var self $result */ + $result = new self(path: $path, delimiter: $delimiter); + return $result; } /** * Create a target from a controller and action pair. * If the action is a method of the controller, the reflection will be set. * - * @template T + * @template T of object * - * @param non-empty-string|class-string $controller + * @param non-empty-string|class-string|T $controller * @param non-empty-string $action * - * @return ($controller is class-string ? self : self) + * @return ($controller is class-string|T ? self : self) */ - public static function fromPair(string $controller, string $action): self + public static function fromPair(string|object $controller, string $action): self { - $target = \method_exists($controller, $action) - ? self::fromReflectionMethod(new \ReflectionMethod($controller, $action), $controller) - : self::fromPathArray([$controller, $action]); - return $target->withPath([$controller, $action]); + /** @psalm-suppress ArgumentTypeCoercion */ + if (\is_object($controller) || \method_exists($controller, $action)) { + /** @var T|class-string $controller */ + return self::fromReflectionMethod(new \ReflectionMethod($controller, $action), $controller); + } + + return self::fromPathArray([$controller, $action]); } - /** - * @return list|list{class-string, non-empty-string} - */ public function getPath(): array { - return match (true) { - $this->path !== null => $this->path, - $this->reflection instanceof \ReflectionMethod => [ - $this->reflection->getDeclaringClass()->getName(), - $this->reflection->getName(), - ], - $this->reflection instanceof \ReflectionFunction => [$this->reflection->getName()], - default => [], - }; + return $this->path; } - public function withPath(array $path): static + public function withPath(array $path, ?string $delimiter = null): static { $clone = clone $this; $clone->path = $path; + $clone->delimiter = $delimiter ?? $clone->delimiter; return $clone; } @@ -143,9 +160,6 @@ public function withReflection(?\ReflectionFunctionAbstract $reflection): static return $clone; } - /** - * @return TController|null - */ public function getObject(): ?object { return $this->object; diff --git a/src/Hmvc/src/Interceptors/Context/TargetInterface.php b/src/Hmvc/src/Interceptors/Context/TargetInterface.php index 48e2b0e77..26f8b07b4 100644 --- a/src/Hmvc/src/Interceptors/Context/TargetInterface.php +++ b/src/Hmvc/src/Interceptors/Context/TargetInterface.php @@ -9,18 +9,21 @@ /** * The target may be a concrete reflection or an alias. * In both cases, you can get a path to the target. + * + * @template-covariant TController of object|null */ interface TargetInterface extends Stringable { /** - * @return list + * @return list|list{class-string, non-empty-string} */ public function getPath(): array; /** * @param list $path + * @param string|null $delimiter The delimiter to use when converting the path to a string. */ - public function withPath(array $path): static; + public function withPath(array $path, ?string $delimiter = null): static; /** * Returns the reflection of the target. @@ -34,9 +37,7 @@ public function withPath(array $path): static; public function getReflection(): ?\ReflectionFunctionAbstract; /** - * @param \ReflectionFunctionAbstract|null $reflection Pass null to remove the reflection. + * @return TController|null */ - public function withReflection(?\ReflectionFunctionAbstract $reflection): static; - public function getObject(): ?object; } diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index db498e858..faeb44fd6 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -15,10 +15,10 @@ public function testCreateFromReflectionFunction(): void { $reflection = new \ReflectionFunction('print_r'); - $target = Target::fromReflectionFunction($reflection); + $target = Target::fromReflectionFunction($reflection, ['print_r-path']); self::assertSame($reflection, $target->getReflection()); - self::assertSame('print_r', (string)$target); + self::assertSame('print_r-path', (string)$target); } public function testCreateFromReflectionMethod(): void @@ -28,7 +28,7 @@ public function testCreateFromReflectionMethod(): void $target = Target::fromReflectionMethod($reflection, __CLASS__); self::assertSame($reflection, $target->getReflection()); - self::assertSame(__FUNCTION__, (string)$target); + self::assertSame(__CLASS__ . '->' . __FUNCTION__, (string)$target); } public function testWithReflectionFunction(): void From 12b822aa816fb5bcc99a7d3e87ebb623a5ba670e Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 7 May 2024 18:51:29 +0400 Subject: [PATCH 81/84] Add tests; update core and reflection handler --- src/Hmvc/src/Core/AbstractCore.php | 16 +++++---- .../Interceptors/Context/TargetInterface.php | 15 +++++++-- .../Handler/ReflectionHandler.php | 2 +- src/Hmvc/tests/Core/CoreTest.php | 18 ++++++++++ .../Interceptors/Unit/Context/TargetTest.php | 33 ++++++++++++++++++- .../Unit/Handler/ReflectionHandlerTest.php | 20 +++++++++++ 6 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/Hmvc/src/Core/AbstractCore.php b/src/Hmvc/src/Core/AbstractCore.php index 6c770a26e..09029a8e7 100644 --- a/src/Hmvc/src/Core/AbstractCore.php +++ b/src/Hmvc/src/Core/AbstractCore.php @@ -33,9 +33,11 @@ public function __construct( // TODO: can we simplify this? // resolver is usually the container itself /** @psalm-suppress MixedAssignment */ - $this->resolver = $container - ->get(InvokerInterface::class) - ->invoke(static fn (#[Proxy] ResolverInterface $resolver) => $resolver); + $this->resolver = $container instanceof ResolverInterface + ? $container + : $container + ->get(InvokerInterface::class) + ->invoke(static fn (#[Proxy] ResolverInterface $resolver) => $resolver); } /** @@ -49,7 +51,7 @@ public function callAction(string $controller, string $action, array $parameters // Validate method ActionResolver::validateControllerMethod($method); - return $this->invoke($controller, $method, $parameters); + return $this->invoke(null, $controller, $method, $parameters); } public function handle(CallContext $context): mixed @@ -57,7 +59,7 @@ public function handle(CallContext $context): mixed $target = $context->getTarget(); $reflection = $target->getReflection(); return $reflection instanceof \ReflectionMethod - ? $this->invoke($target->getPath()[0], $reflection, $context->getArguments()) + ? $this->invoke($target->getObject(), $target->getPath()[0], $reflection, $context->getArguments()) : $this->callAction($target->getPath()[0], $target->getPath()[1], $context->getArguments()); } @@ -82,7 +84,7 @@ protected function resolveArguments(\ReflectionMethod $method, array $parameters /** * @throws \Throwable */ - private function invoke(string $class, \ReflectionMethod $method, array $arguments): mixed + private function invoke(?object $object, string $class, \ReflectionMethod $method, array $arguments): mixed { try { $args = $this->resolveArguments($method, $arguments); @@ -105,6 +107,6 @@ private function invoke(string $class, \ReflectionMethod $method, array $argumen ); } - return $method->invokeArgs($this->container->get($class), $args); + return $method->invokeArgs($object ?? $this->container->get($class), $args); } } diff --git a/src/Hmvc/src/Interceptors/Context/TargetInterface.php b/src/Hmvc/src/Interceptors/Context/TargetInterface.php index 26f8b07b4..fbf4b097b 100644 --- a/src/Hmvc/src/Interceptors/Context/TargetInterface.php +++ b/src/Hmvc/src/Interceptors/Context/TargetInterface.php @@ -15,6 +15,10 @@ interface TargetInterface extends Stringable { /** + * Returns the path to the target. + * If the target was created from a method reflection, the path will contain + * the class name and the method name by default. + * * @return list|list{class-string, non-empty-string} */ public function getPath(): array; @@ -29,14 +33,21 @@ public function withPath(array $path, ?string $delimiter = null): static; * Returns the reflection of the target. * * It may be {@see \ReflectionFunction} or {@see \ReflectionMethod}. - * Note: {@see \ReflectionMethod::getDeclaringClass()} may return a parent class, but not the class used - * when the target was created. Use {@see Target::getPath()} to get the original target class. + * + * NOTE: + * The method {@see \ReflectionMethod::getDeclaringClass()} may return a parent class, + * but not the class used when the target was created. + * Use {@see getObject()} or {@see Target::getPath()}[0] to get the original object or class name. * * @psalm-pure */ public function getReflection(): ?\ReflectionFunctionAbstract; /** + * Returns the object associated with the target. + * + * If the object is present, it always corresponds to the method reflection from {@see getReflection()}. + * * @return TController|null */ public function getObject(): ?object; diff --git a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php index 20511d6f8..ff65a0c6a 100644 --- a/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php +++ b/src/Hmvc/src/Interceptors/Handler/ReflectionHandler.php @@ -62,7 +62,7 @@ public function handle(CallContext $context): mixed throw new TargetCallException("Action not found for target `{$context->getTarget()}`."); } - $controller = $this->container->get($path[0]); + $controller = $context->getTarget()->getObject() ?? $this->container->get($path[0]); // Validate method and controller ActionResolver::validateControllerMethod($method, $controller); diff --git a/src/Hmvc/tests/Core/CoreTest.php b/src/Hmvc/tests/Core/CoreTest.php index b48eb210d..d45c15302 100644 --- a/src/Hmvc/tests/Core/CoreTest.php +++ b/src/Hmvc/tests/Core/CoreTest.php @@ -4,9 +4,13 @@ namespace Spiral\Tests\Core; +use Psr\Container\ContainerInterface; +use Spiral\Core\Container; use Spiral\Core\Exception\ControllerException; +use Spiral\Core\ResolverInterface; use Spiral\Interceptors\Context\CallContext; use Spiral\Interceptors\Context\Target; +use Spiral\Interceptors\Handler\ReflectionHandler; use Spiral\Testing\Attribute\TestScope; use Spiral\Testing\TestCase; use Spiral\Tests\Core\Fixtures\CleanController; @@ -213,4 +217,18 @@ public function testHandleReflectionMethodFromExtendedAbstractClass(): void self::assertSame('hello', $result); } + + public function testHandleReflectionMethodWithObject(): void + { + $c = new Container(); + $handler = new SampleCore($c); + // Call Context + $service = new \Spiral\Tests\Interceptors\Unit\Stub\TestService(); + $ctx = (new CallContext(Target::fromPair($service, 'parentMethod')->withPath(['foo', 'bar']))) + ->withArguments(['HELLO']); + + $result = $handler->handle($ctx); + + self::assertSame('hello', $result); + } } diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index faeb44fd6..6f9c7f267 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -19,9 +19,28 @@ public function testCreateFromReflectionFunction(): void self::assertSame($reflection, $target->getReflection()); self::assertSame('print_r-path', (string)$target); + self::assertNull($target->getObject()); } - public function testCreateFromReflectionMethod(): void + public function testCreateFromClosure(): void + { + $target = Target::fromClosure(\print_r(...), ['print_r-path']); + + self::assertNotNull($target->getReflection()); + self::assertSame('print_r-path', (string)$target); + self::assertNull($target->getObject()); + } + + public function testCreateFromClosureWithContext(): void + { + $target = Target::fromClosure($this->{__FUNCTION__}(...), ['print_r-path']); + + self::assertNotNull($target->getReflection()); + self::assertSame('print_r-path', (string)$target); + self::assertNull($target->getObject()); + } + + public function testCreateFromReflectionMethodClassName(): void { $reflection = new \ReflectionMethod($this, __FUNCTION__); @@ -29,6 +48,18 @@ public function testCreateFromReflectionMethod(): void self::assertSame($reflection, $target->getReflection()); self::assertSame(__CLASS__ . '->' . __FUNCTION__, (string)$target); + self::assertNull($target->getObject()); + } + + public function testCreateFromReflectionMethodObject(): void + { + $reflection = new \ReflectionMethod($this, __FUNCTION__); + + $target = Target::fromReflectionMethod($reflection, $this); + + self::assertSame($reflection, $target->getReflection()); + self::assertSame(__CLASS__ . '->' . __FUNCTION__, (string)$target); + self::assertNotNull($target->getObject()); } public function testWithReflectionFunction(): void diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php index dc32e5ea7..6627bdd47 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php @@ -48,6 +48,26 @@ public function testHandleReflectionFunction(): void self::assertSame('HELLO', $result); } + public function testHandleReflectionMethodWithObject(): void + { + $c = new Container(); + $container = self::createMock(ContainerInterface::class); + $container + ->expects(self::once()) + ->method('get') + ->with(ResolverInterface::class) + ->willReturn($c); + $handler = new ReflectionHandler($container, false); + // Call Context + $service = new TestService(); + $ctx = (new CallContext(Target::fromPair($service, 'parentMethod'))) + ->withArguments(['HELLO']); + + $result = $handler->handle($ctx); + + self::assertSame('hello', $result); + } + public function testHandleWrongReflectionFunction(): void { $handler = $this->createHandler(); From ad847fdff97866c10f7c4429d8b5cc8b4f2286cd Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 7 May 2024 19:24:24 +0400 Subject: [PATCH 82/84] Fix integration with the framework --- src/Console/src/Command.php | 2 +- src/Events/src/EventDispatcher.php | 2 +- .../tests/Interceptors/Unit/Context/TargetTest.php | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Console/src/Command.php b/src/Console/src/Command.php index fe11cf881..2a6ab2772 100644 --- a/src/Console/src/Command.php +++ b/src/Console/src/Command.php @@ -113,7 +113,7 @@ function () use ($method) { return $core instanceof HandlerInterface ? (int)$core->handle(new CallContext( - Target::fromReflectionMethod(new \ReflectionMethod(static::class, $method)), + Target::fromPair($this, $method), $arguments, )) : (int)$core->callAction(static::class, $method, $arguments); diff --git a/src/Events/src/EventDispatcher.php b/src/Events/src/EventDispatcher.php index 853f1722d..47e5b2231 100644 --- a/src/Events/src/EventDispatcher.php +++ b/src/Events/src/EventDispatcher.php @@ -24,7 +24,7 @@ public function dispatch(object $event): object return $this->isLegacy ? $this->core->callAction($event::class, 'dispatch', ['event' => $event]) : $this->core->handle(new CallContext( - Target::fromReflectionMethod(new \ReflectionMethod($event::class, 'dispatch')), + Target::fromPair($event, 'dispatch'), ['event' => $event], )); } diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index 6f9c7f267..d6642193e 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -143,12 +143,25 @@ public function testCreateFromPair(string $controller, string $action, bool $has self::assertSame([$controller, $action], $target->getPath()); $reflection = $target->getReflection(); self::assertSame($hasReflection, $reflection !== null); + self::assertNull($target->getObject()); if ($hasReflection) { self::assertInstanceOf(\ReflectionMethod::class, $reflection); self::assertSame($action, $reflection->getName()); } } + public function testCreateFromObject(): void + { + $service = new TestService(); + $target = Target::fromPair($service, 'parentMethod'); + + self::assertSame([TestService::class, 'parentMethod'], $target->getPath()); + $reflection = $target->getReflection(); + self::assertInstanceOf(\ReflectionMethod::class, $reflection); + self::assertSame('parentMethod', $reflection->getName()); + self::assertSame($service, $target->getObject()); + } + public function testCreateFromPathStringDefaultSeparator(): void { $str = 'foo.bar.baz'; From 00d721c9ce795c5b9271d61100196492c5099f9a Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 7 May 2024 20:20:36 +0400 Subject: [PATCH 83/84] Cleanup --- src/Hmvc/src/Interceptors/Context/Target.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Hmvc/src/Interceptors/Context/Target.php b/src/Hmvc/src/Interceptors/Context/Target.php index dca106022..b246fa78b 100644 --- a/src/Hmvc/src/Interceptors/Context/Target.php +++ b/src/Hmvc/src/Interceptors/Context/Target.php @@ -153,13 +153,6 @@ public function getReflection(): ?\ReflectionFunctionAbstract return $this->reflection; } - public function withReflection(?\ReflectionFunctionAbstract $reflection): static - { - $clone = clone $this; - $clone->reflection = $reflection; - return $clone; - } - public function getObject(): ?object { return $this->object; From 703275a1a9fc28409d43c62461f96f9b228b49a5 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Wed, 8 May 2024 21:04:53 +0400 Subject: [PATCH 84/84] Remove outdated tests --- .../Interceptors/Unit/Context/TargetTest.php | 20 ------------- .../Unit/Handler/ReflectionHandlerTest.php | 30 ------------------- 2 files changed, 50 deletions(-) diff --git a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php index d6642193e..f11c390bb 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Context/TargetTest.php @@ -62,26 +62,6 @@ public function testCreateFromReflectionMethodObject(): void self::assertNotNull($target->getObject()); } - public function testWithReflectionFunction(): void - { - $reflection = new \ReflectionFunction('print_r'); - - $target = Target::fromPathArray(['foo', 'bar']); - $target2 = $target->withReflection($reflection); - - // Immutability - self::assertNotSame($target, $target2); - // First target is not changed - self::assertSame(['foo', 'bar'], $target->getPath()); - self::assertNull($target->getReflection()); - self::assertSame('foo.bar', (string)$target); - // Second target is changed - self::assertSame(['foo', 'bar'], $target2->getPath()); - self::assertSame($reflection, $target2->getReflection()); - // Reflection does'n affect the string representation if path is set - self::assertSame('foo.bar', (string)$target); - } - public function testCreateFromPathStringWithPath(): void { $str = 'foo.bar.baz'; diff --git a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php index 6627bdd47..cf00c25e9 100644 --- a/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php +++ b/src/Hmvc/tests/Interceptors/Unit/Handler/ReflectionHandlerTest.php @@ -68,36 +68,6 @@ public function testHandleReflectionMethodWithObject(): void self::assertSame('hello', $result); } - public function testHandleWrongReflectionFunction(): void - { - $handler = $this->createHandler(); - // Call Context - $ctx = new CallContext( - Target::fromPathArray(['testReflection']) - ->withReflection(new class extends \ReflectionFunctionAbstract { - /** @psalm-immutable */ - public function getName(): string - { - return 'testReflection'; - } - - public function __toString(): string - { - return 'really?'; - } - - public static function export(): void - { - // do nothing - } - })); - - self::expectException(TargetCallException::class); - self::expectExceptionMessageMatches('/Action not found for target `testReflection`/'); - - $handler->handle($ctx); - } - public function testWithoutResolvingFromPathAndReflection(): void { $container = self::createMock(ContainerInterface::class);