diff --git a/Slim/App.php b/Slim/App.php index d70846f87..52faa8551 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->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $this->callableResolver, $container); } /** 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/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 @@ +seedMiddlewareStack($kernel); + $this->callableResolver = $callableResolver; $this->container = $container; } @@ -141,53 +151,82 @@ 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->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveMiddleware($this->middleware); + return $callable($request, $this->next); } - 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)); + $callable = null; + + if ($this->callableResolver instanceof CallableResolverInterface) { + try { + $callable = $this->callableResolver->resolve($this->middleware); + } catch (RuntimeException $e) { + // Do Nothing } - $instance = new $resolved($this->container); } - if ($instance && $instance instanceof MiddlewareInterface) { - return $instance->process($request, $this->next); - } + if (!$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]; + } - $callable = $instance ?? $resolved; - if ($instance && $method) { - $callable = [$instance, $method]; + 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); + } + + 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); + } } if (!is_callable($callable)) { @@ -197,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/Slim/Routing/Route.php b/Slim/Routing/Route.php index 9afd0523a..29b2a8478 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; @@ -145,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); } /** @@ -345,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) { @@ -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; } 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 29b45245a..8fdc00101 100644 --- a/tests/Middleware/ContentLengthMiddlewareTest.php +++ b/tests/Middleware/ContentLengthMiddlewareTest.php @@ -11,11 +11,11 @@ use Psr\Http\Server\RequestHandlerInterface; use Slim\Middleware\ContentLengthMiddleware; -use Slim\MiddlewareDispatcher; use Slim\Tests\TestCase; class ContentLengthMiddlewareTest extends TestCase { + public function testAddsContentLength() { $request = $this->createServerRequest('/'); @@ -28,7 +28,10 @@ public function testAddsContentLength() }; $mw2 = new ContentLengthMiddleware(); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $response = $middlewareDispatcher->handle($request); diff --git a/tests/Middleware/MethodOverrideMiddlewareTest.php b/tests/Middleware/MethodOverrideMiddlewareTest.php index d762f71eb..f088baa80 100644 --- a/tests/Middleware/MethodOverrideMiddlewareTest.php +++ b/tests/Middleware/MethodOverrideMiddlewareTest.php @@ -13,7 +13,6 @@ 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 @@ -31,7 +30,10 @@ 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 + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); @@ -51,7 +53,10 @@ public function testBodyParam() ->createServerRequest('/', 'POST') ->withParsedBody(['_METHOD' => 'PUT']); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); @@ -72,7 +77,10 @@ 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 + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); @@ -90,7 +98,10 @@ public function testNoOverride() $request = $this->createServerRequest('/', 'POST'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandler::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandler::class), + null + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); @@ -122,7 +133,10 @@ 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 + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index 61af0439b..4284db82d 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -11,8 +11,8 @@ use Exception; use Psr\Http\Server\RequestHandlerInterface; +use ReflectionProperty; use Slim\Middleware\OutputBufferingMiddleware; -use Slim\MiddlewareDispatcher; use Slim\Tests\TestCase; class OutputBufferingMiddlewareTest extends TestCase @@ -20,13 +20,23 @@ class OutputBufferingMiddlewareTest extends TestCase 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); } /** @@ -51,7 +61,10 @@ public function testAppend() $request = $this->createServerRequest('/', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $response = $middlewareDispatcher->handle($request); @@ -73,7 +86,10 @@ public function testPrepend() $request = $this->createServerRequest('/', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $response = $middlewareDispatcher->handle($request); @@ -93,7 +109,10 @@ public function testOutputBufferIsCleanedWhenThrowableIsCaught() $request = $this->createServerRequest('/', 'GET'); - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $middlewareDispatcher = $this->createMiddlewareDispatcher( + $this->createMock(RequestHandlerInterface::class), + null + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index d1269ab61..97194d107 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; @@ -65,7 +64,10 @@ 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 + ); $middlewareDispatcher->addCallable($mw); $middlewareDispatcher->addMiddleware($mw2); $middlewareDispatcher->handle($request); @@ -80,8 +82,10 @@ public function testRouteIsNotStoredOnMethodNotAllowed() $request = $this->createServerRequest('https://example.com:443/hello/foo', 'POST'); $requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class); + /** @var RequestHandlerInterface $requestHandler */ + $requestHandler = $requestHandlerProphecy->reveal(); - $middlewareDispatcher = new MiddlewareDispatcher($requestHandlerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($requestHandler, null); $middlewareDispatcher->addMiddleware($routingMiddleware); try { @@ -115,8 +119,10 @@ public function testRouteIsNotStoredOnNotFound() $request = $this->createServerRequest('https://example.com:443/goodbye', 'GET'); $requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class); + /** @var RequestHandlerInterface $requestHandler */ + $requestHandler = $requestHandlerProphecy->reveal(); - $middlewareDispatcher = new MiddlewareDispatcher($requestHandlerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($requestHandler, null); $middlewareDispatcher->addMiddleware($routingMiddleware); try { diff --git a/tests/MiddlewareDispatcherTest.php b/tests/MiddlewareDispatcherTest.php index 9b51c5ce5..cff6e0842 100644 --- a/tests/MiddlewareDispatcherTest.php +++ b/tests/MiddlewareDispatcherTest.php @@ -15,7 +15,8 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\MiddlewareDispatcher; +use RuntimeException; +use Slim\Interfaces\CallableResolverInterface; use Slim\Tests\Mocks\MockMiddlewareSlimCallable; use Slim\Tests\Mocks\MockMiddlewareWithConstructor; use Slim\Tests\Mocks\MockMiddlewareWithoutConstructor; @@ -25,6 +26,14 @@ class MiddlewareDispatcherTest extends TestCase { + public static function setUpBeforeClass() + { + function testProcessRequest(ServerRequestInterface $request, RequestHandlerInterface $handler) + { + return $handler->handle($request); + } + } + public function testAddMiddleware() { $responseFactory = $this->getResponseFactory(); @@ -32,22 +41,21 @@ public function testAddMiddleware() return $responseFactory->createResponse(); }; - $middlewareDispatcher = new MiddlewareDispatcher($this->createMock(RequestHandlerInterface::class)); + $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); } public function testNamedFunctionIsResolved() { - function testProcessRequest(ServerRequestInterface $request, RequestHandlerInterface $handler) - { - return $handler->handle($request); - } - $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred(__NAMESPACE__ . '\testProcessRequest'); $request = $this->createServerRequest('/'); @@ -61,12 +69,22 @@ 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); + + $containerProphecy + ->has('callable') + ->willReturn(true) + ->shouldBeCalledOnce(); + + $containerProphecy + ->get('callable') + ->willReturn($callable) + ->shouldBeCalledOnce(); $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addDeferred('callable'); $request = $this->createServerRequest('/'); @@ -75,10 +93,96 @@ public function testDeferredResolvedCallable() $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, null, $callableResolverProphecy->reveal()); + $middlewareDispatcher->addDeferred('callable'); + + $request = $this->createServerRequest('/'); + $middlewareDispatcher->handle($request); + + $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 deferredCallableProvider + * + * @param string $callable + * @param callable|MiddlewareInterface + */ + 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 = new MiddlewareDispatcher($handler, null); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred(MockMiddlewareSlimCallable::class . ':custom'); $request = $this->createServerRequest('/'); @@ -95,11 +199,8 @@ public function testDeferredResolvedClosureIsBoundToContainer() $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); }; @@ -107,7 +208,7 @@ public function testDeferredResolvedClosureIsBoundToContainer() $containerProphecy->get('callable')->willReturn($callable); $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addDeferred('callable'); $request = $this->createServerRequest('/'); @@ -131,7 +232,7 @@ public function testAddCallableBindsClosureToContainer() }; $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addCallable($callable); $request = $this->createServerRequest('/'); @@ -143,7 +244,7 @@ public function testResolvableReturnsInstantiatedObject() MockMiddlewareWithoutConstructor::$CalledCount = 0; $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred(MockMiddlewareWithoutConstructor::class); $request = $this->createServerRequest('/'); @@ -154,17 +255,25 @@ public function testResolvableReturnsInstantiatedObject() } /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware MiddlewareInterfaceNotImplemented is not resolvable + * @expectedException RuntimeException + * @expectedExceptionMessage MiddlewareInterfaceNotImplemented is not resolvable */ public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface() { $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->has('MiddlewareInterfaceNotImplemented')->willReturn(true); - $containerProphecy->get('MiddlewareInterfaceNotImplemented')->willReturn(new stdClass()); + + $containerProphecy + ->has('MiddlewareInterfaceNotImplemented') + ->willReturn(true) + ->shouldBeCalledOnce(); + + $containerProphecy + ->get('MiddlewareInterfaceNotImplemented') + ->willReturn(new stdClass()) + ->shouldBeCalledOnce(); $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal()); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, $containerProphecy->reveal()); $middlewareDispatcher->addDeferred('MiddlewareInterfaceNotImplemented'); $request = $this->createServerRequest('/'); @@ -172,27 +281,52 @@ public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewa } /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware Unresolvable::class does not exist + * @expectedException RuntimeException + * @expectedExceptionMessageRegExp /(Middleware|Callable) Unresolvable::class does not exist/ */ public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass() { $handler = new MockRequestHandler(); - $middlewareDispatcher = new MiddlewareDispatcher($handler); + $middlewareDispatcher = $this->createMiddlewareDispatcher($handler, null); $middlewareDispatcher->addDeferred('Unresolvable::class'); $request = $this->createServerRequest('/'); $middlewareDispatcher->handle($request); } + /** + * @expectedException RuntimeException + * @expectedExceptionMessageRegExp /(Middleware|Callable) Unresolvable::class does not exist/ + */ + 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); $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); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -281,7 +415,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); $dispatcher->add($middleware0Prophecy->reveal()); $dispatcher->addMiddleware($middleware1Prophecy->reveal()); $dispatcher->addDeferred(MockSequenceMiddleware::class); @@ -304,7 +440,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); $dispatcher->addDeferred(MockSequenceMiddleware::class); $dispatcher->addMiddleware($middlewareProphecy->reveal()); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -316,12 +454,14 @@ public function testDoesNotInstantiateDeferredMiddlewareInCaseOfAnEarlyReturning public function testThrowsExceptionForDeferredNonMiddlewareInterfaceClasses() { - $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); $dispatcher->addDeferred(\stdClass::class); $dispatcher->handle($requestProphecy->reveal()); @@ -336,7 +476,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); $dispatcher->add($middlewareProphecy->reveal()); $response1 = $dispatcher->handle($requestProphecy->reveal()); @@ -371,7 +513,9 @@ public function testCanBeReExecutedRecursivelyDuringDispatch() return $clone; }); - $dispatcher = new MiddlewareDispatcher($kernelProphecy->reveal()); + /** @var RequestHandlerInterface $kernel */ + $kernel = $kernelProphecy->reveal(); + $dispatcher = $this->createMiddlewareDispatcher($kernel, null); $middlewareProphecy = $this->prophesize(MiddlewareInterface::class); $middlewareProphecy @@ -408,8 +552,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); $dispatcher->addDeferred('somemiddlewarename'); $response = $dispatcher->handle($requestProphecy->reveal()); @@ -424,8 +571,11 @@ public function testMiddlewareGetsInstantiatedWithContainer() $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); $dispatcher->addDeferred(MockMiddlewareWithConstructor::class); $dispatcher->handle($requestProphecy->reveal()); 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; + } +} diff --git a/tests/Routing/RouteRunnerTest.php b/tests/Routing/RouteRunnerTest.php index ab1090b5f..7800620c9 100644 --- a/tests/Routing/RouteRunnerTest.php +++ b/tests/Routing/RouteRunnerTest.php @@ -30,7 +30,7 @@ public function testRoutingIsPerformedIfRoutingResultsAreUnavailable() return $response; })->bindTo($this); - $callableResolver = new CallableResolver(); + $callableResolver = $this->getCallableResolver(); $responseFactory = $this->getResponseFactory(); $routeCollector = new RouteCollector($responseFactory, $callableResolver); @@ -42,7 +42,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..3b4d64e9f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -17,8 +17,10 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; +use Psr\Http\Server\RequestHandlerInterface; use Slim\CallableResolver; use Slim\Interfaces\CallableResolverInterface; +use Slim\MiddlewareDispatcher; use Slim\Tests\Providers\PSR7ObjectProvider; abstract class TestCase extends PhpUnitTestCase @@ -60,6 +62,25 @@ protected function getCallableResolver(?ContainerInterface $container = null): C return new CallableResolver($container); } + /** + * @param RequestHandlerInterface $requestHandler + * @param ContainerInterface|null $container + * @param CallableResolverInterface|null $callableResolver + * + * @return MiddlewareDispatcher + */ + protected function createMiddlewareDispatcher( + RequestHandlerInterface $requestHandler, + ?ContainerInterface $container = null, + ?CallableResolverInterface $callableResolver = null + ): MiddlewareDispatcher { + return new MiddlewareDispatcher( + $requestHandler, + $callableResolver ?? $this->getCallableResolver($container), + $container + ); + } + /** * @param string $uri * @param string $method