From 54dfbacb5dc28fc37ad7b7d7d5c7f833ed8f8369 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 11:03:23 +0200 Subject: [PATCH 01/19] Add advanced callable resolver --- Slim/AdvancedCallableResolver.php | 183 ++++++++++++++++++ .../AdvancedCallableResolverInterface.php | 31 +++ 2 files changed, 214 insertions(+) create mode 100644 Slim/AdvancedCallableResolver.php create mode 100644 Slim/Interfaces/AdvancedCallableResolverInterface.php diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php new file mode 100644 index 000000000..6544dc53d --- /dev/null +++ b/Slim/AdvancedCallableResolver.php @@ -0,0 +1,183 @@ +container = $container; + } + + /** + * {@inheritdoc} + */ + public function resolve($toResolve): callable + { + $resolved = $toResolve; + + if (!is_callable($toResolve) && is_string($toResolve)) { + $class = $toResolve; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + + $resolved = [$instance, $method ?? '__invoke']; + } + + if (!is_callable($resolved)) { + throw new RuntimeException(sprintf( + '%s is not resolvable', + is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + )); + } + + if ($this->container && $resolved instanceof Closure) { + $resolved = $resolved->bindTo($this->container); + } + + return $resolved; + } + + /** + * {@inheritdoc} + */ + public function resolveRoute($toResolve): callable + { + $resolved = $toResolve; + + if (!is_callable($toResolve) && is_string($toResolve)) { + $class = $toResolve; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + + // For a class that implements RequestHandlerInterface, we will call handle() + // if no method has been specified explicitly + if ($instance instanceof RequestHandlerInterface && $method === null) { + $method = 'handle'; + } + + $resolved = [$instance, $method ?? '__invoke']; + } + + if ($resolved instanceof RequestHandlerInterface) { + $resolved = [$resolved, 'handle']; + } + + if (!is_callable($resolved)) { + throw new RuntimeException(sprintf( + '%s is not resolvable', + is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + )); + } + + if ($this->container && $resolved instanceof Closure) { + $resolved = $resolved->bindTo($this->container); + } + + return $resolved; + } + + /** + * {@inheritdoc} + */ + public function resolveMiddleware($toResolve): callable + { + $resolved = $toResolve; + + if (!is_callable($toResolve) && is_string($toResolve)) { + $class = $toResolve; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + + if ($instance instanceof MiddlewareInterface && $method === null) { + $method = 'process'; + } + + $resolved = [$instance, $method ?? '__invoke']; + } + + if ($resolved instanceof MiddlewareInterface) { + $resolved = [$resolved, 'process']; + } + + if (!is_callable($resolved)) { + throw new RuntimeException(sprintf( + '%s is not resolvable', + is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + )); + } + + if ($this->container && $resolved instanceof Closure) { + $resolved = $resolved->bindTo($this->container); + } + + return $resolved; + } +} diff --git a/Slim/Interfaces/AdvancedCallableResolverInterface.php b/Slim/Interfaces/AdvancedCallableResolverInterface.php new file mode 100644 index 000000000..89684fc7c --- /dev/null +++ b/Slim/Interfaces/AdvancedCallableResolverInterface.php @@ -0,0 +1,31 @@ + Date: Fri, 9 Aug 2019 11:14:17 +0200 Subject: [PATCH 02/19] Add AdvancedCallableResolver Support --- Slim/App.php | 4 +- Slim/MiddlewareDispatcher.php | 79 ++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/Slim/App.php b/Slim/App.php index d70846f87..8f9a947e7 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -60,7 +60,7 @@ public function __construct( ) { parent::__construct( $responseFactory, - $callableResolver ?? new CallableResolver($container), + $callableResolver ?? new AdvancedCallableResolver($container), $container, $routeCollector ); @@ -68,7 +68,7 @@ public function __construct( $this->routeResolver = $routeResolver ?? new RouteResolver($this->routeCollector); $routeRunner = new RouteRunner($this->routeResolver, $this->routeCollector->getRouteParser()); - $this->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $container); + $this->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $container, $this->callableResolver); } /** diff --git a/Slim/MiddlewareDispatcher.php b/Slim/MiddlewareDispatcher.php index 7d4906da1..410f1f1e9 100644 --- a/Slim/MiddlewareDispatcher.php +++ b/Slim/MiddlewareDispatcher.php @@ -16,6 +16,8 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; +use Slim\Interfaces\AdvancedCallableResolverInterface; +use Slim\Interfaces\CallableResolverInterface; class MiddlewareDispatcher implements RequestHandlerInterface { @@ -32,15 +34,23 @@ class MiddlewareDispatcher implements RequestHandlerInterface protected $container; /** - * @param RequestHandlerInterface $kernel - * @param ContainerInterface|null $container + * @var CallableResolverInterface|null + */ + protected $callableResolver; + + /** + * @param RequestHandlerInterface $kernel + * @param ContainerInterface|null $container + * @param CallableResolverInterface|null $callableResolver */ public function __construct( RequestHandlerInterface $kernel, - ?ContainerInterface $container = null + ?ContainerInterface $container = null, + ?CallableResolverInterface $callableResolver = null ) { $this->seedMiddlewareStack($kernel); $this->container = $container; + $this->callableResolver = $callableResolver; } /** @@ -141,53 +151,62 @@ public function handle(ServerRequestInterface $request): ResponseInterface public function addDeferred(string $middleware): self { $next = $this->tip; - $this->tip = new class($middleware, $next, $this->container) implements RequestHandlerInterface + $this->tip = new class($middleware, $next, $this->container, $this->callableResolver) implements RequestHandlerInterface { private $middleware; private $next; private $container; + private $callableResolver; public function __construct( string $middleware, RequestHandlerInterface $next, - ?ContainerInterface $container = null + ?ContainerInterface $container = null, + ?CallableResolverInterface $callableResolver = null ) { $this->middleware = $middleware; $this->next = $next; $this->container = $container; + $this->callableResolver = $callableResolver; } public function handle(ServerRequestInterface $request): ResponseInterface { - $resolved = $this->middleware; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { - $resolved = $matches[1]; - $method = $matches[2]; - } - - if ($this->container && $this->container->has($resolved)) { - $instance = $this->container->get($resolved); - if ($instance instanceof MiddlewareInterface) { - return $instance->process($request, $this->next); + if ($this->callableResolver === null) { + $resolved = $this->middleware; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { + $resolved = $matches[1]; + $method = $matches[2]; } - } elseif (!function_exists($resolved)) { - if (!class_exists($resolved)) { - throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); + + if ($this->container && $this->container->has($resolved)) { + $instance = $this->container->get($resolved); + if ($instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + } elseif (!function_exists($resolved)) { + if (!class_exists($resolved)) { + throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); + } + $instance = new $resolved($this->container); } - $instance = new $resolved($this->container); - } - if ($instance && $instance instanceof MiddlewareInterface) { - return $instance->process($request, $this->next); - } + if ($instance && $instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } - $callable = $instance ?? $resolved; - if ($instance && $method) { - $callable = [$instance, $method]; + $callable = $instance ?? $resolved; + if ($instance && $method) { + $callable = [$instance, $method]; + } + } elseif ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveMiddleware($this->middleware); + } else { + $callable = null; } if (!is_callable($callable)) { From f5bbb86e9bfd46c0d71a9f12fa7aa3e7134ce939 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 11:14:28 +0200 Subject: [PATCH 03/19] Fix test case --- tests/AppTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/AppTest.php b/tests/AppTest.php index d987a4315..57239aed2 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -19,6 +19,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; +use Slim\AdvancedCallableResolver; use Slim\App; use Slim\CallableResolver; use Slim\Exception\HttpMethodNotAllowedException; @@ -79,7 +80,7 @@ public function testCreatesCallableResolverWhenNull() { $responseFactoryProphecy = $this->prophesize(ResponseFactoryInterface::class); $containerProphecy = $this->prophesize(ContainerInterface::class); - $callableResolver = new CallableResolver($containerProphecy->reveal()); + $callableResolver = new AdvancedCallableResolver($containerProphecy->reveal()); $app = new App($responseFactoryProphecy->reveal(), $containerProphecy->reveal(), null); $this->assertEquals($callableResolver, $app->getCallableResolver()); From cc9f713a424b2f4b0dbaa7a4fc61918865da069e Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 11:18:09 +0200 Subject: [PATCH 04/19] Add AdvancedCallableResolver Support --- Slim/Routing/Route.php | 7 ++++++- Slim/Routing/RouteGroup.php | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Slim/Routing/Route.php b/Slim/Routing/Route.php index 9afd0523a..2e7b92baf 100644 --- a/Slim/Routing/Route.php +++ b/Slim/Routing/Route.php @@ -17,6 +17,7 @@ use Psr\Http\Server\RequestHandlerInterface; use Slim\Handlers\Strategies\RequestHandler; use Slim\Handlers\Strategies\RequestResponse; +use Slim\Interfaces\AdvancedCallableResolverInterface; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\InvocationStrategyInterface; use Slim\Interfaces\RequestHandlerInvocationStrategyInterface; @@ -360,7 +361,11 @@ protected function appendGroupMiddlewareToRoute(): void */ public function handle(ServerRequestInterface $request): ResponseInterface { - $callable = $this->callableResolver->resolve($this->callable); + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveRoute($this->callable); + } else { + $callable = $this->callableResolver->resolve($this->callable); + } $strategy = $this->invocationStrategy; if (is_array($callable) diff --git a/Slim/Routing/RouteGroup.php b/Slim/Routing/RouteGroup.php index af3d60eb6..4801afe76 100644 --- a/Slim/Routing/RouteGroup.php +++ b/Slim/Routing/RouteGroup.php @@ -10,6 +10,7 @@ namespace Slim\Routing; use Psr\Http\Server\MiddlewareInterface; +use Slim\Interfaces\AdvancedCallableResolverInterface; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\RouteCollectorProxyInterface; use Slim\Interfaces\RouteGroupInterface; @@ -65,7 +66,11 @@ public function __construct( */ public function collectRoutes(): RouteGroupInterface { - $callable = $this->callableResolver->resolve($this->callable); + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveRoute($this->callable); + } else { + $callable = $this->callableResolver->resolve($this->callable); + } $callable($this->routeCollectorProxy); return $this; } From d8983bef07cf83e6f6157afb42c95c7ae93c81e4 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 11:22:20 +0200 Subject: [PATCH 05/19] Initialize `$callable` --- Slim/MiddlewareDispatcher.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Slim/MiddlewareDispatcher.php b/Slim/MiddlewareDispatcher.php index 410f1f1e9..8f2f4882d 100644 --- a/Slim/MiddlewareDispatcher.php +++ b/Slim/MiddlewareDispatcher.php @@ -172,6 +172,7 @@ public function __construct( public function handle(ServerRequestInterface $request): ResponseInterface { + $callable = null; if ($this->callableResolver === null) { $resolved = $this->middleware; $instance = null; @@ -205,8 +206,6 @@ public function handle(ServerRequestInterface $request): ResponseInterface } } elseif ($this->callableResolver instanceof AdvancedCallableResolverInterface) { $callable = $this->callableResolver->resolveMiddleware($this->middleware); - } else { - $callable = null; } if (!is_callable($callable)) { From 5a1622a79e24d628bf0e142b73accd7c0100c62f Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 14:20:33 +0200 Subject: [PATCH 06/19] Add test cases for the advanced callable resolver --- tests/AdvancedCallableResolverTest.php | 525 +++++++++++++++++++++++++ tests/Mocks/MiddlewareTest.php | 40 ++ 2 files changed, 565 insertions(+) create mode 100644 tests/AdvancedCallableResolverTest.php create mode 100644 tests/Mocks/MiddlewareTest.php diff --git a/tests/AdvancedCallableResolverTest.php b/tests/AdvancedCallableResolverTest.php new file mode 100644 index 000000000..f9e70cb06 --- /dev/null +++ b/tests/AdvancedCallableResolverTest.php @@ -0,0 +1,525 @@ +containerProphecy = $this->prophesize(ContainerInterface::class); + $this->containerProphecy->has(Argument::type('string'))->willReturn(false); + } + + public function testClosure() + { + $test = function () { + return true; + }; + $resolver = new AdvancedCallableResolver(); // No container injected + $callable = $resolver->resolve($test); + $callableRoute = $resolver->resolveRoute($test); + $callableMiddleware = $resolver->resolveMiddleware($test); + + $this->assertEquals(true, $callable()); + $this->assertEquals(true, $callableRoute()); + $this->assertEquals(true, $callableMiddleware()); + } + + public function testClosureContainer() + { + $this->containerProphecy->has('ultimateAnswer')->willReturn(true); + $this->containerProphecy->get('ultimateAnswer')->willReturn(42); + + $that = $this; + $test = function () use ($that) { + $that->assertInstanceOf(ContainerInterface::class, $this); + + /** @var ContainerInterface $this */ + return $this->get('ultimateAnswer'); + }; + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $callable = $resolver->resolve($test); + $callableRoute = $resolver->resolveRoute($test); + $callableMiddleware = $resolver->resolveMiddleware($test); + + $this->assertEquals(42, $callable()); + $this->assertEquals(42, $callableRoute()); + $this->assertEquals(42, $callableMiddleware()); + } + + public function testFunctionName() + { + $resolver = new AdvancedCallableResolver(); // No container injected + $callable = $resolver->resolve(__NAMESPACE__.'\testAdvancedCallable'); + $callableRoute = $resolver->resolveRoute(__NAMESPACE__.'\testAdvancedCallable'); + $callableMiddleware = $resolver->resolveMiddleware(__NAMESPACE__.'\testAdvancedCallable'); + + $this->assertEquals(true, $callable()); + $this->assertEquals(true, $callableRoute()); + $this->assertEquals(true, $callableMiddleware()); + } + + public function testObjMethodArray() + { + $obj = new CallableTest(); + $resolver = new AdvancedCallableResolver(); // No container injected + $callable = $resolver->resolve([$obj, 'toCall']); + $callableRoute = $resolver->resolveRoute([$obj, 'toCall']); + $callableMiddleware = $resolver->resolveMiddleware([$obj, 'toCall']); + + $callable(); + $this->assertEquals(1, CallableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, CallableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, CallableTest::$CalledCount); + } + + public function testSlimCallable() + { + $resolver = new AdvancedCallableResolver(); // No container injected + $callable = $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); + $callableRoute = $resolver->resolveRoute('Slim\Tests\Mocks\CallableTest:toCall'); + $callableMiddleware = $resolver->resolveMiddleware('Slim\Tests\Mocks\CallableTest:toCall'); + + $callable(); + $this->assertEquals(1, CallableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, CallableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, CallableTest::$CalledCount); + } + + public function testSlimCallableContainer() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($container, CallableTest::$CalledContainer); + + CallableTest::$CalledContainer = null; + $resolver->resolveRoute('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($container, CallableTest::$CalledContainer); + + CallableTest::$CalledContainer = null; + $resolver->resolveMiddleware('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($container, CallableTest::$CalledContainer); + } + + public function testContainer() + { + $this->containerProphecy->has('callable_service')->willReturn(true); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + + $resolver = new AdvancedCallableResolver($container); + $callable = $resolver->resolve('callable_service:toCall'); + $callableRoute = $resolver->resolveRoute('callable_service:toCall'); + $callableMiddleware = $resolver->resolveMiddleware('callable_service:toCall'); + + $callable(); + $this->assertEquals(1, CallableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, CallableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, CallableTest::$CalledCount); + } + + public function testResolutionToAnInvokableClassInContainer() + { + $this->containerProphecy->has('an_invokable')->willReturn(true); + $this->containerProphecy->get('an_invokable')->willReturn(new InvokableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + + $resolver = new AdvancedCallableResolver($container); + $callable = $resolver->resolve('an_invokable'); + $callableRoute = $resolver->resolveRoute('an_invokable'); + $callableMiddleware = $resolver->resolveMiddleware('an_invokable'); + + $callable(); + $this->assertEquals(1, InvokableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, InvokableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, InvokableTest::$CalledCount); + } + + public function testResolutionToAnInvokableClass() + { + $resolver = new AdvancedCallableResolver(); // No container injected + $callable = $resolver->resolve('Slim\Tests\Mocks\InvokableTest'); + $callableRoute = $resolver->resolveRoute('Slim\Tests\Mocks\InvokableTest'); + $callableMiddleware = $resolver->resolveMiddleware('Slim\Tests\Mocks\InvokableTest'); + + $callable(); + $this->assertEquals(1, InvokableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, InvokableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, InvokableTest::$CalledCount); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Slim\Tests\Mocks\RequestHandlerTest is not resolvable + */ + public function testResolutionToAPsrRequestHandlerClass() + { + $resolver = new AdvancedCallableResolver(); // No container injected + $resolver->resolve(RequestHandlerTest::class); + } + + public function testRouteResolutionToAPsrRequestHandlerClass() + { + $request = $this->createServerRequest('/', 'GET'); + $resolver = new AdvancedCallableResolver(); // No container injected + $callableRoute = $resolver->resolveRoute(RequestHandlerTest::class); + $callableRoute($request); + $this->assertEquals('1', RequestHandlerTest::$CalledCount); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Slim\Tests\Mocks\RequestHandlerTest is not resolvable + */ + public function testMiddlewareResolutionToAPsrRequestHandlerClass() + { + $resolver = new AdvancedCallableResolver(); // No container injected + $resolver->resolveMiddleware(RequestHandlerTest::class); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testObjPsrRequestHandlerClass() + { + $obj = new RequestHandlerTest(); + $resolver = new AdvancedCallableResolver(); // No container injected + $resolver->resolve($obj); + } + + public function testRouteObjPsrRequestHandlerClass() + { + $obj = new RequestHandlerTest(); + $request = $this->createServerRequest('/', 'GET'); + $resolver = new AdvancedCallableResolver(); // No container injected + $callableRoute = $resolver->resolveRoute($obj); + $callableRoute($request); + $this->assertEquals('1', RequestHandlerTest::$CalledCount); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testMiddlewareObjPsrRequestHandlerClass() + { + $obj = new RequestHandlerTest(); + $resolver = new AdvancedCallableResolver(); // No container injected + $resolver->resolveMiddleware($obj); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage a_requesthandler is not resolvable + */ + public function testObjPsrRequestHandlerClassInContainer() + { + $this->containerProphecy->has('a_requesthandler')->willReturn(true); + $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolve('a_requesthandler'); + } + + public function testRouteObjPsrRequestHandlerClassInContainer() + { + $this->containerProphecy->has('a_requesthandler')->willReturn(true); + $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $request = $this->createServerRequest('/', 'GET'); + $resolver = new AdvancedCallableResolver($container); + $callable = $resolver->resolveRoute('a_requesthandler'); + $callable($request); + + $this->assertEquals('1', RequestHandlerTest::$CalledCount); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage a_requesthandler is not resolvable + */ + public function testMiddlewareObjPsrRequestHandlerClassInContainer() + { + $this->containerProphecy->has('a_requesthandler')->willReturn(true); + $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveMiddleware('a_requesthandler'); + } + + public function testResolutionToAPsrRequestHandlerClassWithCustomMethod() + { + $resolver = new AdvancedCallableResolver(); // No container injected + $callable = $resolver->resolve(RequestHandlerTest::class.':custom'); + $callableRoute = $resolver->resolveRoute(RequestHandlerTest::class.':custom'); + $callableMiddleware = $resolver->resolveMiddleware(RequestHandlerTest::class.':custom'); + + $this->assertIsArray($callable); + $this->assertInstanceOf(RequestHandlerTest::class, $callable[0]); + $this->assertEquals('custom', $callable[1]); + + $this->assertIsArray($callableRoute); + $this->assertInstanceOf(RequestHandlerTest::class, $callableRoute[0]); + $this->assertEquals('custom', $callableRoute[1]); + + $this->assertIsArray($callableMiddleware); + $this->assertInstanceOf(RequestHandlerTest::class, $callableMiddleware[0]); + $this->assertEquals('custom', $callableMiddleware[1]); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testObjMiddlewareClass() + { + $obj = new MiddlewareTest(); + $resolver = new AdvancedCallableResolver(); // No container injected + $resolver->resolve($obj); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testRouteObjMiddlewareClass() + { + $obj = new MiddlewareTest(); + $resolver = new AdvancedCallableResolver(); // No container injected + $resolver->resolveRoute($obj); + } + + public function testMiddlewareObjMiddlewareClass() + { + $obj = new MiddlewareTest(); + $request = $this->createServerRequest('/', 'GET'); + $resolver = new AdvancedCallableResolver(); // No container injected + $callableRouteMiddleware = $resolver->resolveMiddleware($obj); + $callableRouteMiddleware($request, $this->createMock(RequestHandlerInterface::class)); + $this->assertEquals('1', MiddlewareTest::$CalledCount); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage callable_service:notFound is not resolvable + */ + public function testMethodNotFoundThrowException() + { + $this->containerProphecy->has('callable_service')->willReturn(true); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolve('callable_service:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage callable_service:notFound is not resolvable + */ + public function testRouteMethodNotFoundThrowException() + { + $this->containerProphecy->has('callable_service')->willReturn(true); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveRoute('callable_service:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage callable_service:notFound is not resolvable + */ + public function testMiddlewareMethodNotFoundThrowException() + { + $this->containerProphecy->has('callable_service')->willReturn(true); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveMiddleware('callable_service:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable notFound does not exist + */ + public function testFunctionNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolve('notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable notFound does not exist + */ + public function testRouteFunctionNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveRoute('notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable notFound does not exist + */ + public function testMiddlewareFunctionNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveMiddleware('notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable Unknown does not exist + */ + public function testClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolve('Unknown:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable Unknown does not exist + */ + public function testRouteClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveRoute('Unknown:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable Unknown does not exist + */ + public function testMiddlewareClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveMiddleware('Unknown:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage is not resolvable + */ + public function testCallableClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolve(['Unknown', 'notFound']); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage is not resolvable + */ + public function testRouteCallableClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveRoute(['Unknown', 'notFound']); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage is not resolvable + */ + public function testMiddlewareCallableClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new AdvancedCallableResolver($container); + $resolver->resolveMiddleware(['Unknown', 'notFound']); + } +} diff --git a/tests/Mocks/MiddlewareTest.php b/tests/Mocks/MiddlewareTest.php new file mode 100644 index 000000000..0a10f66d2 --- /dev/null +++ b/tests/Mocks/MiddlewareTest.php @@ -0,0 +1,40 @@ +getResponseFactory(); + + $response = $responseFactory + ->createResponse() + ->withHeader('Content-Type', 'text/plain'); + $calledCount = static::$CalledCount; + $response->getBody()->write("{$calledCount}"); + + return $response; + } +} From 70ae8c8e4dffaad6a1b082939ae73b17b7a19e9d Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 14:47:08 +0200 Subject: [PATCH 07/19] Refactor to avoid duplicate code sections --- Slim/AdvancedCallableResolver.php | 131 ++++++++++++------------------ 1 file changed, 52 insertions(+), 79 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index 6544dc53d..cb22b0373 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -39,40 +39,13 @@ public function resolve($toResolve): callable $resolved = $toResolve; if (!is_callable($toResolve) && is_string($toResolve)) { - $class = $toResolve; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { - $class = $matches[1]; - $method = $matches[2]; + $resolved = $this->resolveInstanceAndMethod($toResolve); + if ($resolved[1] === null) { + $resolved[1] = '__invoke'; } - - if ($this->container && $this->container->has($class)) { - $instance = $this->container->get($class); - } else { - if (!class_exists($class)) { - throw new RuntimeException(sprintf('Callable %s does not exist', $class)); - } - $instance = new $class($this->container); - } - - $resolved = [$instance, $method ?? '__invoke']; } - if (!is_callable($resolved)) { - throw new RuntimeException(sprintf( - '%s is not resolvable', - is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve - )); - } - - if ($this->container && $resolved instanceof Closure) { - $resolved = $resolved->bindTo($this->container); - } - - return $resolved; + return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve); } /** @@ -83,24 +56,7 @@ public function resolveRoute($toResolve): callable $resolved = $toResolve; if (!is_callable($toResolve) && is_string($toResolve)) { - $class = $toResolve; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { - $class = $matches[1]; - $method = $matches[2]; - } - - if ($this->container && $this->container->has($class)) { - $instance = $this->container->get($class); - } else { - if (!class_exists($class)) { - throw new RuntimeException(sprintf('Callable %s does not exist', $class)); - } - $instance = new $class($this->container); - } + [$instance, $method] = $this->resolveInstanceAndMethod($toResolve); // For a class that implements RequestHandlerInterface, we will call handle() // if no method has been specified explicitly @@ -115,18 +71,7 @@ public function resolveRoute($toResolve): callable $resolved = [$resolved, 'handle']; } - if (!is_callable($resolved)) { - throw new RuntimeException(sprintf( - '%s is not resolvable', - is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve - )); - } - - if ($this->container && $resolved instanceof Closure) { - $resolved = $resolved->bindTo($this->container); - } - - return $resolved; + return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve); } /** @@ -137,25 +82,10 @@ public function resolveMiddleware($toResolve): callable $resolved = $toResolve; if (!is_callable($toResolve) && is_string($toResolve)) { - $class = $toResolve; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { - $class = $matches[1]; - $method = $matches[2]; - } - - if ($this->container && $this->container->has($class)) { - $instance = $this->container->get($class); - } else { - if (!class_exists($class)) { - throw new RuntimeException(sprintf('Callable %s does not exist', $class)); - } - $instance = new $class($this->container); - } + [$instance, $method] = $this->resolveInstanceAndMethod($toResolve); + // For a class that implements MiddlewareInterface, we will call process() + // if no method has been specified explicitly if ($instance instanceof MiddlewareInterface && $method === null) { $method = 'process'; } @@ -167,6 +97,49 @@ public function resolveMiddleware($toResolve): callable $resolved = [$resolved, 'process']; } + return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve); + } + + /** + * Resolves the given param and if successful returns an instance as well + * as a method name. + * + * @param $toResolve + * + * @return array [Instance, Method Name] + */ + private function resolveInstanceAndMethod($toResolve): array + { + $class = $toResolve; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + + return [$instance, $method]; + } + + /** + * @param $resolved + * @param $toResolve + * + * @return callable + */ + private function checkResolvedAndBindContainerIfClosure($resolved, $toResolve): callable + { if (!is_callable($resolved)) { throw new RuntimeException(sprintf( '%s is not resolvable', From 56db54b95d6ae7ab6f1a3bf6a57c9caa51840956 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 15:06:37 +0200 Subject: [PATCH 08/19] Add docblock param types --- Slim/AdvancedCallableResolver.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index cb22b0373..23d802a77 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -104,7 +104,7 @@ public function resolveMiddleware($toResolve): callable * Resolves the given param and if successful returns an instance as well * as a method name. * - * @param $toResolve + * @param string|callable $toResolve * * @return array [Instance, Method Name] */ @@ -133,8 +133,8 @@ private function resolveInstanceAndMethod($toResolve): array } /** - * @param $resolved - * @param $toResolve + * @param mixed $resolved + * @param string|callable $toResolve * * @return callable */ From 7af7b0294cd181bde60a30c379dacf0c43862def Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 15:08:12 +0200 Subject: [PATCH 09/19] Convert long line to multi line --- Slim/MiddlewareDispatcher.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Slim/MiddlewareDispatcher.php b/Slim/MiddlewareDispatcher.php index 8f2f4882d..791f52636 100644 --- a/Slim/MiddlewareDispatcher.php +++ b/Slim/MiddlewareDispatcher.php @@ -151,7 +151,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface public function addDeferred(string $middleware): self { $next = $this->tip; - $this->tip = new class($middleware, $next, $this->container, $this->callableResolver) implements RequestHandlerInterface + $this->tip = new class( + $middleware, + $next, + $this->container, + $this->callableResolver + ) implements RequestHandlerInterface { private $middleware; private $next; From 580b52418e05fb1021e6688c6f470ded200972f6 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 15:17:55 +0200 Subject: [PATCH 10/19] Fix types --- Slim/AdvancedCallableResolver.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index 23d802a77..aab909ede 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -104,11 +104,11 @@ public function resolveMiddleware($toResolve): callable * Resolves the given param and if successful returns an instance as well * as a method name. * - * @param string|callable $toResolve + * @param string $toResolve * * @return array [Instance, Method Name] */ - private function resolveInstanceAndMethod($toResolve): array + private function resolveInstanceAndMethod(string $toResolve): array { $class = $toResolve; $instance = null; @@ -143,7 +143,9 @@ private function checkResolvedAndBindContainerIfClosure($resolved, $toResolve): if (!is_callable($resolved)) { throw new RuntimeException(sprintf( '%s is not resolvable', - is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + is_callable($toResolve) || is_array($toResolve) || is_object($toResolve) ? + json_encode($toResolve) : + $toResolve )); } From 19004ee010bec839c95cd4aa060456ee89b81329 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 15:24:01 +0200 Subject: [PATCH 11/19] Fix types again --- Slim/AdvancedCallableResolver.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index aab909ede..68a18658c 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -133,8 +133,8 @@ private function resolveInstanceAndMethod(string $toResolve): array } /** - * @param mixed $resolved - * @param string|callable $toResolve + * @param mixed $resolved + * @param string|object|callable $toResolve * * @return callable */ @@ -143,9 +143,7 @@ private function checkResolvedAndBindContainerIfClosure($resolved, $toResolve): if (!is_callable($resolved)) { throw new RuntimeException(sprintf( '%s is not resolvable', - is_callable($toResolve) || is_array($toResolve) || is_object($toResolve) ? - json_encode($toResolve) : - $toResolve + is_callable($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve )); } From 900fb97e2883244f817d0d447efbc86ec5cdcde2 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Fri, 9 Aug 2019 15:29:39 +0200 Subject: [PATCH 12/19] Fix types --- Slim/AdvancedCallableResolver.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index 68a18658c..026ac5ee3 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -133,8 +133,8 @@ private function resolveInstanceAndMethod(string $toResolve): array } /** - * @param mixed $resolved - * @param string|object|callable $toResolve + * @param mixed $resolved + * @param string|object|array|callable $toResolve * * @return callable */ @@ -143,7 +143,8 @@ private function checkResolvedAndBindContainerIfClosure($resolved, $toResolve): if (!is_callable($resolved)) { throw new RuntimeException(sprintf( '%s is not resolvable', - is_callable($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + is_callable($toResolve) || is_object($toResolve) || is_array($toResolve) ? + json_encode($toResolve) : $toResolve )); } From 4547a78b0f21d7339063b81ebf169a6635e52a7c Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Sat, 10 Aug 2019 11:17:36 +0200 Subject: [PATCH 13/19] Rename method to `assertCallableAndBindClosureToContainer` --- Slim/AdvancedCallableResolver.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index 026ac5ee3..ece871c2b 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -45,7 +45,7 @@ public function resolve($toResolve): callable } } - return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve); + return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); } /** @@ -71,7 +71,7 @@ public function resolveRoute($toResolve): callable $resolved = [$resolved, 'handle']; } - return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve); + return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); } /** @@ -97,7 +97,7 @@ public function resolveMiddleware($toResolve): callable $resolved = [$resolved, 'process']; } - return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve); + return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); } /** @@ -138,7 +138,7 @@ private function resolveInstanceAndMethod(string $toResolve): array * * @return callable */ - private function checkResolvedAndBindContainerIfClosure($resolved, $toResolve): callable + private function assertCallableAndBindClosureToContainer($resolved, $toResolve): callable { if (!is_callable($resolved)) { throw new RuntimeException(sprintf( From 58cc59d5805151e89f69967671d9a4905c078aea Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Sat, 10 Aug 2019 11:23:34 +0200 Subject: [PATCH 14/19] Rearrange if-conditions --- Slim/MiddlewareDispatcher.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Slim/MiddlewareDispatcher.php b/Slim/MiddlewareDispatcher.php index 791f52636..9ebfb926d 100644 --- a/Slim/MiddlewareDispatcher.php +++ b/Slim/MiddlewareDispatcher.php @@ -178,7 +178,9 @@ public function __construct( public function handle(ServerRequestInterface $request): ResponseInterface { $callable = null; - if ($this->callableResolver === null) { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveMiddleware($this->middleware); + } elseif ($this->callableResolver === null) { $resolved = $this->middleware; $instance = null; $method = null; @@ -209,8 +211,6 @@ public function handle(ServerRequestInterface $request): ResponseInterface if ($instance && $method) { $callable = [$instance, $method]; } - } elseif ($this->callableResolver instanceof AdvancedCallableResolverInterface) { - $callable = $this->callableResolver->resolveMiddleware($this->middleware); } if (!is_callable($callable)) { From 16b24c9625aba4f2c20316e2982093375dc1dbb6 Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Sun, 11 Aug 2019 01:04:36 +0200 Subject: [PATCH 15/19] Change signature for `\Slim\MiddlewareDispatcher` constructor --- Slim/AdvancedCallableResolver.php | 4 + Slim/App.php | 2 +- Slim/MiddlewareDispatcher.php | 77 +++--- Slim/Routing/Route.php | 4 +- .../ContentLengthMiddlewareTest.php | 25 +- .../MethodOverrideMiddlewareTest.php | 77 +++++- .../OutputBufferingMiddlewareTest.php | 51 +++- tests/Middleware/RoutingMiddlewareTest.php | 59 ++++- tests/MiddlewareDispatcherTest.php | 250 ++++++++++++++---- tests/Routing/RouteRunnerTest.php | 23 +- tests/TestCase.php | 38 +++ 11 files changed, 484 insertions(+), 126 deletions(-) diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php index ece871c2b..1c2f91475 100644 --- a/Slim/AdvancedCallableResolver.php +++ b/Slim/AdvancedCallableResolver.php @@ -148,6 +148,10 @@ private function assertCallableAndBindClosureToContainer($resolved, $toResolve): )); } + if (is_array($resolved) && $resolved[0] instanceof Closure) { + $resolved = $resolved[0]; + } + if ($this->container && $resolved instanceof Closure) { $resolved = $resolved->bindTo($this->container); } diff --git a/Slim/App.php b/Slim/App.php index 8f9a947e7..a577aa1b1 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -68,7 +68,7 @@ public function __construct( $this->routeResolver = $routeResolver ?? new RouteResolver($this->routeCollector); $routeRunner = new RouteRunner($this->routeResolver, $this->routeCollector->getRouteParser()); - $this->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $container, $this->callableResolver); + $this->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $this->callableResolver, $container); } /** diff --git a/Slim/MiddlewareDispatcher.php b/Slim/MiddlewareDispatcher.php index 9ebfb926d..75f0e3c0f 100644 --- a/Slim/MiddlewareDispatcher.php +++ b/Slim/MiddlewareDispatcher.php @@ -29,28 +29,28 @@ class MiddlewareDispatcher implements RequestHandlerInterface protected $tip; /** - * @var ContainerInterface|null + * @var CallableResolverInterface */ - protected $container; + protected $callableResolver; /** - * @var CallableResolverInterface|null + * @var ContainerInterface|null */ - protected $callableResolver; + protected $container; /** - * @param RequestHandlerInterface $kernel - * @param ContainerInterface|null $container - * @param CallableResolverInterface|null $callableResolver + * @param RequestHandlerInterface $kernel + * @param CallableResolverInterface $callableResolver + * @param ContainerInterface|null $container */ public function __construct( RequestHandlerInterface $kernel, - ?ContainerInterface $container = null, - ?CallableResolverInterface $callableResolver = null + CallableResolverInterface $callableResolver, + ?ContainerInterface $container = null ) { $this->seedMiddlewareStack($kernel); - $this->container = $container; $this->callableResolver = $callableResolver; + $this->container = $container; } /** @@ -180,36 +180,45 @@ public function handle(ServerRequestInterface $request): ResponseInterface $callable = null; if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { $callable = $this->callableResolver->resolveMiddleware($this->middleware); - } elseif ($this->callableResolver === null) { - $resolved = $this->middleware; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { - $resolved = $matches[1]; - $method = $matches[2]; + } else { + if ($this->callableResolver instanceof CallableResolverInterface) { + try { + $callable = $this->callableResolver->resolve($this->middleware); + } catch (RuntimeException $ignore) { + } } - if ($this->container && $this->container->has($resolved)) { - $instance = $this->container->get($resolved); - if ($instance instanceof MiddlewareInterface) { - return $instance->process($request, $this->next); + if (!is_callable($callable)) { + $resolved = $this->middleware; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { + $resolved = $matches[1]; + $method = $matches[2]; } - } elseif (!function_exists($resolved)) { - if (!class_exists($resolved)) { - throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); + + if ($this->container && $this->container->has($resolved)) { + $instance = $this->container->get($resolved); + if ($instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + } elseif (!function_exists($resolved)) { + if (!class_exists($resolved)) { + throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); + } + $instance = new $resolved($this->container); } - $instance = new $resolved($this->container); - } - if ($instance && $instance instanceof MiddlewareInterface) { - return $instance->process($request, $this->next); - } + if ($instance && $instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } - $callable = $instance ?? $resolved; - if ($instance && $method) { - $callable = [$instance, $method]; + $callable = $instance ?? $resolved; + if ($instance && $method) { + $callable = [$instance, $method]; + } } } diff --git a/Slim/Routing/Route.php b/Slim/Routing/Route.php index 2e7b92baf..29b2a8478 100644 --- a/Slim/Routing/Route.php +++ b/Slim/Routing/Route.php @@ -146,7 +146,7 @@ public function __construct( $this->invocationStrategy = $invocationStrategy ?? new RequestResponse(); $this->groups = $groups; $this->identifier = 'route' . $identifier; - $this->middlewareDispatcher = new MiddlewareDispatcher($this, $container); + $this->middlewareDispatcher = new MiddlewareDispatcher($this, $callableResolver, $container); } /** @@ -346,7 +346,7 @@ public function run(ServerRequestInterface $request): ResponseInterface protected function appendGroupMiddlewareToRoute(): void { $inner = $this->middlewareDispatcher; - $this->middlewareDispatcher = new MiddlewareDispatcher($inner, $this->container); + $this->middlewareDispatcher = new MiddlewareDispatcher($inner, $this->callableResolver, $this->container); /** @var RouteGroupInterface $group */ foreach (array_reverse($this->groups) as $group) { diff --git a/tests/Middleware/ContentLengthMiddlewareTest.php b/tests/Middleware/ContentLengthMiddlewareTest.php index 29b45245a..f2ebc4e98 100644 --- a/tests/Middleware/ContentLengthMiddlewareTest.php +++ b/tests/Middleware/ContentLengthMiddlewareTest.php @@ -11,12 +11,27 @@ use Psr\Http\Server\RequestHandlerInterface; use Slim\Middleware\ContentLengthMiddleware; -use Slim\MiddlewareDispatcher; use Slim\Tests\TestCase; class ContentLengthMiddlewareTest extends TestCase { - public function testAddsContentLength() + /** + * Provide a boolean flag to indicate whether the test case should use the + * advanced callable resolver or the non-advanced callable resolver + * + * @return array + */ + public function useAdvancedCallableResolverDataProvider(): array + { + return [[true], [false]]; + } + + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testAddsContentLength(bool $useAdvancedCallableResolver) { $request = $this->createServerRequest('/'); $responseFactory = $this->getResponseFactory(); @@ -28,7 +43,11 @@ public function testAddsContentLength() }; $mw2 = new ContentLengthMiddleware(); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $response = $middlewareDispatcher->handle($request); diff --git a/tests/Middleware/MethodOverrideMiddlewareTest.php b/tests/Middleware/MethodOverrideMiddlewareTest.php index d762f71eb..43f40114c 100644 --- a/tests/Middleware/MethodOverrideMiddlewareTest.php +++ b/tests/Middleware/MethodOverrideMiddlewareTest.php @@ -13,12 +13,27 @@ use Psr\Http\Message\StreamInterface; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Slim\Middleware\MethodOverrideMiddleware; -use Slim\MiddlewareDispatcher; use Slim\Tests\TestCase; class MethodOverrideMiddlewareTest extends TestCase { - public function testHeader() + /** + * Provide a boolean flag to indicate whether the test case should use the + * advanced callable resolver or the non-advanced callable resolver + * + * @return array + */ + public function useAdvancedCallableResolverDataProvider(): array + { + return [[true], [false]]; + } + + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testHeader(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -31,13 +46,22 @@ public function testHeader() ->createServerRequest('/', 'POST') ->withHeader('X-Http-Method-Override', 'PUT'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - public function testBodyParam() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testBodyParam(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -51,13 +75,22 @@ public function testBodyParam() ->createServerRequest('/', 'POST') ->withParsedBody(['_METHOD' => 'PUT']); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - public function testHeaderPreferred() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testHeaderPreferred(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -72,13 +105,22 @@ public function testHeaderPreferred() ->withHeader('X-Http-Method-Override', 'DELETE') ->withParsedBody((object) ['_METHOD' => 'PUT']); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - public function testNoOverride() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testNoOverride(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -90,13 +132,22 @@ public function testNoOverride() $request = $this->createServerRequest('/', 'POST'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - public function testNoOverrideRewindEofBodyStream() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testNoOverrideRewindEofBodyStream(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -122,7 +173,11 @@ public function testNoOverrideRewindEofBodyStream() $body = $bodyProphecy->reveal(); $request = $request->withBody($body); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index 61af0439b..02d2c4dea 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -12,11 +12,21 @@ use Exception; use Psr\Http\Server\RequestHandlerInterface; use Slim\Middleware\OutputBufferingMiddleware; -use Slim\MiddlewareDispatcher; use Slim\Tests\TestCase; class OutputBufferingMiddlewareTest extends TestCase { + /** + * Provide a boolean flag to indicate whether the test case should use the + * advanced callable resolver or the non-advanced callable resolver + * + * @return array + */ + public function useAdvancedCallableResolverDataProvider(): array + { + return [[true], [false]]; + } + public function testStyleDefaultValid() { $mw = new OutputBufferingMiddleware($this->getStreamFactory()); @@ -37,7 +47,12 @@ public function testStyleCustomInvalid() new OutputBufferingMiddleware($this->getStreamFactory(), 'foo'); } - public function testAppend() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testAppend(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = function ($request, $handler) use ($responseFactory) { @@ -51,7 +66,11 @@ public function testAppend() $request = $this->createServerRequest('/', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $response = $middlewareDispatcher->handle($request); @@ -59,7 +78,12 @@ public function testAppend() $this->assertEquals('BodyTest', $response->getBody()); } - public function testPrepend() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testPrepend(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = function ($request, $handler) use ($responseFactory) { @@ -73,7 +97,11 @@ public function testPrepend() $request = $this->createServerRequest('/', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $response = $middlewareDispatcher->handle($request); @@ -81,7 +109,12 @@ public function testPrepend() $this->assertEquals('TestBody', $response->getBody()); } - public function testOutputBufferIsCleanedWhenThrowableIsCaught() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testOutputBufferIsCleanedWhenThrowableIsCaught(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function ($request, $handler) use ($responseFactory) { @@ -93,7 +126,11 @@ public function testOutputBufferIsCleanedWhenThrowableIsCaught() $request = $this->createServerRequest('/', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index d1269ab61..11e943281 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -20,7 +20,6 @@ use Slim\Interfaces\RouteParserInterface; use Slim\Interfaces\RouteResolverInterface; use Slim\Middleware\RoutingMiddleware; -use Slim\MiddlewareDispatcher; use Slim\Routing\RouteCollector; use Slim\Routing\RouteParser; use Slim\Routing\RouteResolver; @@ -29,6 +28,17 @@ class RoutingMiddlewareTest extends TestCase { + /** + * Provide a boolean flag to indicate whether the test case should use the + * advanced callable resolver or the non-advanced callable resolver + * + * @return array + */ + public function useAdvancedCallableResolverDataProvider(): array + { + return [[true], [false]]; + } + protected function getRouteCollector() { $callableResolver = new CallableResolver(); @@ -38,7 +48,12 @@ protected function getRouteCollector() return $routeCollector; } - public function testRouteIsStoredOnSuccessfulMatch() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testRouteIsStoredOnSuccessfulMatch(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $mw = (function (ServerRequestInterface $request) use ($responseFactory) { @@ -65,13 +80,22 @@ public function testRouteIsStoredOnSuccessfulMatch() $request = $this->createServerRequest('https://example.com:443/hello/foo', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - public function testRouteIsNotStoredOnMethodNotAllowed() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testRouteIsNotStoredOnMethodNotAllowed(bool $useAdvancedCallableResolver) { $routeCollector = $this->getRouteCollector(); $routeParser = new RouteParser($routeCollector); @@ -80,8 +104,14 @@ public function testRouteIsNotStoredOnMethodNotAllowed() $request = $this->createServerRequest('https://example.com:443/hello/foo', 'POST'); $requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class); - - $middlewareDispatcher = new MiddlewareDispatcher($requestHandlerProphecy->reveal()); + /** @var RequestHandlerInterface $requestHandler */ + $requestHandler = $requestHandlerProphecy->reveal(); + + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $requestHandler, + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addMiddleware($routingMiddleware); try { @@ -106,7 +136,12 @@ public function testRouteIsNotStoredOnMethodNotAllowed() } } - public function testRouteIsNotStoredOnNotFound() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testRouteIsNotStoredOnNotFound(bool $useAdvancedCallableResolver) { $routeCollector = $this->getRouteCollector(); $routeParser = new RouteParser($routeCollector); @@ -115,8 +150,14 @@ public function testRouteIsNotStoredOnNotFound() $request = $this->createServerRequest('https://example.com:443/goodbye', 'GET'); $requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class); - - $middlewareDispatcher = new MiddlewareDispatcher($requestHandlerProphecy->reveal()); + /** @var RequestHandlerInterface $requestHandler */ + $requestHandler = $requestHandlerProphecy->reveal(); + + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $requestHandler, + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->addMiddleware($routingMiddleware); try { diff --git a/tests/MiddlewareDispatcherTest.php b/tests/MiddlewareDispatcherTest.php index 9b51c5ce5..70296d0b9 100644 --- a/tests/MiddlewareDispatcherTest.php +++ b/tests/MiddlewareDispatcherTest.php @@ -15,7 +15,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\MiddlewareDispatcher; +use RuntimeException; use Slim\Tests\Mocks\MockMiddlewareSlimCallable; use Slim\Tests\Mocks\MockMiddlewareWithConstructor; use Slim\Tests\Mocks\MockMiddlewareWithoutConstructor; @@ -25,29 +25,57 @@ class MiddlewareDispatcherTest extends TestCase { - public function testAddMiddleware() + public static function setUpBeforeClass() + { + function testProcessRequest(ServerRequestInterface $request, RequestHandlerInterface $handler) + { + return $handler->handle($request); + } + } + + /** + * Provide a boolean flag to indicate whether the test case should use the + * advanced callable resolver or the non-advanced callable resolver + * + * @return array + */ + public function useAdvancedCallableResolverDataProvider(): array + { + return [[true], [false]]; + } + + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testAddMiddleware(bool $useAdvancedCallableResolver) { $responseFactory = $this->getResponseFactory(); $callable = function ($request, $handler) use ($responseFactory) { return $responseFactory->createResponse(); }; - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null, + $useAdvancedCallableResolver + ); $middlewareDispatcher->add($callable); $response = $middlewareDispatcher->handle($this->createMock(ServerRequestInterface::class)); $this->assertInstanceOf(ResponseInterface::class, $response); } - public function testNamedFunctionIsResolved() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testNamedFunctionIsResolved(bool $useAdvancedCallableResolver) { - function testProcessRequest(ServerRequestInterface $request, RequestHandlerInterface $handler) - { - return $handler->handle($request); - } - $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred(__NAMESPACE__ . '\testProcessRequest'); $request = $this->createServerRequest('/'); @@ -56,7 +84,12 @@ function testProcessRequest(ServerRequestInterface $request, RequestHandlerInter $this->assertEquals(1, $handler->getCalledCount()); } - public function testDeferredResolvedCallable() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testDeferredResolvedCallable(bool $useAdvancedCallableResolver) { $callable = function (ServerRequestInterface $request, RequestHandlerInterface $handler) { return $handler->handle($request); @@ -64,9 +97,11 @@ public function testDeferredResolvedCallable() $containerProphecy = $this->prophesize(ContainerInterface::class); $containerProphecy->has('callable')->willReturn(true); $containerProphecy->get('callable')->willReturn($callable); + /** @var ContainerInterface $container */ + $container = $containerProphecy->reveal(); $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred('callable'); $request = $this->createServerRequest('/'); @@ -75,10 +110,15 @@ public function testDeferredResolvedCallable() $this->assertEquals(1, $handler->getCalledCount()); } - public function testDeferredResolvedSlimCallable() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testDeferredResolvedSlimCallable(bool $useAdvancedCallableResolver) { $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, null); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred(MockMiddlewareSlimCallable::class . ':custom'); $request = $this->createServerRequest('/'); @@ -87,36 +127,53 @@ public function testDeferredResolvedSlimCallable() $this->assertEquals(1, $handler->getCalledCount()); } - public function testDeferredResolvedClosureIsBoundToContainer() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testDeferredResolvedClosureIsBoundToContainer(bool $useAdvancedCallableResolver) { + if (!$useAdvancedCallableResolver) { + // TODO We need to handle this case. Something fails. + $this->markTestSkipped(); + return; + } $containerProphecy = $this->prophesize(ContainerInterface::class); $self = $this; $callable = function ( ServerRequestInterface $request, RequestHandlerInterface $handler - ) use ( - $self, - $containerProphecy - ) { - $self->assertSame($containerProphecy->reveal(), $this); + ) use ($self) { + $self->assertInstanceOf(ContainerInterface::class, $this); + return $handler->handle($request); }; $containerProphecy->has('callable')->willReturn(true); $containerProphecy->get('callable')->willReturn($callable); + /** @var ContainerInterface $container */ + $container = $containerProphecy->reveal(); $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred('callable'); $request = $this->createServerRequest('/'); $middlewareDispatcher->handle($request); } - public function testAddCallableBindsClosureToContainer() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testAddCallableBindsClosureToContainer(bool $useAdvancedCallableResolver) { $containerProphecy = $this->prophesize(ContainerInterface::class); + /** @var ContainerInterface $container */ + $container = $containerProphecy->reveal(); $self = $this; $callable = function ( @@ -124,26 +181,32 @@ public function testAddCallableBindsClosureToContainer() RequestHandlerInterface $handler ) use ( $self, - $containerProphecy + $container ) { - $self->assertSame($containerProphecy->reveal(), $this); + $self->assertInstanceOf(ContainerInterface::class, $this); + $self->assertSame($container, $this); return $handler->handle($request); }; $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); $middlewareDispatcher->addCallable($callable); $request = $this->createServerRequest('/'); $middlewareDispatcher->handle($request); } - public function testResolvableReturnsInstantiatedObject() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testResolvableReturnsInstantiatedObject(bool $useAdvancedCallableResolver) { MockMiddlewareWithoutConstructor::$CalledCount = 0; $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred(MockMiddlewareWithoutConstructor::class); $request = $this->createServerRequest('/'); @@ -154,17 +217,24 @@ public function testResolvableReturnsInstantiatedObject() } /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware MiddlewareInterfaceNotImplemented is not resolvable + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @expectedException RuntimeException + * @expectedExceptionMessage MiddlewareInterfaceNotImplemented is not resolvable + * + * @param bool $useAdvancedCallableResolver */ - public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface() - { + public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface( + bool $useAdvancedCallableResolver + ) { $containerProphecy = $this->prophesize(ContainerInterface::class); $containerProphecy->has('MiddlewareInterfaceNotImplemented')->willReturn(true); $containerProphecy->get('MiddlewareInterfaceNotImplemented')->willReturn(new stdClass()); + /** @var ContainerInterface $container */ + $container = $containerProphecy->reveal(); $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred('MiddlewareInterfaceNotImplemented'); $request = $this->createServerRequest('/'); @@ -172,27 +242,38 @@ public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewa } /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware Unresolvable::class does not exist + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @expectedException RuntimeException + * @expectedExceptionMessageRegExp /(Middleware|Callable) Unresolvable::class does not exist/ + * + * @param bool $useAdvancedCallableResolver */ - public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass() + public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass(bool $useAdvancedCallableResolver) { $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); $middlewareDispatcher->addDeferred('Unresolvable::class'); $request = $this->createServerRequest('/'); $middlewareDispatcher->handle($request); } - public function testExecutesKernelWithEmptyMiddlewareStack() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testExecutesKernelWithEmptyMiddlewareStack(bool $useAdvancedCallableResolver) { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->willReturn($responseProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -200,7 +281,12 @@ public function testExecutesKernelWithEmptyMiddlewareStack() $this->assertEquals($responseProphecy->reveal(), $response); } - public function testExecutesMiddlewareLastInFirstOut() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testExecutesMiddlewareLastInFirstOut(bool $useAdvancedCallableResolver) { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $requestProphecy->getHeader(Argument::type('string'))->willReturn([]); @@ -281,7 +367,9 @@ public function testExecutesMiddlewareLastInFirstOut() ->withAddedHeader('X-SEQ-POST-REQ-HANDLER', '3'); }); - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); $dispatcher->add($middleware0Prophecy->reveal()); $dispatcher->addMiddleware($middleware1Prophecy->reveal()); $dispatcher->addDeferred(MockSequenceMiddleware::class); @@ -294,8 +382,14 @@ public function testExecutesMiddlewareLastInFirstOut() $this->assertSame(204, $response->getStatusCode()); } - public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturningOuterMiddleware() - { + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturningOuterMiddleware( + bool $useAdvancedCallableResolver + ) { $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -304,7 +398,9 @@ public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturning $middlewareProphecy->process(Argument::cetera())->willReturn($responseProphecy->reveal()); MockSequenceMiddleware::$hasBeenInstantiated = false; - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); $dispatcher->addDeferred(MockSequenceMiddleware::class); $dispatcher->addMiddleware($middlewareProphecy->reveal()); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -314,21 +410,33 @@ public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturning $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - public function testThrowsExceptionForDeferredNonMiddlewareInterfaceClasses() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testThrowsExceptionForDeferredNonMiddlewareInterfaceClasses(bool $useAdvancedCallableResolver) { - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); $dispatcher->addDeferred(\stdClass::class); $dispatcher->handle($requestProphecy->reveal()); $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - public function testCanBeExcutedMultipleTimes() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testCanBeExcutedMultipleTimes(bool $useAdvancedCallableResolver) { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -336,7 +444,9 @@ public function testCanBeExcutedMultipleTimes() $middlewareProphecy = $this->prophesize(MiddlewareInterface::class); $middlewareProphecy->process(Argument::cetera())->willReturn($responseProphecy->reveal()); - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); $dispatcher->add($middlewareProphecy->reveal()); $response1 = $dispatcher->handle($requestProphecy->reveal()); @@ -347,7 +457,12 @@ public function testCanBeExcutedMultipleTimes() $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - public function testCanBeReExecutedRecursivelyDuringDispatch() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testCanBeReExecutedRecursivelyDuringDispatch(bool $useAdvancedCallableResolver) { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -371,7 +486,9 @@ public function testCanBeReExecutedRecursivelyDuringDispatch() return $clone; }); - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); $middlewareProphecy = $this->prophesize(MiddlewareInterface::class); $middlewareProphecy @@ -396,8 +513,18 @@ public function testCanBeReExecutedRecursivelyDuringDispatch() $this->assertSame(['nested', 'outer'], $response->getHeader('X-TRACE')); } - public function testFetchesMiddlewareFromContainer() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testFetchesMiddlewareFromContainer(bool $useAdvancedCallableResolver) { + if (!$useAdvancedCallableResolver) { + // TODO This should be analyzed: "Method `Double\MiddlewareInterface\P461::__invoke()` not found." + $this->markTestSkipped(); + return; + } $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -408,8 +535,11 @@ public function testFetchesMiddlewareFromContainer() $containerProphecy = $this->prophesize(ContainerInterface::class); $containerProphecy->has('somemiddlewarename')->willReturn(true); $containerProphecy->get('somemiddlewarename')->willReturn($middlewareProphecy->reveal()); - - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal(), $containerProphecy->reveal()); + /** @var ContainerInterface $container */ + $container = $containerProphecy->reveal(); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, $container, $useAdvancedCallableResolver); $dispatcher->addDeferred('somemiddlewarename'); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -417,15 +547,23 @@ public function testFetchesMiddlewareFromContainer() $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - public function testMiddlewareGetsInstantiatedWithContainer() + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testMiddlewareGetsInstantiatedWithContainer(bool $useAdvancedCallableResolver) { $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); $containerProphecy = $this->prophesize(ContainerInterface::class); $containerProphecy->has(MockMiddlewareWithConstructor::class)->willReturn(false); - - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal(), $containerProphecy->reveal()); + /** @var ContainerInterface $container */ + $container = $containerProphecy->reveal(); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, $container, $useAdvancedCallableResolver); $dispatcher->addDeferred(MockMiddlewareWithConstructor::class); $dispatcher->handle($requestProphecy->reveal()); diff --git a/tests/Routing/RouteRunnerTest.php b/tests/Routing/RouteRunnerTest.php index ab1090b5f..6c42b27cc 100644 --- a/tests/Routing/RouteRunnerTest.php +++ b/tests/Routing/RouteRunnerTest.php @@ -22,7 +22,23 @@ class RouteRunnerTest extends TestCase { - public function testRoutingIsPerformedIfRoutingResultsAreUnavailable() + /** + * Provide a boolean flag to indicate whether the test case should use the + * advanced callable resolver or the non-advanced callable resolver + * + * @return array + */ + public function useAdvancedCallableResolverDataProvider(): array + { + return [[true], [false]]; + } + + /** + * @dataProvider useAdvancedCallableResolverDataProvider + * + * @param bool $useAdvancedCallableResolver + */ + public function testRoutingIsPerformedIfRoutingResultsAreUnavailable(bool $useAdvancedCallableResolver) { $handler = (function (ServerRequestInterface $request, ResponseInterface $response) { $routingResults = $request->getAttribute('routingResults'); @@ -30,7 +46,8 @@ public function testRoutingIsPerformedIfRoutingResultsAreUnavailable() return $response; })->bindTo($this); - $callableResolver = new CallableResolver(); + $callableResolver = $useAdvancedCallableResolver ? + $this->getAdvancedCallableResolver() : $this->getCallableResolver(); $responseFactory = $this->getResponseFactory(); $routeCollector = new RouteCollector($responseFactory, $callableResolver); @@ -42,7 +59,7 @@ public function testRoutingIsPerformedIfRoutingResultsAreUnavailable() $request = $this->createServerRequest('https://example.com:443/hello/foo', 'GET'); $dispatcher = new RouteRunner($routeResolver, $routeParser); - $middlewareDispatcher = new MiddlewareDispatcher($dispatcher); + $middlewareDispatcher = new MiddlewareDispatcher($dispatcher, $callableResolver); $middlewareDispatcher->handle($request); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index bab867365..8cd6c664d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -17,8 +17,12 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Slim\AdvancedCallableResolver; use Slim\CallableResolver; +use Slim\Interfaces\AdvancedCallableResolverInterface; use Slim\Interfaces\CallableResolverInterface; +use Slim\MiddlewareDispatcher; use Slim\Tests\Providers\PSR7ObjectProvider; abstract class TestCase extends PhpUnitTestCase @@ -60,6 +64,40 @@ protected function getCallableResolver(?ContainerInterface $container = null): C return new CallableResolver($container); } + /** + * Get a default advanced callable resolver with or without a container + * + * @param ContainerInterface|null $container + * + * @return AdvancedCallableResolverInterface + */ + protected function getAdvancedCallableResolver( + ?ContainerInterface $container = null + ): AdvancedCallableResolverInterface { + return new AdvancedCallableResolver($container); + } + + /** + * @param RequestHandlerInterface $requestHandler + * @param ContainerInterface|null $container + * @param bool $useAdvancedCallableResolver + * + * @return MiddlewareDispatcher + */ + protected function createMiddlewareDispatcher( + RequestHandlerInterface $requestHandler, + ?ContainerInterface $container = null, + bool $useAdvancedCallableResolver = true + ): MiddlewareDispatcher { + return new MiddlewareDispatcher( + $requestHandler, + $useAdvancedCallableResolver ? + $this->getAdvancedCallableResolver($container) : + $this->getCallableResolver($container), + $container + ); + } + /** * @param string $uri * @param string $method From 957578fa202f3a6cddd137434d3a94d7077705cc Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Sun, 11 Aug 2019 01:14:40 +0200 Subject: [PATCH 16/19] Remove the test skips --- tests/MiddlewareDispatcherTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/MiddlewareDispatcherTest.php b/tests/MiddlewareDispatcherTest.php index 70296d0b9..d1ce2be17 100644 --- a/tests/MiddlewareDispatcherTest.php +++ b/tests/MiddlewareDispatcherTest.php @@ -134,11 +134,6 @@ public function testDeferredResolvedSlimCallable(bool $useAdvancedCallableResolv */ public function testDeferredResolvedClosureIsBoundToContainer(bool $useAdvancedCallableResolver) { - if (!$useAdvancedCallableResolver) { - // TODO We need to handle this case. Something fails. - $this->markTestSkipped(); - return; - } $containerProphecy = $this->prophesize(ContainerInterface::class); $self = $this; @@ -520,11 +515,6 @@ public function testCanBeReExecutedRecursivelyDuringDispatch(bool $useAdvancedCa */ public function testFetchesMiddlewareFromContainer(bool $useAdvancedCallableResolver) { - if (!$useAdvancedCallableResolver) { - // TODO This should be analyzed: "Method `Double\MiddlewareInterface\P461::__invoke()` not found." - $this->markTestSkipped(); - return; - } $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); From a9178ba1ba70cedfb17f1d91bbcbd3684e1017b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=83=C2=A9rub=C3=83=C2=A9?= Date: Sat, 10 Aug 2019 22:50:08 -0600 Subject: [PATCH 17/19] remove regular CallableResolver, rename AdvancedCallableResolver and fix tests --- Slim/AdvancedCallableResolver.php | 161 ------ Slim/App.php | 2 +- Slim/CallableResolver.php | 128 +++-- Slim/MiddlewareDispatcher.php | 77 +-- tests/AdvancedCallableResolverTest.php | 525 ------------------ tests/AppTest.php | 3 +- tests/CallableResolverTest.php | 395 +++++++++++-- .../ContentLengthMiddlewareTest.php | 20 +- .../MethodOverrideMiddlewareTest.php | 61 +- .../OutputBufferingMiddlewareTest.php | 56 +- tests/Middleware/RoutingMiddlewareTest.php | 47 +- tests/MiddlewareDispatcherTest.php | 309 ++++++----- tests/Routing/RouteRunnerTest.php | 21 +- tests/TestCase.php | 23 +- 14 files changed, 702 insertions(+), 1126 deletions(-) delete mode 100644 Slim/AdvancedCallableResolver.php delete mode 100644 tests/AdvancedCallableResolverTest.php diff --git a/Slim/AdvancedCallableResolver.php b/Slim/AdvancedCallableResolver.php deleted file mode 100644 index 1c2f91475..000000000 --- a/Slim/AdvancedCallableResolver.php +++ /dev/null @@ -1,161 +0,0 @@ -container = $container; - } - - /** - * {@inheritdoc} - */ - public function resolve($toResolve): callable - { - $resolved = $toResolve; - - if (!is_callable($toResolve) && is_string($toResolve)) { - $resolved = $this->resolveInstanceAndMethod($toResolve); - if ($resolved[1] === null) { - $resolved[1] = '__invoke'; - } - } - - return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); - } - - /** - * {@inheritdoc} - */ - public function resolveRoute($toResolve): callable - { - $resolved = $toResolve; - - if (!is_callable($toResolve) && is_string($toResolve)) { - [$instance, $method] = $this->resolveInstanceAndMethod($toResolve); - - // For a class that implements RequestHandlerInterface, we will call handle() - // if no method has been specified explicitly - if ($instance instanceof RequestHandlerInterface && $method === null) { - $method = 'handle'; - } - - $resolved = [$instance, $method ?? '__invoke']; - } - - if ($resolved instanceof RequestHandlerInterface) { - $resolved = [$resolved, 'handle']; - } - - return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); - } - - /** - * {@inheritdoc} - */ - public function resolveMiddleware($toResolve): callable - { - $resolved = $toResolve; - - if (!is_callable($toResolve) && is_string($toResolve)) { - [$instance, $method] = $this->resolveInstanceAndMethod($toResolve); - - // For a class that implements MiddlewareInterface, we will call process() - // if no method has been specified explicitly - if ($instance instanceof MiddlewareInterface && $method === null) { - $method = 'process'; - } - - $resolved = [$instance, $method ?? '__invoke']; - } - - if ($resolved instanceof MiddlewareInterface) { - $resolved = [$resolved, 'process']; - } - - return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); - } - - /** - * Resolves the given param and if successful returns an instance as well - * as a method name. - * - * @param string $toResolve - * - * @return array [Instance, Method Name] - */ - private function resolveInstanceAndMethod(string $toResolve): array - { - $class = $toResolve; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { - $class = $matches[1]; - $method = $matches[2]; - } - - if ($this->container && $this->container->has($class)) { - $instance = $this->container->get($class); - } else { - if (!class_exists($class)) { - throw new RuntimeException(sprintf('Callable %s does not exist', $class)); - } - $instance = new $class($this->container); - } - - return [$instance, $method]; - } - - /** - * @param mixed $resolved - * @param string|object|array|callable $toResolve - * - * @return callable - */ - private function assertCallableAndBindClosureToContainer($resolved, $toResolve): callable - { - if (!is_callable($resolved)) { - throw new RuntimeException(sprintf( - '%s is not resolvable', - is_callable($toResolve) || is_object($toResolve) || is_array($toResolve) ? - json_encode($toResolve) : $toResolve - )); - } - - if (is_array($resolved) && $resolved[0] instanceof Closure) { - $resolved = $resolved[0]; - } - - if ($this->container && $resolved instanceof Closure) { - $resolved = $resolved->bindTo($this->container); - } - - return $resolved; - } -} diff --git a/Slim/App.php b/Slim/App.php index a577aa1b1..52faa8551 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -60,7 +60,7 @@ public function __construct( ) { parent::__construct( $responseFactory, - $callableResolver ?? new AdvancedCallableResolver($container), + $callableResolver ?? new CallableResolver($container), $container, $routeCollector ); diff --git a/Slim/CallableResolver.php b/Slim/CallableResolver.php index 3c4963e46..8fc015693 100644 --- a/Slim/CallableResolver.php +++ b/Slim/CallableResolver.php @@ -11,15 +11,12 @@ use Closure; use Psr\Container\ContainerInterface; +use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; -use Slim\Interfaces\CallableResolverInterface; +use Slim\Interfaces\AdvancedCallableResolverInterface; -/** - * This class resolves a string of the format 'class:method' into a closure - * that can be dispatched. - */ -final class CallableResolver implements CallableResolverInterface +final class CallableResolver implements AdvancedCallableResolverInterface { /** * @var string @@ -40,40 +37,31 @@ public function __construct(?ContainerInterface $container = null) } /** - * Resolve toResolve into a callable that the router can dispatch. - * - * If toResolve is of the format 'class:method', then try to extract 'class' - * from the container otherwise instantiate it and then dispatch 'method'. - * - * @param mixed $toResolve - * @return callable - * - * @throws RuntimeException if the callable does not exist - * @throws RuntimeException if the callable is not resolvable + * {@inheritdoc} */ public function resolve($toResolve): callable { $resolved = $toResolve; if (!is_callable($toResolve) && is_string($toResolve)) { - $class = $toResolve; - $instance = null; - $method = null; - - // Check for Slim callable as `class:method` - if (preg_match(self::$callablePattern, $toResolve, $matches)) { - $class = $matches[1]; - $method = $matches[2]; + $resolved = $this->resolveInstanceAndMethod($toResolve); + if ($resolved[1] === null) { + $resolved[1] = '__invoke'; } + } - if ($this->container && $this->container->has($class)) { - $instance = $this->container->get($class); - } else { - if (!class_exists($class)) { - throw new RuntimeException(sprintf('Callable %s does not exist', $class)); - } - $instance = new $class($this->container); - } + return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); + } + + /** + * {@inheritdoc} + */ + public function resolveRoute($toResolve): callable + { + $resolved = $toResolve; + + if (!is_callable($toResolve) && is_string($toResolve)) { + [$instance, $method] = $this->resolveInstanceAndMethod($toResolve); // For a class that implements RequestHandlerInterface, we will call handle() // if no method has been specified explicitly @@ -88,13 +76,87 @@ public function resolve($toResolve): callable $resolved = [$resolved, 'handle']; } + return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); + } + + /** + * {@inheritdoc} + */ + public function resolveMiddleware($toResolve): callable + { + $resolved = $toResolve; + + if (!is_callable($toResolve) && is_string($toResolve)) { + [$instance, $method] = $this->resolveInstanceAndMethod($toResolve); + + // For a class that implements MiddlewareInterface, we will call process() + // if no method has been specified explicitly + if ($instance instanceof MiddlewareInterface && $method === null) { + $method = 'process'; + } + + $resolved = [$instance, $method ?? '__invoke']; + } + + if ($resolved instanceof MiddlewareInterface) { + $resolved = [$resolved, 'process']; + } + + return $this->assertCallableAndBindClosureToContainer($resolved, $toResolve); + } + + /** + * Resolves the given param and if successful returns an instance as well + * as a method name. + * + * @param string $toResolve + * + * @return array [Instance, Method Name] + */ + private function resolveInstanceAndMethod(string $toResolve): array + { + $class = $toResolve; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + } else { + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + + return [$instance, $method]; + } + + /** + * @param mixed $resolved + * @param string|object|array|callable $toResolve + * + * @return callable + */ + private function assertCallableAndBindClosureToContainer($resolved, $toResolve): callable + { if (!is_callable($resolved)) { throw new RuntimeException(sprintf( '%s is not resolvable', - is_array($toResolve) || is_object($toResolve) ? json_encode($toResolve) : $toResolve + is_callable($toResolve) || is_object($toResolve) || is_array($toResolve) ? + json_encode($toResolve) : $toResolve )); } + if (is_array($resolved) && $resolved[0] instanceof Closure) { + $resolved = $resolved[0]; + } + if ($this->container && $resolved instanceof Closure) { $resolved = $resolved->bindTo($this->container); } diff --git a/Slim/MiddlewareDispatcher.php b/Slim/MiddlewareDispatcher.php index 75f0e3c0f..8112e7909 100644 --- a/Slim/MiddlewareDispatcher.php +++ b/Slim/MiddlewareDispatcher.php @@ -177,48 +177,55 @@ public function __construct( public function handle(ServerRequestInterface $request): ResponseInterface { - $callable = null; if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { $callable = $this->callableResolver->resolveMiddleware($this->middleware); - } else { - if ($this->callableResolver instanceof CallableResolverInterface) { - try { - $callable = $this->callableResolver->resolve($this->middleware); - } catch (RuntimeException $ignore) { - } - } + return $callable($request, $this->next); + } + + $callable = null; - if (!is_callable($callable)) { - $resolved = $this->middleware; - $instance = null; - $method = null; + if ($this->callableResolver instanceof CallableResolverInterface) { + try { + $callable = $this->callableResolver->resolve($this->middleware); + } catch (RuntimeException $e) { + // Do Nothing + } + } - // Check for Slim callable as `class:method` - if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { - $resolved = $matches[1]; - $method = $matches[2]; - } + if (!$callable) { + $resolved = $this->middleware; + $instance = null; + $method = null; - if ($this->container && $this->container->has($resolved)) { - $instance = $this->container->get($resolved); - if ($instance instanceof MiddlewareInterface) { - return $instance->process($request, $this->next); - } - } elseif (!function_exists($resolved)) { - if (!class_exists($resolved)) { - throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); - } - $instance = new $resolved($this->container); - } + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { + $resolved = $matches[1]; + $method = $matches[2]; + } - if ($instance && $instance instanceof MiddlewareInterface) { + if ($this->container && $this->container->has($resolved)) { + $instance = $this->container->get($resolved); + if ($instance instanceof MiddlewareInterface) { return $instance->process($request, $this->next); } - - $callable = $instance ?? $resolved; - if ($instance && $method) { - $callable = [$instance, $method]; + } elseif (!function_exists($resolved)) { + if (!class_exists($resolved)) { + throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); } + $instance = new $resolved($this->container); + } + + if ($instance && $instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + + $callable = $instance ?? $resolved; + if ($instance && $method) { + $callable = [$instance, $method]; + } + + if ($this->container && $callable instanceof Closure) { + $callable = $callable->bindTo($this->container); } } @@ -229,10 +236,6 @@ public function handle(ServerRequestInterface $request): ResponseInterface )); } - if ($this->container && $callable instanceof Closure) { - $callable = $callable->bindTo($this->container); - } - return $callable($request, $this->next); } }; diff --git a/tests/AdvancedCallableResolverTest.php b/tests/AdvancedCallableResolverTest.php deleted file mode 100644 index f9e70cb06..000000000 --- a/tests/AdvancedCallableResolverTest.php +++ /dev/null @@ -1,525 +0,0 @@ -containerProphecy = $this->prophesize(ContainerInterface::class); - $this->containerProphecy->has(Argument::type('string'))->willReturn(false); - } - - public function testClosure() - { - $test = function () { - return true; - }; - $resolver = new AdvancedCallableResolver(); // No container injected - $callable = $resolver->resolve($test); - $callableRoute = $resolver->resolveRoute($test); - $callableMiddleware = $resolver->resolveMiddleware($test); - - $this->assertEquals(true, $callable()); - $this->assertEquals(true, $callableRoute()); - $this->assertEquals(true, $callableMiddleware()); - } - - public function testClosureContainer() - { - $this->containerProphecy->has('ultimateAnswer')->willReturn(true); - $this->containerProphecy->get('ultimateAnswer')->willReturn(42); - - $that = $this; - $test = function () use ($that) { - $that->assertInstanceOf(ContainerInterface::class, $this); - - /** @var ContainerInterface $this */ - return $this->get('ultimateAnswer'); - }; - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $callable = $resolver->resolve($test); - $callableRoute = $resolver->resolveRoute($test); - $callableMiddleware = $resolver->resolveMiddleware($test); - - $this->assertEquals(42, $callable()); - $this->assertEquals(42, $callableRoute()); - $this->assertEquals(42, $callableMiddleware()); - } - - public function testFunctionName() - { - $resolver = new AdvancedCallableResolver(); // No container injected - $callable = $resolver->resolve(__NAMESPACE__.'\testAdvancedCallable'); - $callableRoute = $resolver->resolveRoute(__NAMESPACE__.'\testAdvancedCallable'); - $callableMiddleware = $resolver->resolveMiddleware(__NAMESPACE__.'\testAdvancedCallable'); - - $this->assertEquals(true, $callable()); - $this->assertEquals(true, $callableRoute()); - $this->assertEquals(true, $callableMiddleware()); - } - - public function testObjMethodArray() - { - $obj = new CallableTest(); - $resolver = new AdvancedCallableResolver(); // No container injected - $callable = $resolver->resolve([$obj, 'toCall']); - $callableRoute = $resolver->resolveRoute([$obj, 'toCall']); - $callableMiddleware = $resolver->resolveMiddleware([$obj, 'toCall']); - - $callable(); - $this->assertEquals(1, CallableTest::$CalledCount); - - $callableRoute(); - $this->assertEquals(2, CallableTest::$CalledCount); - - $callableMiddleware(); - $this->assertEquals(3, CallableTest::$CalledCount); - } - - public function testSlimCallable() - { - $resolver = new AdvancedCallableResolver(); // No container injected - $callable = $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); - $callableRoute = $resolver->resolveRoute('Slim\Tests\Mocks\CallableTest:toCall'); - $callableMiddleware = $resolver->resolveMiddleware('Slim\Tests\Mocks\CallableTest:toCall'); - - $callable(); - $this->assertEquals(1, CallableTest::$CalledCount); - - $callableRoute(); - $this->assertEquals(2, CallableTest::$CalledCount); - - $callableMiddleware(); - $this->assertEquals(3, CallableTest::$CalledCount); - } - - public function testSlimCallableContainer() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); - $this->assertEquals($container, CallableTest::$CalledContainer); - - CallableTest::$CalledContainer = null; - $resolver->resolveRoute('Slim\Tests\Mocks\CallableTest:toCall'); - $this->assertEquals($container, CallableTest::$CalledContainer); - - CallableTest::$CalledContainer = null; - $resolver->resolveMiddleware('Slim\Tests\Mocks\CallableTest:toCall'); - $this->assertEquals($container, CallableTest::$CalledContainer); - } - - public function testContainer() - { - $this->containerProphecy->has('callable_service')->willReturn(true); - $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - - $resolver = new AdvancedCallableResolver($container); - $callable = $resolver->resolve('callable_service:toCall'); - $callableRoute = $resolver->resolveRoute('callable_service:toCall'); - $callableMiddleware = $resolver->resolveMiddleware('callable_service:toCall'); - - $callable(); - $this->assertEquals(1, CallableTest::$CalledCount); - - $callableRoute(); - $this->assertEquals(2, CallableTest::$CalledCount); - - $callableMiddleware(); - $this->assertEquals(3, CallableTest::$CalledCount); - } - - public function testResolutionToAnInvokableClassInContainer() - { - $this->containerProphecy->has('an_invokable')->willReturn(true); - $this->containerProphecy->get('an_invokable')->willReturn(new InvokableTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - - $resolver = new AdvancedCallableResolver($container); - $callable = $resolver->resolve('an_invokable'); - $callableRoute = $resolver->resolveRoute('an_invokable'); - $callableMiddleware = $resolver->resolveMiddleware('an_invokable'); - - $callable(); - $this->assertEquals(1, InvokableTest::$CalledCount); - - $callableRoute(); - $this->assertEquals(2, InvokableTest::$CalledCount); - - $callableMiddleware(); - $this->assertEquals(3, InvokableTest::$CalledCount); - } - - public function testResolutionToAnInvokableClass() - { - $resolver = new AdvancedCallableResolver(); // No container injected - $callable = $resolver->resolve('Slim\Tests\Mocks\InvokableTest'); - $callableRoute = $resolver->resolveRoute('Slim\Tests\Mocks\InvokableTest'); - $callableMiddleware = $resolver->resolveMiddleware('Slim\Tests\Mocks\InvokableTest'); - - $callable(); - $this->assertEquals(1, InvokableTest::$CalledCount); - - $callableRoute(); - $this->assertEquals(2, InvokableTest::$CalledCount); - - $callableMiddleware(); - $this->assertEquals(3, InvokableTest::$CalledCount); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Slim\Tests\Mocks\RequestHandlerTest is not resolvable - */ - public function testResolutionToAPsrRequestHandlerClass() - { - $resolver = new AdvancedCallableResolver(); // No container injected - $resolver->resolve(RequestHandlerTest::class); - } - - public function testRouteResolutionToAPsrRequestHandlerClass() - { - $request = $this->createServerRequest('/', 'GET'); - $resolver = new AdvancedCallableResolver(); // No container injected - $callableRoute = $resolver->resolveRoute(RequestHandlerTest::class); - $callableRoute($request); - $this->assertEquals('1', RequestHandlerTest::$CalledCount); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Slim\Tests\Mocks\RequestHandlerTest is not resolvable - */ - public function testMiddlewareResolutionToAPsrRequestHandlerClass() - { - $resolver = new AdvancedCallableResolver(); // No container injected - $resolver->resolveMiddleware(RequestHandlerTest::class); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage {} is not resolvable - */ - public function testObjPsrRequestHandlerClass() - { - $obj = new RequestHandlerTest(); - $resolver = new AdvancedCallableResolver(); // No container injected - $resolver->resolve($obj); - } - - public function testRouteObjPsrRequestHandlerClass() - { - $obj = new RequestHandlerTest(); - $request = $this->createServerRequest('/', 'GET'); - $resolver = new AdvancedCallableResolver(); // No container injected - $callableRoute = $resolver->resolveRoute($obj); - $callableRoute($request); - $this->assertEquals('1', RequestHandlerTest::$CalledCount); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage {} is not resolvable - */ - public function testMiddlewareObjPsrRequestHandlerClass() - { - $obj = new RequestHandlerTest(); - $resolver = new AdvancedCallableResolver(); // No container injected - $resolver->resolveMiddleware($obj); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage a_requesthandler is not resolvable - */ - public function testObjPsrRequestHandlerClassInContainer() - { - $this->containerProphecy->has('a_requesthandler')->willReturn(true); - $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolve('a_requesthandler'); - } - - public function testRouteObjPsrRequestHandlerClassInContainer() - { - $this->containerProphecy->has('a_requesthandler')->willReturn(true); - $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $request = $this->createServerRequest('/', 'GET'); - $resolver = new AdvancedCallableResolver($container); - $callable = $resolver->resolveRoute('a_requesthandler'); - $callable($request); - - $this->assertEquals('1', RequestHandlerTest::$CalledCount); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage a_requesthandler is not resolvable - */ - public function testMiddlewareObjPsrRequestHandlerClassInContainer() - { - $this->containerProphecy->has('a_requesthandler')->willReturn(true); - $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveMiddleware('a_requesthandler'); - } - - public function testResolutionToAPsrRequestHandlerClassWithCustomMethod() - { - $resolver = new AdvancedCallableResolver(); // No container injected - $callable = $resolver->resolve(RequestHandlerTest::class.':custom'); - $callableRoute = $resolver->resolveRoute(RequestHandlerTest::class.':custom'); - $callableMiddleware = $resolver->resolveMiddleware(RequestHandlerTest::class.':custom'); - - $this->assertIsArray($callable); - $this->assertInstanceOf(RequestHandlerTest::class, $callable[0]); - $this->assertEquals('custom', $callable[1]); - - $this->assertIsArray($callableRoute); - $this->assertInstanceOf(RequestHandlerTest::class, $callableRoute[0]); - $this->assertEquals('custom', $callableRoute[1]); - - $this->assertIsArray($callableMiddleware); - $this->assertInstanceOf(RequestHandlerTest::class, $callableMiddleware[0]); - $this->assertEquals('custom', $callableMiddleware[1]); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage {} is not resolvable - */ - public function testObjMiddlewareClass() - { - $obj = new MiddlewareTest(); - $resolver = new AdvancedCallableResolver(); // No container injected - $resolver->resolve($obj); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage {} is not resolvable - */ - public function testRouteObjMiddlewareClass() - { - $obj = new MiddlewareTest(); - $resolver = new AdvancedCallableResolver(); // No container injected - $resolver->resolveRoute($obj); - } - - public function testMiddlewareObjMiddlewareClass() - { - $obj = new MiddlewareTest(); - $request = $this->createServerRequest('/', 'GET'); - $resolver = new AdvancedCallableResolver(); // No container injected - $callableRouteMiddleware = $resolver->resolveMiddleware($obj); - $callableRouteMiddleware($request, $this->createMock(RequestHandlerInterface::class)); - $this->assertEquals('1', MiddlewareTest::$CalledCount); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage callable_service:notFound is not resolvable - */ - public function testMethodNotFoundThrowException() - { - $this->containerProphecy->has('callable_service')->willReturn(true); - $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolve('callable_service:notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage callable_service:notFound is not resolvable - */ - public function testRouteMethodNotFoundThrowException() - { - $this->containerProphecy->has('callable_service')->willReturn(true); - $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveRoute('callable_service:notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage callable_service:notFound is not resolvable - */ - public function testMiddlewareMethodNotFoundThrowException() - { - $this->containerProphecy->has('callable_service')->willReturn(true); - $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); - - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveMiddleware('callable_service:notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Callable notFound does not exist - */ - public function testFunctionNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolve('notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Callable notFound does not exist - */ - public function testRouteFunctionNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveRoute('notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Callable notFound does not exist - */ - public function testMiddlewareFunctionNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveMiddleware('notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Callable Unknown does not exist - */ - public function testClassNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolve('Unknown:notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Callable Unknown does not exist - */ - public function testRouteClassNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveRoute('Unknown:notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Callable Unknown does not exist - */ - public function testMiddlewareClassNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveMiddleware('Unknown:notFound'); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage is not resolvable - */ - public function testCallableClassNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolve(['Unknown', 'notFound']); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage is not resolvable - */ - public function testRouteCallableClassNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveRoute(['Unknown', 'notFound']); - } - - /** - * @expectedException RuntimeException - * @expectedExceptionMessage is not resolvable - */ - public function testMiddlewareCallableClassNotFoundThrowException() - { - /** @var ContainerInterface $container */ - $container = $this->containerProphecy->reveal(); - $resolver = new AdvancedCallableResolver($container); - $resolver->resolveMiddleware(['Unknown', 'notFound']); - } -} diff --git a/tests/AppTest.php b/tests/AppTest.php index 57239aed2..d987a4315 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -19,7 +19,6 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; -use Slim\AdvancedCallableResolver; use Slim\App; use Slim\CallableResolver; use Slim\Exception\HttpMethodNotAllowedException; @@ -80,7 +79,7 @@ public function testCreatesCallableResolverWhenNull() { $responseFactoryProphecy = $this->prophesize(ResponseFactoryInterface::class); $containerProphecy = $this->prophesize(ContainerInterface::class); - $callableResolver = new AdvancedCallableResolver($containerProphecy->reveal()); + $callableResolver = new CallableResolver($containerProphecy->reveal()); $app = new App($responseFactoryProphecy->reveal(), $containerProphecy->reveal(), null); $this->assertEquals($callableResolver, $app->getCallableResolver()); diff --git a/tests/CallableResolverTest.php b/tests/CallableResolverTest.php index 41c29bdab..7b1a114a6 100644 --- a/tests/CallableResolverTest.php +++ b/tests/CallableResolverTest.php @@ -12,9 +12,12 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Psr\Container\ContainerInterface; +use Psr\Http\Server\RequestHandlerInterface; +use RuntimeException; use Slim\CallableResolver; use Slim\Tests\Mocks\CallableTest; use Slim\Tests\Mocks\InvokableTest; +use Slim\Tests\Mocks\MiddlewareTest; use Slim\Tests\Mocks\RequestHandlerTest; class CallableResolverTest extends TestCase @@ -24,6 +27,14 @@ class CallableResolverTest extends TestCase */ private $containerProphecy; + public static function setUpBeforeClass() + { + function testAdvancedCallable() + { + return true; + } + } + public function setUp() { CallableTest::$CalledCount = 0; @@ -41,21 +52,49 @@ public function testClosure() }; $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve($test); + $callableRoute = $resolver->resolveRoute($test); + $callableMiddleware = $resolver->resolveMiddleware($test); $this->assertEquals(true, $callable()); + $this->assertEquals(true, $callableRoute()); + $this->assertEquals(true, $callableMiddleware()); } - public function testFunctionName() + public function testClosureContainer() { - function testCallable() - { - return true; - } + $this->containerProphecy->has('ultimateAnswer')->willReturn(true); + $this->containerProphecy->get('ultimateAnswer')->willReturn(42); + $that = $this; + $test = function () use ($that) { + $that->assertInstanceOf(ContainerInterface::class, $this); + + /** @var ContainerInterface $this */ + return $this->get('ultimateAnswer'); + }; + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $callable = $resolver->resolve($test); + $callableRoute = $resolver->resolveRoute($test); + $callableMiddleware = $resolver->resolveMiddleware($test); + + $this->assertEquals(42, $callable()); + $this->assertEquals(42, $callableRoute()); + $this->assertEquals(42, $callableMiddleware()); + } + + public function testFunctionName() + { $resolver = new CallableResolver(); // No container injected - $callable = $resolver->resolve(__NAMESPACE__ . '\testCallable'); + $callable = $resolver->resolve(__NAMESPACE__.'\testAdvancedCallable'); + $callableRoute = $resolver->resolveRoute(__NAMESPACE__.'\testAdvancedCallable'); + $callableMiddleware = $resolver->resolveMiddleware(__NAMESPACE__.'\testAdvancedCallable'); $this->assertEquals(true, $callable()); + $this->assertEquals(true, $callableRoute()); + $this->assertEquals(true, $callableMiddleware()); } public function testObjMethodArray() @@ -63,138 +102,424 @@ public function testObjMethodArray() $obj = new CallableTest(); $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve([$obj, 'toCall']); - $callable(); + $callableRoute = $resolver->resolveRoute([$obj, 'toCall']); + $callableMiddleware = $resolver->resolveMiddleware([$obj, 'toCall']); + $callable(); $this->assertEquals(1, CallableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, CallableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, CallableTest::$CalledCount); } public function testSlimCallable() { $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); - $callable(); + $callableRoute = $resolver->resolveRoute('Slim\Tests\Mocks\CallableTest:toCall'); + $callableMiddleware = $resolver->resolveMiddleware('Slim\Tests\Mocks\CallableTest:toCall'); + $callable(); $this->assertEquals(1, CallableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, CallableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, CallableTest::$CalledCount); } public function testSlimCallableContainer() { - $resolver = new CallableResolver($this->containerProphecy->reveal()); + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($container, CallableTest::$CalledContainer); + + CallableTest::$CalledContainer = null; + $resolver->resolveRoute('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($container, CallableTest::$CalledContainer); - $this->assertEquals($this->containerProphecy->reveal(), CallableTest::$CalledContainer); + CallableTest::$CalledContainer = null; + $resolver->resolveMiddleware('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($container, CallableTest::$CalledContainer); } public function testContainer() { $this->containerProphecy->has('callable_service')->willReturn(true); $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); - $resolver = new CallableResolver($this->containerProphecy->reveal()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + + $resolver = new CallableResolver($container); $callable = $resolver->resolve('callable_service:toCall'); - $callable(); + $callableRoute = $resolver->resolveRoute('callable_service:toCall'); + $callableMiddleware = $resolver->resolveMiddleware('callable_service:toCall'); + $callable(); $this->assertEquals(1, CallableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, CallableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, CallableTest::$CalledCount); } public function testResolutionToAnInvokableClassInContainer() { $this->containerProphecy->has('an_invokable')->willReturn(true); $this->containerProphecy->get('an_invokable')->willReturn(new InvokableTest()); - $resolver = new CallableResolver($this->containerProphecy->reveal()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + + $resolver = new CallableResolver($container); $callable = $resolver->resolve('an_invokable'); - $callable(); + $callableRoute = $resolver->resolveRoute('an_invokable'); + $callableMiddleware = $resolver->resolveMiddleware('an_invokable'); + $callable(); $this->assertEquals(1, InvokableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, InvokableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, InvokableTest::$CalledCount); } public function testResolutionToAnInvokableClass() { $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve('Slim\Tests\Mocks\InvokableTest'); - $callable(); + $callableRoute = $resolver->resolveRoute('Slim\Tests\Mocks\InvokableTest'); + $callableMiddleware = $resolver->resolveMiddleware('Slim\Tests\Mocks\InvokableTest'); + $callable(); $this->assertEquals(1, InvokableTest::$CalledCount); + + $callableRoute(); + $this->assertEquals(2, InvokableTest::$CalledCount); + + $callableMiddleware(); + $this->assertEquals(3, InvokableTest::$CalledCount); } + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Slim\Tests\Mocks\RequestHandlerTest is not resolvable + */ public function testResolutionToAPsrRequestHandlerClass() + { + $resolver = new CallableResolver(); // No container injected + $resolver->resolve(RequestHandlerTest::class); + } + + public function testRouteResolutionToAPsrRequestHandlerClass() { $request = $this->createServerRequest('/', 'GET'); $resolver = new CallableResolver(); // No container injected - $callable = $resolver->resolve(RequestHandlerTest::class); - $callable($request); + $callableRoute = $resolver->resolveRoute(RequestHandlerTest::class); + $callableRoute($request); + $this->assertEquals('1', RequestHandlerTest::$CalledCount); + } - $this->assertEquals("1", RequestHandlerTest::$CalledCount); + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Slim\Tests\Mocks\RequestHandlerTest is not resolvable + */ + public function testMiddlewareResolutionToAPsrRequestHandlerClass() + { + $resolver = new CallableResolver(); // No container injected + $resolver->resolveMiddleware(RequestHandlerTest::class); } + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ public function testObjPsrRequestHandlerClass() + { + $obj = new RequestHandlerTest(); + $resolver = new CallableResolver(); // No container injected + $resolver->resolve($obj); + } + + public function testRouteObjPsrRequestHandlerClass() { $obj = new RequestHandlerTest(); $request = $this->createServerRequest('/', 'GET'); $resolver = new CallableResolver(); // No container injected - $callable = $resolver->resolve($obj); - $callable($request); + $callableRoute = $resolver->resolveRoute($obj); + $callableRoute($request); + $this->assertEquals('1', RequestHandlerTest::$CalledCount); + } - $this->assertEquals("1", RequestHandlerTest::$CalledCount); + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testMiddlewareObjPsrRequestHandlerClass() + { + $obj = new RequestHandlerTest(); + $resolver = new CallableResolver(); // No container injected + $resolver->resolveMiddleware($obj); } + /** + * @expectedException RuntimeException + * @expectedExceptionMessage a_requesthandler is not resolvable + */ public function testObjPsrRequestHandlerClassInContainer() { $this->containerProphecy->has('a_requesthandler')->willReturn(true); $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolve('a_requesthandler'); + } + + public function testRouteObjPsrRequestHandlerClassInContainer() + { + $this->containerProphecy->has('a_requesthandler')->willReturn(true); + $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); $request = $this->createServerRequest('/', 'GET'); - $resolver = new CallableResolver($this->containerProphecy->reveal()); - $callable = $resolver->resolve('a_requesthandler'); + $resolver = new CallableResolver($container); + $callable = $resolver->resolveRoute('a_requesthandler'); $callable($request); - $this->assertEquals("1", RequestHandlerTest::$CalledCount); + $this->assertEquals('1', RequestHandlerTest::$CalledCount); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage a_requesthandler is not resolvable + */ + public function testMiddlewareObjPsrRequestHandlerClassInContainer() + { + $this->containerProphecy->has('a_requesthandler')->willReturn(true); + $this->containerProphecy->get('a_requesthandler')->willReturn(new RequestHandlerTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveMiddleware('a_requesthandler'); } public function testResolutionToAPsrRequestHandlerClassWithCustomMethod() { $resolver = new CallableResolver(); // No container injected - $callable = $resolver->resolve(RequestHandlerTest::class . ':custom'); - $this->assertInternalType('array', $callable); + $callable = $resolver->resolve(RequestHandlerTest::class.':custom'); + $callableRoute = $resolver->resolveRoute(RequestHandlerTest::class.':custom'); + $callableMiddleware = $resolver->resolveMiddleware(RequestHandlerTest::class.':custom'); + + $this->assertIsArray($callable); $this->assertInstanceOf(RequestHandlerTest::class, $callable[0]); $this->assertEquals('custom', $callable[1]); + + $this->assertIsArray($callableRoute); + $this->assertInstanceOf(RequestHandlerTest::class, $callableRoute[0]); + $this->assertEquals('custom', $callableRoute[1]); + + $this->assertIsArray($callableMiddleware); + $this->assertInstanceOf(RequestHandlerTest::class, $callableMiddleware[0]); + $this->assertEquals('custom', $callableMiddleware[1]); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testObjMiddlewareClass() + { + $obj = new MiddlewareTest(); + $resolver = new CallableResolver(); // No container injected + $resolver->resolve($obj); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage {} is not resolvable + */ + public function testRouteObjMiddlewareClass() + { + $obj = new MiddlewareTest(); + $resolver = new CallableResolver(); // No container injected + $resolver->resolveRoute($obj); + } + + public function testMiddlewareObjMiddlewareClass() + { + $obj = new MiddlewareTest(); + $request = $this->createServerRequest('/', 'GET'); + $resolver = new CallableResolver(); // No container injected + $callableRouteMiddleware = $resolver->resolveMiddleware($obj); + $callableRouteMiddleware($request, $this->createMock(RequestHandlerInterface::class)); + $this->assertEquals('1', MiddlewareTest::$CalledCount); } /** - * @expectedException \RuntimeException + * @expectedException RuntimeException + * @expectedExceptionMessage callable_service:notFound is not resolvable */ public function testMethodNotFoundThrowException() { $this->containerProphecy->has('callable_service')->willReturn(true); - $this->containerProphecy->get('callable_service')->willReturn(new CallableTest); - $resolver = new CallableResolver($this->containerProphecy->reveal()); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); $resolver->resolve('callable_service:notFound'); } /** - * @expectedException \RuntimeException + * @expectedException RuntimeException + * @expectedExceptionMessage callable_service:notFound is not resolvable + */ + public function testRouteMethodNotFoundThrowException() + { + $this->containerProphecy->has('callable_service')->willReturn(true); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveRoute('callable_service:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage callable_service:notFound is not resolvable + */ + public function testMiddlewareMethodNotFoundThrowException() + { + $this->containerProphecy->has('callable_service')->willReturn(true); + $this->containerProphecy->get('callable_service')->willReturn(new CallableTest()); + + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveMiddleware('callable_service:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable notFound does not exist */ public function testFunctionNotFoundThrowException() { - $resolver = new CallableResolver($this->containerProphecy->reveal()); + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); $resolver->resolve('notFound'); } /** - * @expectedException \RuntimeException + * @expectedException RuntimeException + * @expectedExceptionMessage Callable notFound does not exist + */ + public function testRouteFunctionNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveRoute('notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable notFound does not exist + */ + public function testMiddlewareFunctionNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveMiddleware('notFound'); + } + + /** + * @expectedException RuntimeException * @expectedExceptionMessage Callable Unknown does not exist */ public function testClassNotFoundThrowException() { - $resolver = new CallableResolver($this->containerProphecy->reveal()); + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); $resolver->resolve('Unknown:notFound'); } /** - * @expectedException \RuntimeException + * @expectedException RuntimeException + * @expectedExceptionMessage Callable Unknown does not exist + */ + public function testRouteClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveRoute('Unknown:notFound'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Callable Unknown does not exist + */ + public function testMiddlewareClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveMiddleware('Unknown:notFound'); + } + + /** + * @expectedException RuntimeException * @expectedExceptionMessage is not resolvable */ public function testCallableClassNotFoundThrowException() { - $resolver = new CallableResolver($this->containerProphecy->reveal()); + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); $resolver->resolve(['Unknown', 'notFound']); } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage is not resolvable + */ + public function testRouteCallableClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveRoute(['Unknown', 'notFound']); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage is not resolvable + */ + public function testMiddlewareCallableClassNotFoundThrowException() + { + /** @var ContainerInterface $container */ + $container = $this->containerProphecy->reveal(); + $resolver = new CallableResolver($container); + $resolver->resolveMiddleware(['Unknown', 'notFound']); + } } diff --git a/tests/Middleware/ContentLengthMiddlewareTest.php b/tests/Middleware/ContentLengthMiddlewareTest.php index f2ebc4e98..8fdc00101 100644 --- a/tests/Middleware/ContentLengthMiddlewareTest.php +++ b/tests/Middleware/ContentLengthMiddlewareTest.php @@ -15,23 +15,8 @@ class ContentLengthMiddlewareTest extends TestCase { - /** - * Provide a boolean flag to indicate whether the test case should use the - * advanced callable resolver or the non-advanced callable resolver - * - * @return array - */ - public function useAdvancedCallableResolverDataProvider(): array - { - return [[true], [false]]; - } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testAddsContentLength(bool $useAdvancedCallableResolver) + public function testAddsContentLength() { $request = $this->createServerRequest('/'); $responseFactory = $this->getResponseFactory(); @@ -45,8 +30,7 @@ public function testAddsContentLength(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandlerInterface::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); diff --git a/tests/Middleware/MethodOverrideMiddlewareTest.php b/tests/Middleware/MethodOverrideMiddlewareTest.php index 43f40114c..f088baa80 100644 --- a/tests/Middleware/MethodOverrideMiddlewareTest.php +++ b/tests/Middleware/MethodOverrideMiddlewareTest.php @@ -17,23 +17,7 @@ class MethodOverrideMiddlewareTest extends TestCase { - /** - * Provide a boolean flag to indicate whether the test case should use the - * advanced callable resolver or the non-advanced callable resolver - * - * @return array - */ - public function useAdvancedCallableResolverDataProvider(): array - { - return [[true], [false]]; - } - - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testHeader(bool $useAdvancedCallableResolver) + public function testHeader() { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -48,20 +32,14 @@ public function testHeader(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandler::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testBodyParam(bool $useAdvancedCallableResolver) + public function testBodyParam() { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -77,20 +55,14 @@ public function testBodyParam(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandler::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testHeaderPreferred(bool $useAdvancedCallableResolver) + public function testHeaderPreferred() { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -107,20 +79,14 @@ public function testHeaderPreferred(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandler::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testNoOverride(bool $useAdvancedCallableResolver) + public function testNoOverride() { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -134,20 +100,14 @@ public function testNoOverride(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandler::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testNoOverrideRewindEofBodyStream(bool $useAdvancedCallableResolver) + public function testNoOverrideRewindEofBodyStream() { $responseFactory = $this->getResponseFactory(); $mw = (function (Request $request, RequestHandler $handler) use ($responseFactory) { @@ -175,8 +135,7 @@ public function testNoOverrideRewindEofBodyStream(bool $useAdvancedCallableResol $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandler::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index 02d2c4dea..4284db82d 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -11,32 +11,32 @@ use Exception; use Psr\Http\Server\RequestHandlerInterface; +use ReflectionProperty; use Slim\Middleware\OutputBufferingMiddleware; use Slim\Tests\TestCase; class OutputBufferingMiddlewareTest extends TestCase { - /** - * Provide a boolean flag to indicate whether the test case should use the - * advanced callable resolver or the non-advanced callable resolver - * - * @return array - */ - public function useAdvancedCallableResolverDataProvider(): array - { - return [[true], [false]]; - } - public function testStyleDefaultValid() { $mw = new OutputBufferingMiddleware($this->getStreamFactory()); - $this->assertAttributeEquals('append', 'style', $mw); + + $reflectionProperty = new ReflectionProperty($mw, 'style'); + $reflectionProperty->setAccessible(true); + $value = $reflectionProperty->getValue($mw); + + $this->assertEquals('append', $value); } public function testStyleCustomValid() { $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); - $this->assertAttributeEquals('prepend', 'style', $mw); + + $reflectionProperty = new ReflectionProperty($mw, 'style'); + $reflectionProperty->setAccessible(true); + $value = $reflectionProperty->getValue($mw); + + $this->assertEquals('prepend', $value); } /** @@ -47,12 +47,7 @@ public function testStyleCustomInvalid() new OutputBufferingMiddleware($this->getStreamFactory(), 'foo'); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testAppend(bool $useAdvancedCallableResolver) + public function testAppend() { $responseFactory = $this->getResponseFactory(); $mw = function ($request, $handler) use ($responseFactory) { @@ -68,8 +63,7 @@ public function testAppend(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandlerInterface::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); @@ -78,12 +72,7 @@ public function testAppend(bool $useAdvancedCallableResolver) $this->assertEquals('BodyTest', $response->getBody()); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testPrepend(bool $useAdvancedCallableResolver) + public function testPrepend() { $responseFactory = $this->getResponseFactory(); $mw = function ($request, $handler) use ($responseFactory) { @@ -99,8 +88,7 @@ public function testPrepend(bool $useAdvancedCallableResolver) $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandlerInterface::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); @@ -109,12 +97,7 @@ public function testPrepend(bool $useAdvancedCallableResolver) $this->assertEquals('TestBody', $response->getBody()); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testOutputBufferIsCleanedWhenThrowableIsCaught(bool $useAdvancedCallableResolver) + public function testOutputBufferIsCleanedWhenThrowableIsCaught() { $responseFactory = $this->getResponseFactory(); $mw = (function ($request, $handler) use ($responseFactory) { @@ -128,8 +111,7 @@ public function testOutputBufferIsCleanedWhenThrowableIsCaught(bool $useAdvanced $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandlerInterface::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index 11e943281..97194d107 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -28,17 +28,6 @@ class RoutingMiddlewareTest extends TestCase { - /** - * Provide a boolean flag to indicate whether the test case should use the - * advanced callable resolver or the non-advanced callable resolver - * - * @return array - */ - public function useAdvancedCallableResolverDataProvider(): array - { - return [[true], [false]]; - } - protected function getRouteCollector() { $callableResolver = new CallableResolver(); @@ -48,12 +37,7 @@ protected function getRouteCollector() return $routeCollector; } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testRouteIsStoredOnSuccessfulMatch(bool $useAdvancedCallableResolver) + public function testRouteIsStoredOnSuccessfulMatch() { $responseFactory = $this->getResponseFactory(); $mw = (function (ServerRequestInterface $request) use ($responseFactory) { @@ -82,20 +66,14 @@ public function testRouteIsStoredOnSuccessfulMatch(bool $useAdvancedCallableReso $middlewareDispatcher = $this->createMiddlewareDispatcher( $this->createMock(RequestHandlerInterface::class), - null, - $useAdvancedCallableResolver + null ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testRouteIsNotStoredOnMethodNotAllowed(bool $useAdvancedCallableResolver) + public function testRouteIsNotStoredOnMethodNotAllowed() { $routeCollector = $this->getRouteCollector(); $routeParser = new RouteParser($routeCollector); @@ -107,11 +85,7 @@ public function testRouteIsNotStoredOnMethodNotAllowed(bool $useAdvancedCallable /** @var RequestHandlerInterface $requestHandler */ $requestHandler = $requestHandlerProphecy->reveal(); - $middlewareDispatcher = $this->createMiddlewareDispatcher( - $requestHandler, - null, - $useAdvancedCallableResolver - ); + $middlewareDispatcher = $this->createMiddlewareDispatcher($requestHandler, null); $middlewareDispatcher->addMiddleware($routingMiddleware); try { @@ -136,12 +110,7 @@ public function testRouteIsNotStoredOnMethodNotAllowed(bool $useAdvancedCallable } } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testRouteIsNotStoredOnNotFound(bool $useAdvancedCallableResolver) + public function testRouteIsNotStoredOnNotFound() { $routeCollector = $this->getRouteCollector(); $routeParser = new RouteParser($routeCollector); @@ -153,11 +122,7 @@ public function testRouteIsNotStoredOnNotFound(bool $useAdvancedCallableResolver /** @var RequestHandlerInterface $requestHandler */ $requestHandler = $requestHandlerProphecy->reveal(); - $middlewareDispatcher = $this->createMiddlewareDispatcher( - $requestHandler, - null, - $useAdvancedCallableResolver - ); + $middlewareDispatcher = $this->createMiddlewareDispatcher($requestHandler, null); $middlewareDispatcher->addMiddleware($routingMiddleware); try { diff --git a/tests/MiddlewareDispatcherTest.php b/tests/MiddlewareDispatcherTest.php index d1ce2be17..4d67e665a 100644 --- a/tests/MiddlewareDispatcherTest.php +++ b/tests/MiddlewareDispatcherTest.php @@ -16,6 +16,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; +use Slim\Interfaces\CallableResolverInterface; use Slim\Tests\Mocks\MockMiddlewareSlimCallable; use Slim\Tests\Mocks\MockMiddlewareWithConstructor; use Slim\Tests\Mocks\MockMiddlewareWithoutConstructor; @@ -33,49 +34,28 @@ function testProcessRequest(ServerRequestInterface $request, RequestHandlerInter } } - /** - * Provide a boolean flag to indicate whether the test case should use the - * advanced callable resolver or the non-advanced callable resolver - * - * @return array - */ - public function useAdvancedCallableResolverDataProvider(): array - { - return [[true], [false]]; - } - - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testAddMiddleware(bool $useAdvancedCallableResolver) + public function testAddMiddleware() { $responseFactory = $this->getResponseFactory(); $callable = function ($request, $handler) use ($responseFactory) { return $responseFactory->createResponse(); }; - $middlewareDispatcher = $this->createMiddlewareDispatcher( - $this->createMock(RequestHandlerInterface::class), - null, - $useAdvancedCallableResolver - ); + $requestProphecy = $this->prophesize(ServerRequestInterface::class); + $requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($requestHandlerProphecy->reveal()); $middlewareDispatcher->add($callable); - $response = $middlewareDispatcher->handle($this->createMock(ServerRequestInterface::class)); + $response = $middlewareDispatcher->handle($requestProphecy->reveal()); $this->assertInstanceOf(ResponseInterface::class, $response); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testNamedFunctionIsResolved(bool $useAdvancedCallableResolver) + public function testNamedFunctionIsResolved() { $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred(__NAMESPACE__ . '\testProcessRequest'); $request = $this->createServerRequest('/'); @@ -84,24 +64,51 @@ public function testNamedFunctionIsResolved(bool $useAdvancedCallableResolver) $this->assertEquals(1, $handler->getCalledCount()); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testDeferredResolvedCallable(bool $useAdvancedCallableResolver) + public function testDeferredResolvedCallable() { $callable = function (ServerRequestInterface $request, RequestHandlerInterface $handler) { return $handler->handle($request); }; + $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->has('callable')->willReturn(true); - $containerProphecy->get('callable')->willReturn($callable); - /** @var ContainerInterface $container */ - $container = $containerProphecy->reveal(); + + $containerProphecy + ->has('callable') + ->willReturn(true) + ->shouldBeCalledOnce(); + + $containerProphecy + ->get('callable') + ->willReturn($callable) + ->shouldBeCalledOnce(); + + $handler = new MockRequestHandler(); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher->addDeferred('callable'); + + $request = $this->createServerRequest('/'); + $middlewareDispatcher->handle($request); + + $this->assertEquals(1, $handler->getCalledCount()); + } + + public function testDeferredResolvedCallableWithoutContainerAndNonAdvancedCallableResolver() + { + $callable = function (ServerRequestInterface $request, RequestHandlerInterface $handler) { + return $handler->handle($request); + }; + + $callableResolverProphecy = $this->prophesize(CallableResolverInterface::class); + + $callableResolverProphecy + ->resolve('callable') + ->willReturn($callable) + ->shouldBeCalledOnce(); $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $callableResolverProphecy->reveal()); $middlewareDispatcher->addDeferred('callable'); $request = $this->createServerRequest('/'); @@ -110,15 +117,66 @@ public function testDeferredResolvedCallable(bool $useAdvancedCallableResolver) $this->assertEquals(1, $handler->getCalledCount()); } + public function deferredCallableProvider() + { + return [ + [MockMiddlewareSlimCallable::class . ':custom', new MockMiddlewareSlimCallable()], + ['MiddlewareInstance', new MockMiddlewareWithoutConstructor()], + ['NamedFunction', __NAMESPACE__ . '\testProcessRequest'], + ['Callable', function (ServerRequestInterface $request, RequestHandlerInterface $handler) { + return $handler->handle($request); + }], + ['MiddlewareInterfaceNotImplemented', 'MiddlewareInterfaceNotImplemented'] + ]; + } + /** - * @dataProvider useAdvancedCallableResolverDataProvider + * @dataProvider deferredCallableProvider * - * @param bool $useAdvancedCallableResolver + * @param string $callable + * @param callable|MiddlewareInterface */ - public function testDeferredResolvedSlimCallable(bool $useAdvancedCallableResolver) + public function testDeferredResolvedCallableWithContainerAndNonAdvancedCallableResolverUnableToResolveCallable($callable, $result) + { + if ($callable === 'MiddlewareInterfaceNotImplemented') { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Middleware MiddlewareInterfaceNotImplemented is not resolvable'); + } + + $callableResolverProphecy = $this->prophesize(CallableResolverInterface::class); + + $callableResolverProphecy + ->resolve($callable) + ->willThrow(RuntimeException::class) + ->shouldBeCalledOnce(); + + $containerProphecy = $this->prophesize(ContainerInterface::class); + + $containerProphecy + ->has(Argument::any()) + ->willReturn(true) + ->shouldBeCalledOnce(); + + $containerProphecy + ->get(Argument::any()) + ->willReturn($result) + ->shouldBeCalledOnce(); + + $handler = new MockRequestHandler(); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal(), $callableResolverProphecy->reveal()); + $middlewareDispatcher->addDeferred($callable); + + $request = $this->createServerRequest('/'); + $middlewareDispatcher->handle($request); + + $this->assertEquals(1, $handler->getCalledCount()); + } + + public function testDeferredResolvedSlimCallable() { $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred(MockMiddlewareSlimCallable::class . ':custom'); $request = $this->createServerRequest('/'); @@ -127,12 +185,7 @@ public function testDeferredResolvedSlimCallable(bool $useAdvancedCallableResolv $this->assertEquals(1, $handler->getCalledCount()); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testDeferredResolvedClosureIsBoundToContainer(bool $useAdvancedCallableResolver) + public function testDeferredResolvedClosureIsBoundToContainer() { $containerProphecy = $this->prophesize(ContainerInterface::class); @@ -142,33 +195,23 @@ public function testDeferredResolvedClosureIsBoundToContainer(bool $useAdvancedC RequestHandlerInterface $handler ) use ($self) { $self->assertInstanceOf(ContainerInterface::class, $this); - return $handler->handle($request); }; $containerProphecy->has('callable')->willReturn(true); $containerProphecy->get('callable')->willReturn($callable); - /** @var ContainerInterface $container */ - $container = $containerProphecy->reveal(); $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addDeferred('callable'); $request = $this->createServerRequest('/'); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testAddCallableBindsClosureToContainer(bool $useAdvancedCallableResolver) + public function testAddCallableBindsClosureToContainer() { $containerProphecy = $this->prophesize(ContainerInterface::class); - /** @var ContainerInterface $container */ - $container = $containerProphecy->reveal(); $self = $this; $callable = function ( @@ -176,32 +219,26 @@ public function testAddCallableBindsClosureToContainer(bool $useAdvancedCallable RequestHandlerInterface $handler ) use ( $self, - $container + $containerProphecy ) { - $self->assertInstanceOf(ContainerInterface::class, $this); - $self->assertSame($container, $this); + $self->assertSame($containerProphecy->reveal(), $this); return $handler->handle($request); }; $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addCallable($callable); $request = $this->createServerRequest('/'); $middlewareDispatcher->handle($request); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testResolvableReturnsInstantiatedObject(bool $useAdvancedCallableResolver) + public function testResolvableReturnsInstantiatedObject() { MockMiddlewareWithoutConstructor::$CalledCount = 0; $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred(MockMiddlewareWithoutConstructor::class); $request = $this->createServerRequest('/'); @@ -212,24 +249,25 @@ public function testResolvableReturnsInstantiatedObject(bool $useAdvancedCallabl } /** - * @dataProvider useAdvancedCallableResolverDataProvider - * * @expectedException RuntimeException * @expectedExceptionMessage MiddlewareInterfaceNotImplemented is not resolvable - * - * @param bool $useAdvancedCallableResolver */ - public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface( - bool $useAdvancedCallableResolver - ) { + public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface() + { $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->has('MiddlewareInterfaceNotImplemented')->willReturn(true); - $containerProphecy->get('MiddlewareInterfaceNotImplemented')->willReturn(new stdClass()); - /** @var ContainerInterface $container */ - $container = $containerProphecy->reveal(); + + $containerProphecy + ->has('MiddlewareInterfaceNotImplemented') + ->willReturn(true) + ->shouldBeCalledOnce(); + + $containerProphecy + ->get('MiddlewareInterfaceNotImplemented') + ->willReturn(new stdClass()) + ->shouldBeCalledOnce(); $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $container, $useAdvancedCallableResolver); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addDeferred('MiddlewareInterfaceNotImplemented'); $request = $this->createServerRequest('/'); @@ -237,17 +275,13 @@ public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewa } /** - * @dataProvider useAdvancedCallableResolverDataProvider - * * @expectedException RuntimeException * @expectedExceptionMessageRegExp /(Middleware|Callable) Unresolvable::class does not exist/ - * - * @param bool $useAdvancedCallableResolver */ - public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass(bool $useAdvancedCallableResolver) + public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass() { $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $useAdvancedCallableResolver); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred('Unresolvable::class'); $request = $this->createServerRequest('/'); @@ -255,11 +289,29 @@ public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass(b } /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver + * @expectedException RuntimeException + * @expectedExceptionMessageRegExp /(Middleware|Callable) Unresolvable::class does not exist/ */ - public function testExecutesKernelWithEmptyMiddlewareStack(bool $useAdvancedCallableResolver) + public function testResolveThrowsExceptionWithoutContainerNonAdvancedCallableResolverAndUnresolvableClass() + { + $unresolvable = 'Unresolvable::class'; + + $callableResolverProphecy = $this->prophesize(CallableResolverInterface::class); + + $callableResolverProphecy + ->resolve($unresolvable) + ->willThrow(RuntimeException::class) + ->shouldBeCalledOnce(); + + $handler = new MockRequestHandler(); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null, $callableResolverProphecy->reveal()); + $middlewareDispatcher->addDeferred($unresolvable); + + $request = $this->createServerRequest('/'); + $middlewareDispatcher->handle($request); + } + + public function testExecutesKernelWithEmptyMiddlewareStack() { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -268,7 +320,7 @@ public function testExecutesKernelWithEmptyMiddlewareStack(bool $useAdvancedCall /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -276,12 +328,7 @@ public function testExecutesKernelWithEmptyMiddlewareStack(bool $useAdvancedCall $this->assertEquals($responseProphecy->reveal(), $response); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testExecutesMiddlewareLastInFirstOut(bool $useAdvancedCallableResolver) + public function testExecutesMiddlewareLastInFirstOut() { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $requestProphecy->getHeader(Argument::type('string'))->willReturn([]); @@ -364,7 +411,7 @@ public function testExecutesMiddlewareLastInFirstOut(bool $useAdvancedCallableRe /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $dispatcher->add($middleware0Prophecy->reveal()); $dispatcher->addMiddleware($middleware1Prophecy->reveal()); $dispatcher->addDeferred(MockSequenceMiddleware::class); @@ -377,13 +424,8 @@ public function testExecutesMiddlewareLastInFirstOut(bool $useAdvancedCallableRe $this->assertSame(204, $response->getStatusCode()); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturningOuterMiddleware( - bool $useAdvancedCallableResolver + ) { $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); @@ -395,7 +437,7 @@ public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturning MockSequenceMiddleware::$hasBeenInstantiated = false; /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $dispatcher->addDeferred(MockSequenceMiddleware::class); $dispatcher->addMiddleware($middlewareProphecy->reveal()); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -405,12 +447,7 @@ public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturning $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testThrowsExceptionForDeferredNonMiddlewareInterfaceClasses(bool $useAdvancedCallableResolver) + public function testThrowsExceptionForDeferredNonMiddlewareInterfaceClasses() { $this->expectException(RuntimeException::class); @@ -419,19 +456,14 @@ public function testThrowsExceptionForDeferredNonMiddlewareInterfaceClasses(bool /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $dispatcher->addDeferred(\stdClass::class); $dispatcher->handle($requestProphecy->reveal()); $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testCanBeExcutedMultipleTimes(bool $useAdvancedCallableResolver) + public function testCanBeExcutedMultipleTimes() { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -441,7 +473,7 @@ public function testCanBeExcutedMultipleTimes(bool $useAdvancedCallableResolver) /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $dispatcher->add($middlewareProphecy->reveal()); $response1 = $dispatcher->handle($requestProphecy->reveal()); @@ -452,12 +484,7 @@ public function testCanBeExcutedMultipleTimes(bool $useAdvancedCallableResolver) $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testCanBeReExecutedRecursivelyDuringDispatch(bool $useAdvancedCallableResolver) + public function testCanBeReExecutedRecursivelyDuringDispatch() { $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); @@ -483,7 +510,7 @@ public function testCanBeReExecutedRecursivelyDuringDispatch(bool $useAdvancedCa /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, null, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $middlewareProphecy = $this->prophesize(MiddlewareInterface::class); $middlewareProphecy @@ -508,12 +535,7 @@ public function testCanBeReExecutedRecursivelyDuringDispatch(bool $useAdvancedCa $this->assertSame(['nested', 'outer'], $response->getHeader('X-TRACE')); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testFetchesMiddlewareFromContainer(bool $useAdvancedCallableResolver) + public function testFetchesMiddlewareFromContainer() { $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); @@ -529,7 +551,7 @@ public function testFetchesMiddlewareFromContainer(bool $useAdvancedCallableReso $container = $containerProphecy->reveal(); /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, $container, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, $container); $dispatcher->addDeferred('somemiddlewarename'); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -537,12 +559,7 @@ public function testFetchesMiddlewareFromContainer(bool $useAdvancedCallableReso $kernelProphecy->handle(Argument::type(ServerRequestInterface::class))->shouldNotHaveBeenCalled(); } - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testMiddlewareGetsInstantiatedWithContainer(bool $useAdvancedCallableResolver) + public function testMiddlewareGetsInstantiatedWithContainer() { $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); @@ -553,7 +570,7 @@ public function testMiddlewareGetsInstantiatedWithContainer(bool $useAdvancedCal $container = $containerProphecy->reveal(); /** @var RequestHandlerInterface $kernel */ $kernel = $kernelProphecy->reveal(); - $dispatcher = $this->createMiddlewareDispatcher($kernel, $container, $useAdvancedCallableResolver); + $dispatcher = $this->createMiddlewareDispatcher($kernel, $container); $dispatcher->addDeferred(MockMiddlewareWithConstructor::class); $dispatcher->handle($requestProphecy->reveal()); diff --git a/tests/Routing/RouteRunnerTest.php b/tests/Routing/RouteRunnerTest.php index 6c42b27cc..7800620c9 100644 --- a/tests/Routing/RouteRunnerTest.php +++ b/tests/Routing/RouteRunnerTest.php @@ -22,23 +22,7 @@ class RouteRunnerTest extends TestCase { - /** - * Provide a boolean flag to indicate whether the test case should use the - * advanced callable resolver or the non-advanced callable resolver - * - * @return array - */ - public function useAdvancedCallableResolverDataProvider(): array - { - return [[true], [false]]; - } - - /** - * @dataProvider useAdvancedCallableResolverDataProvider - * - * @param bool $useAdvancedCallableResolver - */ - public function testRoutingIsPerformedIfRoutingResultsAreUnavailable(bool $useAdvancedCallableResolver) + public function testRoutingIsPerformedIfRoutingResultsAreUnavailable() { $handler = (function (ServerRequestInterface $request, ResponseInterface $response) { $routingResults = $request->getAttribute('routingResults'); @@ -46,8 +30,7 @@ public function testRoutingIsPerformedIfRoutingResultsAreUnavailable(bool $useAd return $response; })->bindTo($this); - $callableResolver = $useAdvancedCallableResolver ? - $this->getAdvancedCallableResolver() : $this->getCallableResolver(); + $callableResolver = $this->getCallableResolver(); $responseFactory = $this->getResponseFactory(); $routeCollector = new RouteCollector($responseFactory, $callableResolver); diff --git a/tests/TestCase.php b/tests/TestCase.php index 8cd6c664d..3b4d64e9f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -18,9 +18,7 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\AdvancedCallableResolver; use Slim\CallableResolver; -use Slim\Interfaces\AdvancedCallableResolverInterface; use Slim\Interfaces\CallableResolverInterface; use Slim\MiddlewareDispatcher; use Slim\Tests\Providers\PSR7ObjectProvider; @@ -64,36 +62,21 @@ protected function getCallableResolver(?ContainerInterface $container = null): C return new CallableResolver($container); } - /** - * Get a default advanced callable resolver with or without a container - * - * @param ContainerInterface|null $container - * - * @return AdvancedCallableResolverInterface - */ - protected function getAdvancedCallableResolver( - ?ContainerInterface $container = null - ): AdvancedCallableResolverInterface { - return new AdvancedCallableResolver($container); - } - /** * @param RequestHandlerInterface $requestHandler * @param ContainerInterface|null $container - * @param bool $useAdvancedCallableResolver + * @param CallableResolverInterface|null $callableResolver * * @return MiddlewareDispatcher */ protected function createMiddlewareDispatcher( RequestHandlerInterface $requestHandler, ?ContainerInterface $container = null, - bool $useAdvancedCallableResolver = true + ?CallableResolverInterface $callableResolver = null ): MiddlewareDispatcher { return new MiddlewareDispatcher( $requestHandler, - $useAdvancedCallableResolver ? - $this->getAdvancedCallableResolver($container) : - $this->getCallableResolver($container), + $callableResolver ?? $this->getCallableResolver($container), $container ); } From 5d4a7b72291d5353ced69924fd885ab208e52497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20B=C3=83=C2=A9rub=C3=83=C2=A9?= Date: Sat, 10 Aug 2019 22:50:08 -0600 Subject: [PATCH 18/19] fix code style errors --- tests/MiddlewareDispatcherTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/MiddlewareDispatcherTest.php b/tests/MiddlewareDispatcherTest.php index 4d67e665a..32641c244 100644 --- a/tests/MiddlewareDispatcherTest.php +++ b/tests/MiddlewareDispatcherTest.php @@ -424,9 +424,8 @@ public function testExecutesMiddlewareLastInFirstOut() $this->assertSame(204, $response->getStatusCode()); } - public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturningOuterMiddleware( - - ) { + public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturningOuterMiddleware() + { $kernelProphecy = $this->prophesize(RequestHandlerInterface::class); $requestProphecy = $this->prophesize(ServerRequestInterface::class); $responseProphecy = $this->prophesize(ResponseInterface::class); From ebab2db46c95040ce97580980b3a4600e324797e Mon Sep 17 00:00:00 2001 From: Adrian Suter Date: Sun, 11 Aug 2019 21:00:26 +0200 Subject: [PATCH 19/19] Convert long line to multi line --- tests/MiddlewareDispatcherTest.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/MiddlewareDispatcherTest.php b/tests/MiddlewareDispatcherTest.php index 32641c244..cff6e0842 100644 --- a/tests/MiddlewareDispatcherTest.php +++ b/tests/MiddlewareDispatcherTest.php @@ -136,8 +136,10 @@ public function deferredCallableProvider() * @param string $callable * @param callable|MiddlewareInterface */ - public function testDeferredResolvedCallableWithContainerAndNonAdvancedCallableResolverUnableToResolveCallable($callable, $result) - { + public function testDeferredResolvedCallableWithContainerAndNonAdvancedCallableResolverUnableToResolveCallable( + $callable, + $result + ) { if ($callable === 'MiddlewareInterfaceNotImplemented') { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Middleware MiddlewareInterfaceNotImplemented is not resolvable'); @@ -164,7 +166,11 @@ public function testDeferredResolvedCallableWithContainerAndNonAdvancedCallableR $handler = new MockRequestHandler(); - $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal(), $callableResolverProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $handler, + $containerProphecy->reveal(), + $callableResolverProphecy->reveal() + ); $middlewareDispatcher->addDeferred($callable); $request = $this->createServerRequest('/');