diff --git a/README.md b/README.md index 3b594a80e..afb7b8ce4 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,9 @@ use Slim\Psr7\Factory\StreamFactory; $responseFactory = new DecoratedResponseFactory(new ResponseFactory(), new StreamFactory()); $app = new App($responseFactory); -// Add middlweare (LIFO stack) -$app->add(new ErrorMiddleware($app->getCallableResolver(), $responseFactory, true, true, true)); +// Add middleware (LIFO stack) +$errorMiddleware = new ErrorMiddleware($app->getCallableResolver(), $responseFactory, true, true, true); +$app->add($errorMiddleware); // Action $app->get('/hello/{name}', function ($request, $response, $args) { @@ -80,14 +81,17 @@ $serverRequestFactory = new ServerRequestCreator( ); /** - * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters + * The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters * @param ResponseFactoryInterface Any implementation of a ResponseFactory * @param ContainerInterface|null Any implementation of a Container * @param array Settings array + * @param CallableResolverInterface|null Any implementation of a CallableResolver + * @param RouterInterface|null Any implementation of a Router */ $app = new Slim\App($psr17Factory); $app->get('/hello/{name}', function ($request, $response, $args) { - return $response->getBody()->write("Hello, " . $args['name']); + $response->getBody()->write("Hello, " . $args['name']); + return $response; }); /** @@ -111,14 +115,17 @@ $responseFactory = new ResponseFactory(); $serverRequestFactory = new ServerRequestFactory(); /** - * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters + * The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters * @param ResponseFactoryInterface Any implementation of a ResponseFactory * @param ContainerInterface|null Any implementation of a Container * @param array Settings array + * @param CallableResolverInterface|null Any implementation of a CallableResolver + * @param RouterInterface|null Any implementation of a Router */ $app = new Slim\App($responseFactory); $app->get('/hello/{name}', function ($request, $response, $args) { - return $response->getBody()->write("Hello, " . $args['name']); + $response->getBody()->write("Hello, " . $args['name']); + return $response; }); /** @@ -155,12 +162,14 @@ $decoratedResponseFactory = new DecoratedResponseFactory($responseFactory, $stre $serverRequestFactory = new ServerRequestFactory(); /** - * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters + * The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters * Note that we pass in the decorated response factory which will give us access to the Slim\Http * decorated Response methods like withJson() * @param ResponseFactoryInterface Any implementation of a ResponseFactory * @param ContainerInterface|null Any implementation of a Container * @param array Settings array + * @param CallableResolverInterface|null Any implementation of a CallableResolver + * @param RouterInterface|null Any implementation of a Router */ $app = new Slim\App($decoratedResponseFactory); $app->get('/hello/{name}', function ($request, $response, $args) { @@ -189,14 +198,17 @@ use Http\Factory\Guzzle\ResponseFactory; $responseFactory = new ResponseFactory(); /** - * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters + * The App::__constructor() method takes 1 mandatory parameter and 4 optional parameters * @param ResponseFactoryInterface Any implementation of a ResponseFactory * @param ContainerInterface|null Any implementation of a Container * @param array Settings array + * @param CallableResolverInterface|null Any implementation of a CallableResolver + * @param RouterInterface|null Any implementation of a Router */ $app = new Slim\App($responseFactory); $app->get('/hello/{name}', function ($request, $response, $args) { - return $response->getBody()->write("Hello, " . $args['name']); + $response->getBody()->write("Hello, " . $args['name']); + return $response; }); /** @@ -220,7 +232,7 @@ For more information on how to configure your web server, see the [Documentation To execute the test suite, you'll need to install all development dependencies. ```bash -$ git clone https://github.com/slimphp/Slim-Http +$ git clone https://github.com/slimphp/Slim $ composer install $ composer test ``` diff --git a/Slim/App.php b/Slim/App.php index 826b21555..93cd6671d 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -11,19 +11,22 @@ namespace Slim; +use Closure; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; +use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\Exception\HttpMethodNotAllowedException; -use Slim\Exception\HttpNotFoundException; +use RuntimeException; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\RouteGroupInterface; use Slim\Interfaces\RouteInterface; use Slim\Interfaces\RouterInterface; -use Slim\Middleware\RoutingMiddleware; +use Slim\Middleware\ClosureMiddleware; +use Slim\Middleware\DeferredResolutionMiddleware; +use Slim\Middleware\DispatchMiddleware; /** * App @@ -34,8 +37,6 @@ */ class App implements RequestHandlerInterface { - use MiddlewareAwareTrait; - /** * Current version * @@ -55,6 +56,11 @@ class App implements RequestHandlerInterface */ protected $callableResolver; + /** + * @var MiddlewareRunner + */ + protected $middlewareRunner; + /** * @var RouterInterface */ @@ -70,7 +76,7 @@ class App implements RequestHandlerInterface */ protected $settings = [ 'httpVersion' => '1.1', - 'routerCacheFile' => false, + 'routerCacheFile' => null, ]; /******************************************************************************** @@ -80,54 +86,60 @@ class App implements RequestHandlerInterface /** * Create new application * - * @param ResponseFactoryInterface $responseFactory - * @param ContainerInterface|null $container - * @param array $settings + * @param ResponseFactoryInterface $responseFactory + * @param ContainerInterface|null $container + * @param array $settings + * @param CallableResolverInterface $callableResolver + * @param RouterInterface $router */ public function __construct( ResponseFactoryInterface $responseFactory, ContainerInterface $container = null, - array $settings = [] + array $settings = [], + CallableResolverInterface $callableResolver = null, + RouterInterface $router = null ) { $this->responseFactory = $responseFactory; $this->container = $container; + $this->callableResolver = $callableResolver ?? new CallableResolver($container); $this->addSettings($settings); - } - /** - * Get container - * - * @return ContainerInterface|null - */ - public function getContainer() - { - return $this->container; + $this->router = $router ?? new Router($responseFactory, $this->callableResolver); + $routerCacheFile = $this->getSetting('routerCacheFile', null); + $this->router->setCacheFile($routerCacheFile); + + $this->middlewareRunner = new MiddlewareRunner(); + $this->addMiddleware(new DispatchMiddleware($this->router)); } /** - * Set container - * - * @param ContainerInterface $container + * @param MiddlewareInterface|string|callable $middleware + * @return self */ - public function setContainer(ContainerInterface $container) + public function add($middleware): self { - $this->container = $container; + if (is_string($middleware)) { + $middleware = new DeferredResolutionMiddleware($middleware, $this->container); + } elseif ($middleware instanceof Closure) { + $middleware = new ClosureMiddleware($middleware); + } elseif (!($middleware instanceof MiddlewareInterface)) { + throw new RuntimeException( + 'Parameter 1 of `Slim\App::add()` must be a closure or an object/class name '. + 'referencing an implementation of MiddlewareInterface.' + ); + } + + return $this->addMiddleware($middleware); } /** - * Add middleware - * - * This method prepends new middleware to the app's middleware stack. - * - * @param callable|string $callable The callback routine - * - * @return static + * @param MiddlewareInterface $middleware + * @return self */ - public function add($callable) + public function addMiddleware(MiddlewareInterface $middleware): self { - return $this->addMiddleware( - new DeferredCallable($callable, $this->getCallableResolver()) - ); + $this->middlewareRunner->add($middleware); + return $this; } /******************************************************************************** @@ -189,17 +201,17 @@ public function addSetting(string $key, $value) } /******************************************************************************** - * Setter and getter methods + * Getter methods *******************************************************************************/ /** - * Set callable resolver + * Get container * - * @param CallableResolverInterface $resolver + * @return ContainerInterface|null */ - public function setCallableResolver(CallableResolverInterface $resolver) + public function getContainer(): ?ContainerInterface { - $this->callableResolver = $resolver; + return $this->container; } /** @@ -209,23 +221,9 @@ public function setCallableResolver(CallableResolverInterface $resolver) */ public function getCallableResolver(): CallableResolverInterface { - if (! $this->callableResolver instanceof CallableResolverInterface) { - $this->callableResolver = new CallableResolver($this->container); - } - return $this->callableResolver; } - /** - * Set router - * - * @param RouterInterface $router - */ - public function setRouter(RouterInterface $router) - { - $this->router = $router; - } - /** * Get router * @@ -233,18 +231,6 @@ public function setRouter(RouterInterface $router) */ public function getRouter(): RouterInterface { - if (! $this->router instanceof RouterInterface) { - $router = new Router(); - $resolver = $this->getCallableResolver(); - if ($resolver instanceof CallableResolverInterface) { - $router->setCallableResolver($resolver); - } - $routerCacheFile = $this->getSetting('routerCacheFile', false); - $router->setCacheFile($routerCacheFile); - - $this->router = $router; - } - return $this->router; } @@ -377,7 +363,7 @@ public function map(array $methods, string $pattern, $callable): RouteInterface */ public function redirect(string $from, $to, int $status = 302): RouteInterface { - $handler = function ($request, ResponseInterface $response) use ($to, $status) { + $handler = function (ServerRequestInterface $request, ResponseInterface $response) use ($to, $status) { return $response->withHeader('Location', (string)$to)->withStatus($status); }; @@ -398,17 +384,9 @@ public function redirect(string $from, $to, int $status = 302): RouteInterface */ public function group(string $pattern, $callable): RouteGroupInterface { - $router = $this->getRouter(); - - /** @var RouteGroup $group */ - $group = $router->pushGroup($pattern, $callable); - if ($this->callableResolver instanceof CallableResolverInterface) { - $group->setCallableResolver($this->callableResolver); - } - + $group = $this->router->pushGroup($pattern, $callable); $group($this); - $router->popGroup(); - + $this->router->popGroup(); return $group; } @@ -442,13 +420,7 @@ public function run(ServerRequestInterface $request): void */ public function handle(ServerRequestInterface $request): ResponseInterface { - $httpVersion = $this->getSetting('httpVersion'); - $response = $this->responseFactory - ->createResponse(200, '') - ->withProtocolVersion($httpVersion) - ->withHeader('Content-Type', 'text/html; charset=UTF-8'); - - $response = $this->callMiddlewareStack($request, $response); + $response = $this->middlewareRunner->run($request); /** * This is to be in compliance with RFC 2616, Section 9. @@ -465,36 +437,4 @@ public function handle(ServerRequestInterface $request): ResponseInterface return $response; } - - /** - * Invoke application - * - * This method implements the middleware interface. It receives - * Request and Response objects, and it returns a Response object - * after compiling the routes registered in the Router and dispatching - * the Request object to the appropriate Route callback routine. - * - * @param ServerRequestInterface $request The most recent Request object - * @param ResponseInterface $response The most recent Response object - * - * @return ResponseInterface - * - * @throws HttpNotFoundException - * @throws HttpMethodNotAllowedException - */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface - { - /** @var RoutingResults|null $routingResults */ - $routingResults = $request->getAttribute('routingResults'); - - // If routing hasn't been done, then do it now so we can dispatch - if ($routingResults === null) { - $router = $this->getRouter(); - $routingMiddleware = new RoutingMiddleware($router); - $request = $routingMiddleware->performRouting($request); - } - - $route = $request->getAttribute('route'); - return $route->run($request, $response); - } } diff --git a/Slim/CallableResolver.php b/Slim/CallableResolver.php index ca5bf64e2..008234502 100644 --- a/Slim/CallableResolver.php +++ b/Slim/CallableResolver.php @@ -11,6 +11,7 @@ namespace Slim; +use Closure; use Psr\Container\ContainerInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; @@ -42,7 +43,6 @@ public function __construct(ContainerInterface $container = null) * from the container otherwise instantiate it and then dispatch 'method'. * * @param mixed $toResolve - * * @return callable * * @throws RuntimeException if the callable does not exist @@ -89,10 +89,18 @@ public function resolve($toResolve): callable )); } - if ($this->container instanceof ContainerInterface && $resolved instanceof \Closure) { + if ($this->container instanceof ContainerInterface && $resolved instanceof Closure) { $resolved = $resolved->bindTo($this->container); } return $resolved; } + + /** + * @return ContainerInterface|null + */ + public function getContainer(): ?ContainerInterface + { + return $this->container; + } } diff --git a/Slim/Handlers/Strategies/RequestHandler.php b/Slim/Handlers/Strategies/RequestHandler.php index 226ef52e4..9931e9f5a 100644 --- a/Slim/Handlers/Strategies/RequestHandler.php +++ b/Slim/Handlers/Strategies/RequestHandler.php @@ -21,7 +21,7 @@ class RequestHandler implements InvocationStrategyInterface { /** - * Invoke a route callable that implments RequestHandlerInterface + * Invoke a route callable that implements RequestHandlerInterface * * @param callable $callable * @param ServerRequestInterface $request diff --git a/Slim/Interfaces/CallableResolverInterface.php b/Slim/Interfaces/CallableResolverInterface.php index fc2ca4f70..4f359f7fd 100644 --- a/Slim/Interfaces/CallableResolverInterface.php +++ b/Slim/Interfaces/CallableResolverInterface.php @@ -11,6 +11,8 @@ namespace Slim\Interfaces; +use Psr\Container\ContainerInterface; + /** * Resolves a callable. * @@ -23,8 +25,12 @@ interface CallableResolverInterface * Invoke the resolved callable. * * @param mixed $toResolve - * * @return callable */ public function resolve($toResolve): callable; + + /** + * @return ContainerInterface|null + */ + public function getContainer(): ?ContainerInterface; } diff --git a/Slim/Interfaces/RouteGroupInterface.php b/Slim/Interfaces/RouteGroupInterface.php index 02fad3647..4c4e16e83 100644 --- a/Slim/Interfaces/RouteGroupInterface.php +++ b/Slim/Interfaces/RouteGroupInterface.php @@ -11,6 +11,7 @@ namespace Slim\Interfaces; +use Psr\Http\Server\MiddlewareInterface; use Slim\App; /** @@ -29,13 +30,16 @@ interface RouteGroupInterface public function getPattern(): string; /** - * Prepend middleware to the group middleware collection - * - * @param callable|string $callable The callback routine - * + * @param MiddlewareInterface|string|callable $middleware + * @return RouteGroupInterface + */ + public function add($middleware): RouteGroupInterface; + + /** + * @param MiddlewareInterface $middleware * @return RouteGroupInterface */ - public function add($callable); + public function addMiddleware(MiddlewareInterface $middleware): RouteGroupInterface; /** * Execute route group callable in the context of the Slim App diff --git a/Slim/Interfaces/RouteInterface.php b/Slim/Interfaces/RouteInterface.php index 211b2b0b5..acf13eac5 100644 --- a/Slim/Interfaces/RouteInterface.php +++ b/Slim/Interfaces/RouteInterface.php @@ -13,6 +13,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; /** * Route Interface @@ -22,7 +23,6 @@ */ interface RouteInterface { - /** * Retrieve a specific route argument * @@ -83,15 +83,16 @@ public function setArguments(array $arguments): self; public function setName(string $name): self; /** - * Add middleware - * - * This method prepends new middleware to the route's middleware stack. - * - * @param callable|string $callable The callback routine - * + * @param MiddlewareInterface|string|callable $middleware + * @return RouteInterface + */ + public function add($middleware): RouteInterface; + + /** + * @param MiddlewareInterface $middleware * @return RouteInterface */ - public function add($callable); + public function addMiddleware(MiddlewareInterface $middleware): RouteInterface; /** * Prepare the route for use @@ -109,22 +110,7 @@ public function prepare(ServerRequestInterface $request, array $arguments); * back to the Application. * * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @return ResponseInterface - */ - public function run(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface; - - /** - * Dispatch route callable against current Request and Response objects - * - * This method invokes the route object's callable. If middleware is - * registered for the route, each callable middleware is invoked in - * the order specified. - * - * @param ServerRequestInterface $request The current Request object - * @param ResponseInterface $response The current Response object - * * @return ResponseInterface */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface; + public function run(ServerRequestInterface $request): ResponseInterface; } diff --git a/Slim/Interfaces/RouterInterface.php b/Slim/Interfaces/RouterInterface.php index f538dc094..3c82ac4c2 100644 --- a/Slim/Interfaces/RouterInterface.php +++ b/Slim/Interfaces/RouterInterface.php @@ -24,10 +24,6 @@ */ interface RouterInterface { - // array keys from route result - const DISPATCH_STATUS = 0; - const ALLOWED_METHODS = 1; - /** * Add route * @@ -113,6 +109,12 @@ public function relativePathFor(string $name, array $data = [], array $queryPara */ public function pathFor(string $name, array $data = [], array $queryParams = []): string; + /** + * @param string|null $cacheFile + * @return RouterInterface + */ + public function setCacheFile(?string $cacheFile): RouterInterface; + /** * Set default route invocation strategy * diff --git a/Slim/Middleware/ClosureMiddleware.php b/Slim/Middleware/ClosureMiddleware.php new file mode 100644 index 000000000..532f73a3e --- /dev/null +++ b/Slim/Middleware/ClosureMiddleware.php @@ -0,0 +1,49 @@ +closure = $closure; + } + + /** + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return ($this->closure)($request, $handler); + } +} diff --git a/Slim/Middleware/ContentLengthMiddleware.php b/Slim/Middleware/ContentLengthMiddleware.php index 6443f8155..109397e6f 100644 --- a/Slim/Middleware/ContentLengthMiddleware.php +++ b/Slim/Middleware/ContentLengthMiddleware.php @@ -13,24 +13,24 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; -class ContentLengthMiddleware +/** + * Class ContentLengthMiddleware + * @package Slim\Middleware + */ +class ContentLengthMiddleware implements MiddlewareInterface { /** - * Invoke - * - * @param ServerRequestInterface $request PSR7 server request - * @param ResponseInterface $response PSR7 response - * @param callable $next Middleware callable - * @return ResponseInterface PSR7 response + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - callable $next - ): ResponseInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { /** @var ResponseInterface $response */ - $response = $next($request, $response); + $response = $handler->handle($request); // Add Content-Length header if not already added $size = $response->getBody()->getSize(); diff --git a/Slim/Middleware/DeferredResolutionMiddleware.php b/Slim/Middleware/DeferredResolutionMiddleware.php new file mode 100644 index 000000000..d658d58d0 --- /dev/null +++ b/Slim/Middleware/DeferredResolutionMiddleware.php @@ -0,0 +1,88 @@ +resolvable = $resolvable; + $this->container = $container; + } + + /** + * @return MiddlewareInterface + */ + protected function resolve(): MiddlewareInterface + { + $resolved = $this->resolvable; + + if ($this->container && $this->container->has($this->resolvable)) { + $resolved = $this->container->get($this->resolvable); + + if ($resolved instanceof MiddlewareInterface) { + return $resolved; + } + } + + if (is_subclass_of($resolved, MiddlewareInterface::class)) { + return new $resolved(); + } + + if (is_callable($resolved)) { + $closure = ($resolved instanceof Closure) ? $resolved : Closure::fromCallable($resolved); + return new ClosureMiddleware($closure); + } + + throw new RuntimeException(sprintf( + '%s is not resolvable', + $this->resolvable + )); + } + + /** + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + return $this->resolve()->process($request, $handler); + } +} diff --git a/Slim/Middleware/DispatchMiddleware.php b/Slim/Middleware/DispatchMiddleware.php new file mode 100644 index 000000000..8178cc372 --- /dev/null +++ b/Slim/Middleware/DispatchMiddleware.php @@ -0,0 +1,69 @@ +router = $router; + } + + /** + * This middleware is instantiated automatically in App::__construct() + * It is at the very tip of the middleware queue meaning it will be executed + * last and it detects whether or not routing has been performed in the user + * defined middleware queue. In the event that the user did not perform routing + * it is done here + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + // If routing hasn't been done, then do it now so we can dispatch + if ($request->getAttribute('routingResults') === null) { + $routingMiddleware = new RoutingMiddleware($this->router); + $request = $routingMiddleware->performRouting($request); + } + + /** @var Route $route */ + $route = $request->getAttribute('route'); + return $route->run($request); + } +} diff --git a/Slim/Middleware/ErrorMiddleware.php b/Slim/Middleware/ErrorMiddleware.php index 7bea9ce98..fc4185fb4 100644 --- a/Slim/Middleware/ErrorMiddleware.php +++ b/Slim/Middleware/ErrorMiddleware.php @@ -14,13 +14,19 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; use Slim\Exception\HttpException; use Slim\Handlers\ErrorHandler; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\ErrorHandlerInterface; use Throwable; -class ErrorMiddleware +/** + * Class ErrorMiddleware + * @package Slim\Middleware + */ +class ErrorMiddleware implements MiddlewareInterface { /** * @var CallableResolverInterface @@ -80,21 +86,14 @@ public function __construct( } /** - * Invoke error handler - * - * @param ServerRequestInterface $request The most recent Request object - * @param ResponseInterface $response The most recent Response object - * @param callable $next - * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler * @return ResponseInterface */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - callable $next - ): ResponseInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { try { - return $next($request, $response); + return $handler->handle($request); } catch (Throwable $e) { return $this->handleException($request, $e); } diff --git a/Slim/Middleware/MethodOverrideMiddleware.php b/Slim/Middleware/MethodOverrideMiddleware.php index 75beba633..69e8eaf85 100644 --- a/Slim/Middleware/MethodOverrideMiddleware.php +++ b/Slim/Middleware/MethodOverrideMiddleware.php @@ -13,25 +13,22 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; /** - * Override HTTP Request method by given body param or custom header + * Class MethodOverrideMiddleware + * @package Slim\Middleware */ -class MethodOverrideMiddleware +class MethodOverrideMiddleware implements MiddlewareInterface { /** - * Invoke - * - * @param ServerRequestInterface $request PSR7 server request - * @param ResponseInterface $response PSR7 response - * @param callable $next Middleware callable - * @return ResponseInterface PSR7 response + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - callable $next - ): ResponseInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { $methodHeader = $request->getHeaderLine('X-Http-Method-Override'); if ($methodHeader) { @@ -48,6 +45,6 @@ public function __invoke( } } - return $next($request, $response); + return $handler->handle($request); } } diff --git a/Slim/Middleware/OutputBufferingMiddleware.php b/Slim/Middleware/OutputBufferingMiddleware.php index 1398debb4..c20581d69 100644 --- a/Slim/Middleware/OutputBufferingMiddleware.php +++ b/Slim/Middleware/OutputBufferingMiddleware.php @@ -11,12 +11,19 @@ namespace Slim\Middleware; +use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; use Throwable; -class OutputBufferingMiddleware +/** + * Class OutputBufferingMiddleware + * @package Slim\Middleware + */ +class OutputBufferingMiddleware implements MiddlewareInterface { const APPEND = 'append'; const PREPEND = 'prepend'; @@ -43,44 +50,38 @@ public function __construct(StreamFactoryInterface $streamFactory, string $style $this->style = $style; if (!in_array($style, [static::APPEND, static::PREPEND])) { - throw new \InvalidArgumentException('Invalid style. Must be one of: append, prepend'); + throw new InvalidArgumentException("Invalid style `{$style}`. Must be `append` or `prepend`"); } } /** - * Invoke - * - * @param ServerRequestInterface $request PSR7 server request - * @param ResponseInterface $response PSR7 response - * @param callable $next Middleware callable - * @return ResponseInterface PSR7 response + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface * @throws Throwable */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - callable $next - ): ResponseInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { try { ob_start(); - /** @var ResponseInterface $newResponse */ - $newResponse = $next($request, $response); + /** @var ResponseInterface $response */ + $response = $handler->handle($request); $output = ob_get_clean(); - } catch (\Throwable $e) { + } catch (Throwable $e) { ob_end_clean(); throw $e; } - if (!empty($output) && $newResponse->getBody()->isWritable()) { + if (!empty($output) && $response->getBody()->isWritable()) { if ($this->style === static::PREPEND) { $body = $this->streamFactory->createStream(); - $body->write($output . $newResponse->getBody()); - $newResponse = $newResponse->withBody($body); + $body->write($output . $response->getBody()); + $response = $response->withBody($body); } elseif ($this->style === static::APPEND) { - $newResponse->getBody()->write($output); + $response->getBody()->write($output); } } - return $newResponse; + return $response; } } diff --git a/Slim/Middleware/RoutingMiddleware.php b/Slim/Middleware/RoutingMiddleware.php index eb0f64d5c..17faf16c1 100644 --- a/Slim/Middleware/RoutingMiddleware.php +++ b/Slim/Middleware/RoutingMiddleware.php @@ -14,15 +14,18 @@ use FastRoute\Dispatcher; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use RuntimeException; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; use Slim\Interfaces\RouterInterface; -use RuntimeException; /** - * Perform routing and store matched route to the request's attributes + * Class RoutingMiddleware + * @package Slim\Middleware */ -class RoutingMiddleware +class RoutingMiddleware implements MiddlewareInterface { /** * @var RouterInterface @@ -39,30 +42,24 @@ public function __construct(RouterInterface $router) } /** - * Invoke - * - * @param ServerRequestInterface $request PSR7 server request - * @param ResponseInterface $response PSR7 response - * @param callable $next Middleware callable - * @return ResponseInterface PSR7 response + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface * * @throws HttpNotFoundException * @throws HttpMethodNotAllowedException * @throws RuntimeException */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - callable $next - ): ResponseInterface { + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { $request = $this->performRouting($request); - return $next($request, $response); + return $handler->handle($request); } /** * Perform routing * - * @param ServerRequestInterface $request PSR7 server request + * @param ServerRequestInterface $request PSR7 Server Request * @return ServerRequestInterface * * @throws HttpNotFoundException diff --git a/Slim/MiddlewareAwareTrait.php b/Slim/MiddlewareAwareTrait.php deleted file mode 100644 index 6f221b9da..000000000 --- a/Slim/MiddlewareAwareTrait.php +++ /dev/null @@ -1,124 +0,0 @@ -middlewareLock) { - throw new RuntimeException('Middleware can’t be added once the stack is dequeuing'); - } - - if (is_null($this->tip)) { - $this->seedMiddlewareStack(); - } - $next = $this->tip; - $this->tip = function ( - ServerRequestInterface $request, - ResponseInterface $response - ) use ( - $callable, - $next - ) { - $result = $callable($request, $response, $next); - if ($result instanceof ResponseInterface === false) { - throw new UnexpectedValueException( - 'Middleware must return instance of \Psr\Http\Message\ResponseInterface' - ); - } - - return $result; - }; - - return $this; - } - - /** - * Seed middleware stack with first callable - * - * @param callable $kernel The last item to run as middleware - * - * @throws RuntimeException if the stack is seeded more than once - */ - protected function seedMiddlewareStack(callable $kernel = null) - { - if (!is_null($this->tip)) { - throw new RuntimeException('MiddlewareStack can only be seeded once.'); - } - if ($kernel === null) { - $kernel = $this; - } - $this->tip = $kernel; - } - - /** - * Call middleware stack - * - * @param ServerRequestInterface $request A request object - * @param ResponseInterface $response A response object - * - * @return ResponseInterface - */ - public function callMiddlewareStack(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface - { - if (is_null($this->tip)) { - $this->seedMiddlewareStack(); - } - /** @var callable $start */ - $start = $this->tip; - $this->middlewareLock = true; - $response = $start($request, $response); - $this->middlewareLock = false; - return $response; - } -} diff --git a/Slim/MiddlewareRunner.php b/Slim/MiddlewareRunner.php new file mode 100644 index 000000000..3feba491f --- /dev/null +++ b/Slim/MiddlewareRunner.php @@ -0,0 +1,134 @@ +setMiddleware($middleware); + } + + /** + * @param MiddlewareInterface $middleware + * @return self + */ + public function add(MiddlewareInterface $middleware): self + { + array_unshift($this->middleware, $middleware); + return $this; + } + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function run(ServerRequestInterface $request): ResponseInterface + { + if (empty($this->middleware)) { + throw new RuntimeException('Middleware queue should not be empty.'); + } + + $stages = $this->buildStages(); + $runner = new MiddlewareRunner(); + $runner->setStages($stages); + return $runner->handle($request); + } + + /** + * @return SplObjectStorage + */ + protected function buildStages(): SplObjectStorage + { + $stages = new SplObjectStorage(); + foreach ($this->middleware as $middleware) { + $stages->attach($middleware); + } + $stages->rewind(); + return $stages; + } + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + if (!($this->stages instanceof SplObjectStorage)) { + throw new RuntimeException( + 'Middleware queue stages have not been set yet. '. + 'Please use the `MiddlewareRunner::run()` method.' + ); + } + + /** @var MiddlewareInterface $stage */ + $stage = $this->stages->current(); + $this->stages->next(); + return $stage->process($request, $this); + } + + /** + * @return array + */ + public function getMiddleware(): array + { + return $this->middleware; + } + + /** + * @param MiddlewareInterface[] $middleware List of middleware in LIFO order + * @return self + */ + public function setMiddleware(array $middleware): self + { + $this->middleware = []; + while ($item = array_pop($middleware)) { + $this->add($item); + } + return $this; + } + + /** + * @param SplObjectStorage $stages + * @return self + */ + public function setStages(SplObjectStorage $stages): self + { + $this->stages = $stages; + return $this; + } +} diff --git a/Slim/Routable.php b/Slim/Routable.php index 1ca59fd6c..c8c0fe89d 100644 --- a/Slim/Routable.php +++ b/Slim/Routable.php @@ -11,13 +11,17 @@ namespace Slim; +use Closure; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Server\MiddlewareInterface; +use RuntimeException; use Slim\Interfaces\CallableResolverInterface; +use Slim\Middleware\ClosureMiddleware; +use Slim\Middleware\DeferredResolutionMiddleware; /** - * A routable, middleware-aware object - * + * Class Routable * @package Slim - * @since 3.0.0 */ abstract class Routable { @@ -29,16 +33,19 @@ abstract class Routable protected $callable; /** - * @var CallableResolverInterface|null + * @var CallableResolverInterface */ protected $callableResolver; /** - * Route middleware - * - * @var callable[] + * @var ResponseFactoryInterface + */ + protected $responseFactory; + + /** + * @var MiddlewareRunner */ - protected $middleware = []; + protected $middlewareRunner; /** * Route pattern @@ -48,56 +55,55 @@ abstract class Routable protected $pattern; /** - * Get the middleware registered for the group - * - * @return callable[] + * @param MiddlewareInterface|string|callable $middleware + * @return self */ - public function getMiddleware(): array + protected function addRouteMiddleware($middleware): self { - return $this->middleware; - } + if (is_string($middleware)) { + $middleware = new DeferredResolutionMiddleware($middleware, $this->callableResolver->getContainer()); + } elseif ($middleware instanceof Closure) { + $middleware = new ClosureMiddleware($middleware); + } elseif (!($middleware instanceof MiddlewareInterface)) { + $calledClass = get_called_class(); + throw new RuntimeException( + "Parameter 1 of `{$calledClass}::add()` must be a closure or an object/class name ". + "referencing an implementation of MiddlewareInterface." + ); + } - /** - * Get the route pattern - * - * @return string - */ - public function getPattern(): string - { - return $this->pattern; + $this->middlewareRunner->add($middleware); + return $this; } /** - * Set callable resolver + * Get callable resolver * - * @param CallableResolverInterface $resolver + * @return CallableResolverInterface */ - public function setCallableResolver(CallableResolverInterface $resolver) + public function getCallableResolver(): CallableResolverInterface { - $this->callableResolver = $resolver; + return $this->callableResolver; } /** - * Get callable resolver + * Get the middleware registered for the group * - * @return CallableResolverInterface|null + * @return MiddlewareInterface[] */ - public function getCallableResolver() + public function getMiddleware(): array { - return $this->callableResolver; + return $this->middlewareRunner->getMiddleware(); } /** - * Prepend middleware to the middleware collection - * - * @param callable|string $callable The callback routine + * Get the route pattern * - * @return static + * @return string */ - public function add($callable) + public function getPattern(): string { - $this->middleware[] = new DeferredCallable($callable, $this->callableResolver); - return $this; + return $this->pattern; } /** diff --git a/Slim/Route.php b/Slim/Route.php index 316ae1161..1f70af2ed 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -11,21 +11,22 @@ namespace Slim; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Handlers\Strategies\RequestHandler; use Slim\Handlers\Strategies\RequestResponse; +use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\InvocationStrategyInterface; use Slim\Interfaces\RouteInterface; /** * Route */ -class Route extends Routable implements RouteInterface +class Route extends Routable implements RouteInterface, MiddlewareInterface { - use MiddlewareAwareTrait; - /** * HTTP methods supported by this route * @@ -60,9 +61,9 @@ class Route extends Routable implements RouteInterface private $finalized = false; /** - * @var \Slim\Interfaces\InvocationStrategyInterface + * @var InvocationStrategyInterface */ - protected $routeInvocationStrategy; + protected $invocationStrategy; /** * Route parameters @@ -81,30 +82,34 @@ class Route extends Routable implements RouteInterface /** * Create new route * - * @param string|string[] $methods The route HTTP methods - * @param string $pattern The route pattern - * @param callable|string $callable The route callable - * @param RouteGroup[] $groups The parent route groups - * @param int $identifier The route identifier + * @param string[] $methods The route HTTP methods + * @param string $pattern The route pattern + * @param callable|string $callable The route callable + * @param ResponseFactoryInterface $responseFactory + * @param CallableResolverInterface $callableResolver + * @param InvocationStrategyInterface|null $invocationStrategy + * @param RouteGroup[] $groups The parent route groups + * @param int $identifier The route identifier */ - public function __construct($methods, string $pattern, $callable, array $groups = [], int $identifier = 0) - { - $this->methods = is_string($methods) ? [$methods] : $methods; - $this->pattern = $pattern; + public function __construct( + array $methods, + string $pattern, + $callable, + ResponseFactoryInterface $responseFactory, + CallableResolverInterface $callableResolver, + InvocationStrategyInterface $invocationStrategy = null, + array $groups = [], + int $identifier = 0 + ) { + $this->methods = $methods; + $this->pattern = $pattern; $this->callable = $callable; - $this->groups = $groups; + $this->responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->invocationStrategy = $invocationStrategy ?? new RequestResponse(); + $this->groups = $groups; $this->identifier = 'route' . $identifier; - $this->routeInvocationStrategy = new RequestResponse(); - } - - /** - * Set route invocation strategy - * - * @param InvocationStrategyInterface $strategy - */ - public function setInvocationStrategy(InvocationStrategyInterface $strategy) - { - $this->routeInvocationStrategy = $strategy; + $this->middlewareRunner = new MiddlewareRunner(); } /** @@ -114,30 +119,7 @@ public function setInvocationStrategy(InvocationStrategyInterface $strategy) */ public function getInvocationStrategy(): InvocationStrategyInterface { - return $this->routeInvocationStrategy; - } - - /** - * Finalize the route in preparation for dispatching - */ - public function finalize() - { - if ($this->finalized) { - return; - } - - $groupMiddleware = []; - foreach ($this->getGroups() as $group) { - $groupMiddleware = array_merge($group->getMiddleware(), $groupMiddleware); - } - - $this->middleware = array_merge($this->middleware, $groupMiddleware); - - foreach ($this->getMiddleware() as $middleware) { - $this->addMiddleware($middleware); - } - - $this->finalized = true; + return $this->invocationStrategy; } /** @@ -154,10 +136,12 @@ public function getCallable() * This method enables you to override the Route's callable * * @param callable|string $callable + * @return self */ - public function setCallable($callable) + public function setCallable($callable): self { $this->callable = $callable; + return $this; } /** @@ -183,9 +167,9 @@ public function getGroups(): array /** * Get route name * - * @return null|string + * @return string|null */ - public function getName() + public function getName(): ?string { return $this->name; } @@ -213,13 +197,27 @@ public function setName(string $name): RouteInterface return $this; } + /** + * Retrieve a specific route argument + * + * @param string $name + * @param string|null $default + * @return mixed + */ + public function getArgument(string $name, $default = null) + { + if (array_key_exists($name, $this->arguments)) { + return $this->arguments[$name]; + } + return $default; + } + /** * Set a route argument * * @param string $name * @param string $value * @param bool $includeInSavedArguments - * * @return self */ public function setArgument(string $name, string $value, bool $includeInSavedArguments = true): RouteInterface @@ -232,12 +230,21 @@ public function setArgument(string $name, string $value, bool $includeInSavedArg return $this; } + /** + * Retrieve route arguments + * + * @return array + */ + public function getArguments(): array + { + return $this->arguments; + } + /** * Replace route arguments * * @param array $arguments * @param bool $includeInSavedArguments - * * @return self */ public function setArguments(array $arguments, bool $includeInSavedArguments = true): RouteInterface @@ -251,29 +258,23 @@ public function setArguments(array $arguments, bool $includeInSavedArguments = t } /** - * Retrieve route arguments - * - * @return array + * @param MiddlewareInterface|string|callable $middleware + * @return RouteInterface */ - public function getArguments(): array + public function add($middleware): RouteInterface { - return $this->arguments; + $this->addRouteMiddleware($middleware); + return $this; } /** - * Retrieve a specific route argument - * - * @param string $name - * @param string|null $default - * - * @return mixed + * @param MiddlewareInterface $middleware + * @return RouteInterface */ - public function getArgument(string $name, $default = null) + public function addMiddleware(MiddlewareInterface $middleware): RouteInterface { - if (array_key_exists($name, $this->arguments)) { - return $this->arguments[$name]; - } - return $default; + $this->addRouteMiddleware($middleware); + return $this; } /******************************************************************************** @@ -285,8 +286,9 @@ public function getArgument(string $name, $default = null) * * @param ServerRequestInterface $request * @param array $arguments + * @return void */ - public function prepare(ServerRequestInterface $request, array $arguments) + public function prepare(ServerRequestInterface $request, array $arguments): void { // Remove temp arguments $this->setArguments($this->savedArguments); @@ -297,6 +299,30 @@ public function prepare(ServerRequestInterface $request, array $arguments) } } + /** + * Finalize the route in preparation for dispatching + * @return void + */ + public function finalize(): void + { + if ($this->finalized) { + return; + } + + $groupMiddleware = []; + foreach ($this->groups as $group) { + foreach ($group->getMiddleware() as $middleware) { + array_unshift($groupMiddleware, $middleware); + } + } + + $middleware = array_merge(array_reverse($groupMiddleware), $this->getMiddleware()); + $middleware[] = $this; + $this->middlewareRunner->setMiddleware($middleware); + + $this->finalized = true; + } + /** * Run route * @@ -305,17 +331,15 @@ public function prepare(ServerRequestInterface $request, array $arguments) * back to the Application. * * @param ServerRequestInterface $request - * @param ResponseInterface $response - * * @return ResponseInterface */ - public function run(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + public function run(ServerRequestInterface $request): ResponseInterface { // Finalise route now that we are about to run it $this->finalize(); // Traverse middleware stack and fetch updated response - return $this->callMiddlewareStack($request, $response); + return $this->middlewareRunner->run($request); } /** @@ -325,27 +349,20 @@ public function run(ServerRequestInterface $request, ResponseInterface $response * registered for the route, each callable middleware is invoked in * the order specified. * - * @param ServerRequestInterface $request The current Request object - * @param ResponseInterface $response The current Response object - * @return \Psr\Http\Message\ResponseInterface - * @throws \Exception if the route callable throws an exception + * @param ServerRequestInterface $request The current Request object + * @param RequestHandlerInterface $handler The current RequestHandler object + * @return ResponseInterface */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - /** @var callable $callable */ - $callable = $this->callable; - if ($this->callableResolver) { - $callable = $this->callableResolver->resolve($callable); - } + $callable = $this->callableResolver->resolve($this->callable); - /** @var InvocationStrategyInterface|RequestHandler $handler */ - $handler = $this->routeInvocationStrategy; + $strategy = $this->invocationStrategy; if (is_array($callable) && $callable[0] instanceof RequestHandlerInterface) { - // callables that implement RequestHandlerInterface use the RequestHandler strategy - $handler = new RequestHandler(); + $strategy = new RequestHandler(); } - // invoke route callable via invokation strategy handler - return $handler($callable, $request, $response, $this->arguments); + $response = $this->responseFactory->createResponse(); + return $strategy($callable, $request, $response, $this->arguments); } } diff --git a/Slim/RouteGroup.php b/Slim/RouteGroup.php index 2c70bdffa..1d101f6f2 100644 --- a/Slim/RouteGroup.php +++ b/Slim/RouteGroup.php @@ -11,6 +11,9 @@ namespace Slim; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Server\MiddlewareInterface; +use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\RouteGroupInterface; /** @@ -23,13 +26,42 @@ class RouteGroup extends Routable implements RouteGroupInterface /** * Create a new RouteGroup * - * @param string $pattern The pattern prefix for the group - * @param callable $callable The group callable + * @param string $pattern The pattern prefix for the group + * @param callable $callable The group callable + * @param ResponseFactoryInterface $responseFactory + * @param CallableResolverInterface $callableResolver */ - public function __construct(string $pattern, $callable) - { + public function __construct( + string $pattern, + $callable, + ResponseFactoryInterface $responseFactory, + CallableResolverInterface $callableResolver + ) { $this->pattern = $pattern; $this->callable = $callable; + $this->responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->middlewareRunner = new MiddlewareRunner(); + } + + /** + * @param MiddlewareInterface|string|callable $middleware + * @return RouteGroupInterface + */ + public function add($middleware): RouteGroupInterface + { + $this->addRouteMiddleware($middleware); + return $this; + } + + /** + * @param MiddlewareInterface $middleware + * @return RouteGroupInterface + */ + public function addMiddleware(MiddlewareInterface $middleware): RouteGroupInterface + { + $this->addRouteMiddleware($middleware); + return $this; } /** @@ -40,11 +72,7 @@ public function __construct(string $pattern, $callable) public function __invoke(App $app = null) { /** @var callable $callable */ - $callable = $this->callable; - if ($this->callableResolver) { - $callable = $this->callableResolver->resolve($callable); - } - + $callable = $this->callableResolver->resolve($this->callable); $callable($app); } } diff --git a/Slim/Router.php b/Slim/Router.php index 3d40ffbd3..db7106f68 100644 --- a/Slim/Router.php +++ b/Slim/Router.php @@ -15,8 +15,10 @@ use FastRoute\RouteParser; use FastRoute\RouteParser\Std as StdParser; use InvalidArgumentException; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; +use Slim\Handlers\Strategies\RequestResponse; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\InvocationStrategyInterface; use Slim\Interfaces\RouteGroupInterface; @@ -36,21 +38,19 @@ class Router implements RouterInterface /** * Parser * - * @var \FastRoute\RouteParser + * @var RouteParser */ protected $routeParser; /** - * Callable resolver - * - * @var CallableResolverInterface|null + * @var CallableResolverInterface */ protected $callableResolver; /** - * @var InvocationStrategyInterface|null + * @var InvocationStrategyInterface */ - protected $routeInvocationStrategy; + protected $defaultInvocationStrategy; /** * Base path used in pathFor() @@ -62,9 +62,9 @@ class Router implements RouterInterface /** * Path to fast route cache file. Set to false to disable route caching * - * @var string|False + * @var string|null */ - protected $cacheFile = false; + protected $cacheFile = null; /** * Routes @@ -75,6 +75,7 @@ class Router implements RouterInterface /** * Route counter incrementer + * * @var int */ protected $routeCounter = 0; @@ -91,34 +92,57 @@ class Router implements RouterInterface */ protected $dispatcher; + /** + * @var ResponseFactoryInterface + */ + protected $responseFactory; + /** * Create new router * - * @param RouteParser $parser + * @param ResponseFactoryInterface $responseFactory + * @param CallableResolverInterface $callableResolver + * @param InvocationStrategyInterface $defaultInvocationStrategy + * @param RouteParser $parser */ - public function __construct(RouteParser $parser = null) - { - $this->routeParser = $parser ?: new StdParser; + public function __construct( + ResponseFactoryInterface $responseFactory, + CallableResolverInterface $callableResolver, + InvocationStrategyInterface $defaultInvocationStrategy = null, + RouteParser $parser = null + ) { + $this->responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->defaultInvocationStrategy = $defaultInvocationStrategy ?? new RequestResponse(); + $this->routeParser = $parser ?? new StdParser; } /** - * Set default route invocation strategy + * Get default route invocation strategy * + * @return InvocationStrategyInterface + */ + public function getDefaultInvocationStrategy() + { + return $this->defaultInvocationStrategy; + } + + /** * @param InvocationStrategyInterface $strategy + * @return self */ public function setDefaultInvocationStrategy(InvocationStrategyInterface $strategy) { - $this->routeInvocationStrategy = $strategy; + $this->defaultInvocationStrategy = $strategy; + return $this; } /** - * Get default route invocation strategy - * - * @return InvocationStrategyInterface|null + * @return CallableResolverInterface */ - public function getDefaultInvocationStrategy() + public function getCallableResolver(): CallableResolverInterface { - return $this->routeInvocationStrategy; + return $this->callableResolver; } /** @@ -138,39 +162,23 @@ public function setBasePath(string $basePath): self /** * Set path to fast route cache file. If this is false then route caching is disabled. * - * @param string|bool $cacheFile - * + * @param string|null $cacheFile * @return self * * @throws InvalidArgumentException * @throws RuntimeException */ - public function setCacheFile($cacheFile) + public function setCacheFile(?string $cacheFile): RouterInterface { - if (!is_string($cacheFile) && $cacheFile !== false) { - throw new InvalidArgumentException('Router cacheFile must be a string or false'); - } - $this->cacheFile = $cacheFile; - if ($cacheFile !== false && !is_writable(dirname($cacheFile))) { + if (is_string($cacheFile) && !is_writable(dirname($cacheFile))) { throw new RuntimeException('Router cacheFile directory must be writable'); } - return $this; } - /** - * Set callable resolver - * - * @param CallableResolverInterface $resolver - */ - public function setCallableResolver(CallableResolverInterface $resolver) - { - $this->callableResolver = $resolver; - } - /** * Add route * @@ -219,15 +227,16 @@ public function dispatch(ServerRequestInterface $request): RoutingResults */ protected function createRoute(array $methods, string $pattern, $callable): RouteInterface { - $route = new Route($methods, $pattern, $callable, $this->routeGroups, $this->routeCounter); - if ($this->callableResolver) { - $route->setCallableResolver($this->callableResolver); - } - if ($this->routeInvocationStrategy) { - $route->setInvocationStrategy($this->routeInvocationStrategy); - } - - return $route; + return new Route( + $methods, + $pattern, + $callable, + $this->responseFactory, + $this->callableResolver, + $this->defaultInvocationStrategy, + $this->routeGroups, + $this->routeCounter + ); } /** @@ -339,7 +348,7 @@ protected function processGroups(): string */ public function pushGroup(string $pattern, $callable): RouteGroupInterface { - $group = new RouteGroup($pattern, $callable); + $group = new RouteGroup($pattern, $callable, $this->responseFactory, $this->callableResolver); $this->routeGroups[] = $group; return $group; } @@ -347,9 +356,9 @@ public function pushGroup(string $pattern, $callable): RouteGroupInterface /** * Removes the last route group from the array * - * @return RouteGroup|null The last RouteGroup, if one exists + * @return RouteGroupInterface|null The last RouteGroup, if one exists */ - public function popGroup() + public function popGroup(): ?RouteGroupInterface { return array_pop($this->routeGroups); } diff --git a/composer.json b/composer.json index 56650641c..49415e2e3 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ "psr/container": "^1.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", - "psr/http-server-handler": "^1.0" + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" }, "require-dev": { "ext-simplexml": "*", diff --git a/tests/AppTest.php b/tests/AppTest.php index 2dfc85a3f..a010f0eb2 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -13,13 +13,19 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; +use Psr\Http\Server\RequestHandlerInterface; +use ReflectionClass; use Slim\App; use Slim\CallableResolver; use Slim\Error\Renderers\HtmlErrorRenderer; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Handlers\Strategies\RequestResponseArgs; +use Slim\Middleware\DispatchMiddleware; +use Slim\Route; use Slim\Router; use Slim\Tests\Mocks\MockAction; +use Slim\Tests\Mocks\MockMiddleware; +use Slim\Tests\Mocks\MockMiddlewareWithoutInterface; class AppTest extends TestCase { @@ -28,6 +34,43 @@ public static function setupBeforeClass() ini_set('error_log', tempnam(sys_get_temp_dir(), 'slim')); } + /******************************************************************************** + * Getter methods + *******************************************************************************/ + + public function testGetContainer() + { + $pimple = new Pimple(); + $container = new Psr11Container($pimple); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory, $container); + + $this->assertEquals($container, $app->getContainer()); + } + + public function testGetCallableResolver() + { + $pimple = new Pimple(); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory, $container); + + $this->assertEquals($callableResolver, $app->getCallableResolver()); + } + + public function testGetRouter() + { + $pimple = new Pimple(); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $responseFactory = $this->getResponseFactory(); + $router = new Router($responseFactory, $callableResolver); + $app = new App($responseFactory, $container); + + $this->assertEquals($router, $app->getRouter()); + } + /******************************************************************************** * Settings management methods *******************************************************************************/ @@ -85,34 +128,6 @@ public function testAddSetting() $this->assertAttributeContains('bar', 'settings', $app); } - public function testSetContainer() - { - $responseFactory = $this->getResponseFactory(); - $app = new App($responseFactory); - $pimple = new Pimple(); - $container = new Psr11Container($pimple); - $app->setContainer($container); - $this->assertSame($container, $app->getContainer()); - } - - public function testSetCallableResolver() - { - $responseFactory = $this->getResponseFactory(); - $app = new App($responseFactory); - $callableResolver = new CallableResolver(); - $app->setCallableResolver($callableResolver); - $this->assertSame($callableResolver, $app->getCallableResolver()); - } - - public function testSetRouter() - { - $responseFactory = $this->getResponseFactory(); - $app = new App($responseFactory); - $router = new Router(); - $app->setRouter($router); - $this->assertSame($router, $app->getRouter()); - } - /******************************************************************************** * Router proxy methods *******************************************************************************/ @@ -127,7 +142,7 @@ public function testGetRoute() $app = new App($responseFactory); $route = $app->get($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('GET', 'methods', $route); } @@ -141,7 +156,7 @@ public function testPostRoute() $app = new App($responseFactory); $route = $app->post($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('POST', 'methods', $route); } @@ -156,7 +171,7 @@ public function testPutRoute() $app = new App($responseFactory); $route = $app->put($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('PUT', 'methods', $route); } @@ -171,7 +186,7 @@ public function testPatchRoute() $app = new App($responseFactory); $route = $app->patch($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('PATCH', 'methods', $route); } @@ -186,7 +201,7 @@ public function testDeleteRoute() $app = new App($responseFactory); $route = $app->delete($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('DELETE', 'methods', $route); } @@ -201,7 +216,7 @@ public function testOptionsRoute() $app = new App($responseFactory); $route = $app->options($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('OPTIONS', 'methods', $route); } @@ -216,7 +231,7 @@ public function testAnyRoute() $app = new App($responseFactory); $route = $app->any($path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('GET', 'methods', $route); $this->assertAttributeContains('POST', 'methods', $route); $this->assertAttributeContains('PUT', 'methods', $route); @@ -236,7 +251,7 @@ public function testMapRoute() $app = new App($responseFactory); $route = $app->map(['GET', 'POST'], $path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('GET', 'methods', $route); $this->assertAttributeContains('POST', 'methods', $route); } @@ -250,7 +265,7 @@ public function testMapRouteWithLowercaseMethod() $app = new App($this->getResponseFactory()); $route = $app->map(['get'], $path, $callable); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('get', 'methods', $route); } @@ -263,7 +278,7 @@ public function testRedirectRoute() $app = new App($responseFactory); $route = $app->redirect($source, $destination, 301); - $this->assertInstanceOf('\Slim\Route', $route); + $this->assertInstanceOf(Route::class, $route); $this->assertAttributeContains('GET', 'methods', $route); $response = $route->run($this->createServerRequest($source), $this->createResponse()); @@ -291,20 +306,17 @@ public function testRouteWithInternationalCharacters() return $response; }); - // Prepare request and response objects $request = $this->createServerRequest('/новости'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello', (string)$response->getBody()); } /******************************************************************************** * Route Patterns *******************************************************************************/ + public function testSegmentRouteThatDoesNotEndInASlash() { $responseFactory = $this->getResponseFactory(); @@ -312,7 +324,7 @@ public function testSegmentRouteThatDoesNotEndInASlash() $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo', 'pattern', $router->lookupRoute('route0')); } @@ -324,7 +336,7 @@ public function testSegmentRouteThatEndsInASlash() $app->get('/foo/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/', 'pattern', $router->lookupRoute('route0')); } @@ -336,7 +348,7 @@ public function testSegmentRouteThatDoesNotStartWithASlash() $app->get('foo', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('foo', 'pattern', $router->lookupRoute('route0')); } @@ -348,7 +360,7 @@ public function testSingleSlashRoute() $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/', 'pattern', $router->lookupRoute('route0')); } @@ -360,7 +372,7 @@ public function testEmptyRoute() $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('', 'pattern', $router->lookupRoute('route0')); } @@ -368,12 +380,13 @@ public function testEmptyRoute() /******************************************************************************** * Route Groups *******************************************************************************/ + public function testGroupClosureIsBoundToThisClass() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); $testCase = $this; - $app->group('/foo', function ($app) use ($testCase) { + $app->group('/foo', function (App $app) use ($testCase) { $testCase->assertSame($testCase, $this); }); } @@ -382,12 +395,12 @@ public function testGroupSegmentWithSegmentRouteThatDoesNotEndInASlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { + $app->group('/foo', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/bar', 'pattern', $router->lookupRoute('route0')); } @@ -396,12 +409,12 @@ public function testGroupSegmentWithSegmentRouteThatEndsInASlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { + $app->group('/foo', function (App $app) { $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/bar/', 'pattern', $router->lookupRoute('route0')); } @@ -410,12 +423,12 @@ public function testGroupSegmentWithSingleSlashRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { + $app->group('/foo', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/', 'pattern', $router->lookupRoute('route0')); } @@ -424,12 +437,12 @@ public function testGroupSegmentWithEmptyRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { + $app->group('/foo', function (App $app) { $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo', 'pattern', $router->lookupRoute('route0')); } @@ -438,14 +451,14 @@ public function testTwoGroupSegmentsWithSingleSlashRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/baz/', 'pattern', $router->lookupRoute('route0')); } @@ -454,14 +467,14 @@ public function testTwoGroupSegmentsWithAnEmptyRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/baz', 'pattern', $router->lookupRoute('route0')); } @@ -470,14 +483,14 @@ public function testTwoGroupSegmentsWithSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/baz/bar', 'pattern', $router->lookupRoute('route0')); } @@ -486,14 +499,14 @@ public function testTwoGroupSegmentsWithSegmentRouteThatHasATrailingSlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/baz/bar/', 'pattern', $router->lookupRoute('route0')); } @@ -502,14 +515,14 @@ public function testGroupSegmentWithSingleSlashNestedGroupAndSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('/', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo//bar', 'pattern', $router->lookupRoute('route0')); } @@ -518,14 +531,14 @@ public function testGroupSegmentWithSingleSlashGroupAndSegmentRouteWithoutLeadin { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('/', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/', function (App $app) { $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/bar', 'pattern', $router->lookupRoute('route0')); } @@ -534,14 +547,14 @@ public function testGroupSegmentWithEmptyNestedGroupAndSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foo/bar', 'pattern', $router->lookupRoute('route0')); } @@ -550,14 +563,14 @@ public function testGroupSegmentWithEmptyNestedGroupAndSegmentRouteWithoutLeadin { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/foo', function ($app) { - $app->group('', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('', function (App $app) { $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/foobar', 'pattern', $router->lookupRoute('route0')); } @@ -566,12 +579,12 @@ public function testGroupSingleSlashWithSegmentRouteThatDoesNotEndInASlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { + $app->group('/', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//bar', 'pattern', $router->lookupRoute('route0')); } @@ -580,12 +593,12 @@ public function testGroupSingleSlashWithSegmentRouteThatEndsInASlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { + $app->group('/', function (App $app) { $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//bar/', 'pattern', $router->lookupRoute('route0')); } @@ -594,12 +607,12 @@ public function testGroupSingleSlashWithSingleSlashRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { + $app->group('/', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//', 'pattern', $router->lookupRoute('route0')); } @@ -608,12 +621,12 @@ public function testGroupSingleSlashWithEmptyRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { + $app->group('/', function (App $app) { $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/', 'pattern', $router->lookupRoute('route0')); } @@ -622,14 +635,14 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithSingleSlashRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//baz/', 'pattern', $router->lookupRoute('route0')); } @@ -638,14 +651,14 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithAnEmptyRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//baz', 'pattern', $router->lookupRoute('route0')); } @@ -654,14 +667,14 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//baz/bar', 'pattern', $router->lookupRoute('route0')); } @@ -670,14 +683,14 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithSegmentRouteThatHa { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//baz/bar/', 'pattern', $router->lookupRoute('route0')); } @@ -686,14 +699,14 @@ public function testGroupSingleSlashWithSingleSlashNestedGroupAndSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('/', function ($app) { + $app->group('/', function (App $app) { + $app->group('/', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('///bar', 'pattern', $router->lookupRoute('route0')); } @@ -702,14 +715,14 @@ public function testGroupSingleSlashWithSingleSlashGroupAndSegmentRouteWithoutLe { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('/', function ($app) { + $app->group('/', function (App $app) { + $app->group('/', function (App $app) { $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//bar', 'pattern', $router->lookupRoute('route0')); } @@ -718,14 +731,14 @@ public function testGroupSingleSlashWithEmptyNestedGroupAndSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('', function ($app) { + $app->group('/', function (App $app) { + $app->group('', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//bar', 'pattern', $router->lookupRoute('route0')); } @@ -734,14 +747,14 @@ public function testGroupSingleSlashWithEmptyNestedGroupAndSegmentRouteWithoutLe { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('/', function ($app) { - $app->group('', function ($app) { + $app->group('/', function (App $app) { + $app->group('', function (App $app) { $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/bar', 'pattern', $router->lookupRoute('route0')); } @@ -750,12 +763,12 @@ public function testEmptyGroupWithSegmentRouteThatDoesNotEndInASlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { + $app->group('', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/bar', 'pattern', $router->lookupRoute('route0')); } @@ -764,12 +777,12 @@ public function testEmptyGroupWithSegmentRouteThatEndsInASlash() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { + $app->group('', function (App $app) { $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/bar/', 'pattern', $router->lookupRoute('route0')); } @@ -778,12 +791,12 @@ public function testEmptyGroupWithSingleSlashRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { + $app->group('', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/', 'pattern', $router->lookupRoute('route0')); } @@ -792,12 +805,12 @@ public function testEmptyGroupWithEmptyRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { + $app->group('', function (App $app) { $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('', 'pattern', $router->lookupRoute('route0')); } @@ -806,14 +819,14 @@ public function testEmptyGroupWithNestedGroupSegmentWithSingleSlashRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('/baz', function ($app) { + $app->group('', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/baz/', 'pattern', $router->lookupRoute('route0')); } @@ -822,14 +835,14 @@ public function testEmptyGroupWithNestedGroupSegmentWithAnEmptyRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('/baz', function ($app) { + $app->group('', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/baz', 'pattern', $router->lookupRoute('route0')); } @@ -838,14 +851,14 @@ public function testEmptyGroupWithNestedGroupSegmentWithSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('/baz', function ($app) { + $app->group('', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/baz/bar', 'pattern', $router->lookupRoute('route0')); } @@ -854,14 +867,14 @@ public function testEmptyGroupWithNestedGroupSegmentWithSegmentRouteThatHasATrai { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('/baz', function ($app) { + $app->group('', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/baz/bar/', 'pattern', $router->lookupRoute('route0')); } @@ -870,14 +883,14 @@ public function testEmptyGroupWithSingleSlashNestedGroupAndSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('/', function ($app) { + $app->group('', function (App $app) { + $app->group('/', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('//bar', 'pattern', $router->lookupRoute('route0')); } @@ -886,14 +899,14 @@ public function testEmptyGroupWithSingleSlashGroupAndSegmentRouteWithoutLeadingS { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('/', function ($app) { + $app->group('', function (App $app) { + $app->group('/', function (App $app) { $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/bar', 'pattern', $router->lookupRoute('route0')); } @@ -902,14 +915,14 @@ public function testEmptyGroupWithEmptyNestedGroupAndSegmentRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('', function ($app) { + $app->group('', function (App $app) { + $app->group('', function (App $app) { $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('/bar', 'pattern', $router->lookupRoute('route0')); } @@ -918,14 +931,14 @@ public function testEmptyGroupWithEmptyNestedGroupAndSegmentRouteWithoutLeadingS { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->group('', function ($app) { - $app->group('', function ($app) { + $app->group('', function (App $app) { + $app->group('', function (App $app) { $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); }); - /** @var \Slim\Router $router */ + $router = $app->getRouter(); $this->assertAttributeEquals('bar', 'pattern', $router->lookupRoute('route0')); } @@ -934,24 +947,25 @@ public function testEmptyGroupWithEmptyNestedGroupAndSegmentRouteWithoutLeadingS * Middleware *******************************************************************************/ - public function testBottomMiddlewareIsApp() + public function testBottomMiddlewareIsDispatchMiddleware() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $bottom = null; - $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { - $bottom = $next; - return $response; - }; - $app->add($mw); + $reflection = new ReflectionClass(App::class); + $property = $reflection->getProperty('middlewareRunner'); + $property->setAccessible(true); + $middlewareRunner = $property->getValue($app); - $app->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $app->add(function ($request, $handler) use (&$bottom, $responseFactory) { + return $responseFactory->createResponse(); + }); - $this->assertEquals($app, $bottom); + /** @var array $middleware */ + $middleware = $middlewareRunner->getMiddleware(); + $bottom = $middleware[1]; + + $this->assertInstanceOf(DispatchMiddleware::class, $bottom); } public function testAddMiddleware() @@ -960,156 +974,205 @@ public function testAddMiddleware() $app = new App($responseFactory); $called = 0; - $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$called) { + $app->add(function ($request, $handler) use (&$called, $responseFactory) { $called++; - return $response; - }; - $app->add($mw); + return $responseFactory->createResponse(); + }); - $app->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $request = $this->createServerRequest('/'); + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + return $response; + }); + $app->handle($request); $this->assertSame($called, 1); } + public function testAddMiddlewareUsingDeferredResolution() + { + $responseFactory = $this->getResponseFactory(); + + $pimple = new Pimple(); + $pimple->offsetSet(MockMiddleware::class, new MockMiddleware($responseFactory)); + $container = new Psr11Container($pimple); + + $app = new App($responseFactory, $container); + $app->add(MockMiddleware::class); + + $request = $this->createServerRequest('/'); + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + return $response; + }); + + $response = $app->handle($request); + $this->assertSame('Hello World', (string) $response->getBody()); + } + public function testAddMiddlewareOnRoute() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write('Center'); + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('Center'); return $response; - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In1'); - $response = $next($request, $response); - $response->getBody()->write('Out1'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In1'); + $response = $handler->handle($request); + $appendToOutput('Out1'); return $response; - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In2'); - $response = $next($request, $response); - $response->getBody()->write('Out2'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In2'); + $response = $handler->handle($request); + $appendToOutput('Out2'); return $response; }); - // Prepare request and response objects + $output = ''; + $appendToOutput = function (string $value) use (&$output) { + $output .= $value; + }; $request = $this->createServerRequest('/'); - $response = $this->createResponse(); + $request = $request->withAttribute('appendToOutput', $appendToOutput); - // Invoke app - $response = $app($request, $response); + $app->run($request); - $this->assertEquals('In2In1CenterOut1Out2', (string)$response->getBody()); + $this->assertEquals('In2In1CenterOut1Out2', $output); } - public function testAddMiddlewareOnRouteGroup() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - - $app->group('/foo', function ($app) { + $app->group('/foo', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write('Center'); + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('Center'); return $response; }); - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In1'); - $response = $next($request, $response); - $response->getBody()->write('Out1'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In1'); + $response = $handler->handle($request); + $appendToOutput('Out1'); return $response; - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In2'); - $response = $next($request, $response); - $response->getBody()->write('Out2'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In2'); + $response = $handler->handle($request); + $appendToOutput('Out2'); return $response; }); - // Prepare request and response objects + $output = ''; + $appendToOutput = function (string $value) use (&$output) { + $output .= $value; + }; $request = $this->createServerRequest('/foo/'); - $response = $this->createResponse(); + $request = $request->withAttribute('appendToOutput', $appendToOutput); - // Invoke app - $response = $app($request, $response); + $app->run($request); - $this->assertEquals('In2In1CenterOut1Out2', (string)$response->getBody()); + $this->assertEquals('In2In1CenterOut1Out2', $output); } public function testAddMiddlewareOnTwoRouteGroup() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - - $app->group('/foo', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write('Center'); + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('Center'); return $response; }); - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In2'); - $response = $next($request, $response); - $response->getBody()->write('Out2'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In2'); + $response = $handler->handle($request); + $appendToOutput('Out2'); return $response; }); - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In1'); - $response = $next($request, $response); - $response->getBody()->write('Out1'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In1'); + $response = $handler->handle($request); + $appendToOutput('Out1'); return $response; }); - // Prepare request and response objects + // Prepare request object + $output = ''; + $appendToOutput = function (string $value) use (&$output) { + $output .= $value; + }; $request = $this->createServerRequest('/foo/baz/'); - $response = $this->createResponse(); + $request = $request->withAttribute('appendToOutput', $appendToOutput); - // Invoke app - $response = $app($request, $response); + $app->run($request); - $this->assertEquals('In1In2CenterOut2Out1', (string)$response->getBody()); + $this->assertEquals('In1In2CenterOut2Out1', $output); } public function testAddMiddlewareOnRouteAndOnTwoRouteGroup() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - - $app->group('/foo', function ($app) { - $app->group('/baz', function ($app) { + $app->group('/foo', function (App $app) { + $app->group('/baz', function (App $app) { $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write('Center'); + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('Center'); return $response; - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In3'); - $response = $next($request, $response); - $response->getBody()->write('Out3'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In3'); + $response = $handler->handle($request); + $appendToOutput('Out3'); return $response; }); - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In2'); - $response = $next($request, $response); - $response->getBody()->write('Out2'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In2'); + $response = $handler->handle($request); + $appendToOutput('Out2'); return $response; }); - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In1'); - $response = $next($request, $response); - $response->getBody()->write('Out1'); + })->add(function ($request, $handler) { + $appendToOutput = $request->getAttribute('appendToOutput'); + $appendToOutput('In1'); + $response = $handler->handle($request); + $appendToOutput('Out1'); return $response; }); - // Prepare request and response objects + $output = ''; + $appendToOutput = function (string $value) use (&$output) { + $output .= $value; + }; $request = $this->createServerRequest('/foo/baz/'); - $response = $this->createResponse(); + $request = $request->withAttribute('appendToOutput', $appendToOutput); - // Invoke app - $response = $app($request, $response); + $app->run($request); - $this->assertEquals('In1In2In3CenterOut3Out2Out1', (string)$response->getBody()); + $this->assertEquals('In1In2In3CenterOut3Out2Out1', $output); } + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage + * Parameter 1 of `Slim\App::add()` must be a closure or an object/class name + * referencing an implementation of MiddlewareInterface. + */ + public function testAddMiddlewareAsStringNotImplementingInterfaceThrowsException() + { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->add(new MockMiddlewareWithoutInterface()); + } /******************************************************************************** * Runner @@ -1127,24 +1190,19 @@ public function testInvokeReturnMethodNotAllowed() return $response; }); - // Prepare request and response objects $request = $this->createServerRequest('/foo', 'POST'); - $response = $this->createResponse(); - // Create Html Renderer and Assert Output $exception = new HttpMethodNotAllowedException($request); $exception->setAllowedMethods(['GET']); - $renderer = new HtmlErrorRenderer(); - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals(405, (string)$resOut->getStatusCode()); - $this->assertEquals(['GET'], $resOut->getHeader('Allow')); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals(405, (string)$response->getStatusCode()); + $this->assertEquals(['GET'], $response->getHeader('Allow')); $this->assertContains( - $renderer->render($exception, false), - (string)$resOut->getBody() + (new HtmlErrorRenderer())->render($exception, false), + (string)$response->getBody() ); } @@ -1157,15 +1215,11 @@ public function testInvokeWithMatchingRoute() return $response; }); - // Prepare request and response objects $request = $this->createServerRequest('/foo'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello', (string)$response->getBody()); } public function testInvokeWithMatchingRouteWithSetArgument() @@ -1177,15 +1231,11 @@ public function testInvokeWithMatchingRouteWithSetArgument() return $response; })->setArgument('attribute', 'world!'); - // Prepare request and response objects $request = $this->createServerRequest('/foo/bar'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello world!', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello world!', (string)$response->getBody()); } public function testInvokeWithMatchingRouteWithSetArguments() @@ -1197,15 +1247,11 @@ public function testInvokeWithMatchingRouteWithSetArguments() return $response; })->setArguments(['attribute1' => 'there', 'attribute2' => 'world!']); - // Prepare request and response objects $request = $this->createServerRequest('/foo/bar'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello there world!', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello there world!', (string)$response->getBody()); } public function testInvokeWithMatchingRouteWithNamedParameter() @@ -1217,15 +1263,11 @@ public function testInvokeWithMatchingRouteWithNamedParameter() return $response; }); - // Prepare request and response objects $request = $this->createServerRequest('/foo/test!'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello test!', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello test!', (string)$response->getBody()); } public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseArgStrategy() @@ -1238,15 +1280,11 @@ public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseArgS return $response; }); - // Prepare request and response objects $request = $this->createServerRequest('/foo/test!'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello test!', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello test!', (string)$response->getBody()); } public function testInvokeWithMatchingRouteWithNamedParameterOverwritesSetArgument() @@ -1258,15 +1296,11 @@ public function testInvokeWithMatchingRouteWithNamedParameterOverwritesSetArgume return $response; })->setArguments(['extra' => 'there', 'name' => 'world!']); - // Prepare request and response objects $request = $this->createServerRequest('/foo/test!'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello there test!', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello there test!', (string)$response->getBody()); } /** @@ -1281,20 +1315,15 @@ public function testInvokeWithoutMatchingRoute() return $response; }); - // Prepare request and response objects $request = $this->createServerRequest('/foo'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertAttributeEquals(404, 'status', $resOut); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertAttributeEquals(404, 'status', $response); } public function testInvokeWithCallableRegisteredInContainer() { - // Prepare request and response objects $request = $this->createServerRequest('/foo'); $response = $this->createResponse(); @@ -1308,17 +1337,16 @@ public function testInvokeWithCallableRegisteredInContainer() ->willReturn($response); return $mock; }; + $container = new Psr11Container($pimple); $responseFactory = $this->getResponseFactory(); - $container = new Psr11Container($pimple); $app = new App($responseFactory, $container); $app->get('/foo', 'foo:bar'); - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('Hello', (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('Hello', (string)$response->getBody()); } /** @@ -1326,9 +1354,7 @@ public function testInvokeWithCallableRegisteredInContainer() */ public function testInvokeWithNonExistentMethodOnCallableRegisteredInContainer() { - // Prepare request and response objects $request = $this->createServerRequest('/foo'); - $response = $this->createResponse(); $mock = $this->getMockBuilder('StdClass')->getMock(); @@ -1336,21 +1362,18 @@ public function testInvokeWithNonExistentMethodOnCallableRegisteredInContainer() $pimple['foo'] = function () use ($mock) { return $mock; }; + $container = new Psr11Container($pimple); $responseFactory = $this->getResponseFactory(); - $container = new Psr11Container($pimple); $app = new App($responseFactory, $container); $app->get('/foo', 'foo:bar'); - // Invoke app - $app($request, $response); + $app->handle($request); } public function testInvokeWithCallableInContainerViaMagicMethod() { - // Prepare request and response objects $request = $this->createServerRequest('/foo'); - $response = $this->createResponse(); $mock = new MockAction(); @@ -1358,17 +1381,16 @@ public function testInvokeWithCallableInContainerViaMagicMethod() $pimple['foo'] = function () use ($mock) { return $mock; }; + $container = new Psr11Container($pimple); $responseFactory = $this->getResponseFactory(); - $container = new Psr11Container($pimple); $app = new App($responseFactory, $container); $app->get('/foo', 'foo:bar'); - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals(json_encode(['name'=>'bar', 'arguments' => []]), (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals(json_encode(['name'=>'bar', 'arguments' => []]), (string)$response->getBody()); } public function testInvokeFunctionName() @@ -1377,24 +1399,18 @@ public function testInvokeFunctionName() $app = new App($responseFactory); // @codingStandardsIgnoreStart - function handle(ServerRequestInterface $request, ResponseInterface $response) - { + function handle(ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('foo'); - return $response; } // @codingStandardsIgnoreEnd $app->get('/foo', __NAMESPACE__ . '\handle'); - // Prepare request and response objects $request = $this->createServerRequest('/foo'); - $response = $this->createResponse(); - - // Invoke app - $resOut = $app($request, $response); + $response = $app->handle($request); - $this->assertEquals('foo', (string)$resOut->getBody()); + $this->assertEquals('foo', (string)$response->getBody()); } public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArguments() @@ -1406,14 +1422,10 @@ public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArguments() return $response; }); - // Prepare request and response objects - $request = $this->createServerRequest('/foo/rob'); - $request = $request->withAttribute("one", 1); - $response = $this->createResponse(); + $request = $this->createServerRequest('/foo/rob')->withAttribute("one", 1); + $response = $app->handle($request); - // Invoke app - $resOut = $app($request, $response); - $this->assertEquals('1rob', (string)$resOut->getBody()); + $this->assertEquals('1rob', (string)$response->getBody()); } public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArgumentsRequestResponseArg() @@ -1426,14 +1438,10 @@ public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArgumentsRe return $response; }); - // Prepare request and response objects - $request = $this->createServerRequest('/foo/rob'); - $request = $request->withAttribute("one", 1); - $response = $this->createResponse(); + $request = $this->createServerRequest('/foo/rob')->withAttribute("one", 1); + $response = $app->handle($request); - // Invoke app - $resOut = $app($request, $response); - $this->assertEquals('1rob', (string)$resOut->getBody()); + $this->assertEquals('1rob', (string)$response->getBody()); } public function testRun() @@ -1482,9 +1490,8 @@ public function testCanBeReExecutedRecursivelyDuringDispatch() $response = $app->handle($request->withAddedHeader('X-NESTED', '1')); return $response->withAddedHeader('X-TRACE', 'outer'); - }); - $app->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response = $next($request, $response); + })->add(function (ServerRequestInterface $request, RequestHandlerInterface $handler) { + $response = $handler->handle($request); $response->getBody()->write('1'); return $response; }); @@ -1503,129 +1510,103 @@ public function testCanBeReExecutedRecursivelyDuringDispatch() public function testContainerSetToRoute() { - // Prepare request and response objects - $request = $this->createServerRequest('/foo'); - $response = $this->createResponse(); - $mock = new MockAction(); - $pimple = new Pimple(); $pimple['foo'] = function () use ($mock) { return $mock; }; + $container = new Psr11Container($pimple); $responseFactory = $this->getResponseFactory(); - $app = new App($responseFactory); - $app->setContainer(new Psr11Container($pimple)); + $app = new App($responseFactory, $container); - /** @var Router $router */ $router = $app->getRouter(); $router->map(['GET'], '/foo', 'foo:bar'); - // Invoke app - $resOut = $app($request, $response); + $request = $this->createServerRequest('/foo'); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals(json_encode(['name'=>'bar', 'arguments' => []]), (string)$resOut->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals(json_encode(['name'=>'bar', 'arguments' => []]), (string)$response->getBody()); } public function testAppIsARequestHandler() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $this->assertInstanceOf('Psr\Http\Server\RequestHandlerInterface', $app); + $this->assertInstanceOf(RequestHandlerInterface::class, $app); } public function testInvokeSequentialProccessToAPathWithOptionalArgsAndWithoutOptionalArgs() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->get('/foo[/{bar}]', function ($req, $res, $args) { - $res->getBody()->write((string)count($args)); - return $res; + $app->get('/foo[/{bar}]', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write((string) count($args)); + return $response; }); - // Prepare request and response objects - $req = $this->createServerRequest('/foo/bar', 'GET'); - $res = $this->createResponse(); - - // Invoke process with optional arg - $resOut = $app($req, $res); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('1', (string)$resOut->getBody()); + $request = $this->createServerRequest('/foo/bar', 'GET'); + $response = $app->handle($request); - // Prepare request and response objects - $req = $this->createServerRequest('/foo', 'GET'); - $res = $this->createResponse(); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('1', (string) $response->getBody()); - // Invoke process without optional arg - $resOut2 = $app($req, $res); + $request = $this->createServerRequest('/foo', 'GET'); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut2); - $this->assertEquals('0', (string)$resOut2->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('0', (string) $response->getBody()); } public function testInvokeSequentialProccessToAPathWithOptionalArgsAndWithoutOptionalArgsAndKeepSetedArgs() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $app->get('/foo[/{bar}]', function ($req, $res, $args) { - $res->getBody()->write((string)count($args)); - return $res; + $app->get('/foo[/{bar}]', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write((string) count($args)); + return $response; })->setArgument('baz', 'quux'); - // Prepare request and response objects - $req = $this->createServerRequest('/foo/bar', 'GET'); - $res = $this->createResponse(); - - // Invoke process without optional arg - $resOut = $app($req, $res); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('2', (string)$resOut->getBody()); + $request = $this->createServerRequest('/foo/bar', 'GET'); + $response = $app->handle($request); - // Prepare request and response objects - $req = $this->createServerRequest('/foo', 'GET'); - $res = $this->createResponse(); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('2', (string) $response->getBody()); - // Invoke process with optional arg - $resOut2 = $app($req, $res); + $request = $this->createServerRequest('/foo', 'GET'); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut2); - $this->assertEquals('1', (string)$resOut2->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('1', (string) $response->getBody()); } public function testInvokeSequentialProccessAfterAddingAnotherRouteArgument() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); - $route = $app->get('/foo[/{bar}]', function ($req, $res, $args) { - $res->getBody()->write((string)count($args)); - return $res; + $route = $app->get('/foo[/{bar}]', function ( + ServerRequestInterface $request, + ResponseInterface $response, + $args + ) { + $response->getBody()->write((string) count($args)); + return $response; })->setArgument('baz', 'quux'); - // Prepare request and response objects - $req = $this->createServerRequest('/foo/bar', 'GET'); - $res = $this->createResponse(); - - // Invoke process with optional arg - $resOut = $app($req, $res); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals('2', (string)$resOut->getBody()); + $request = $this->createServerRequest('/foo/bar', 'GET'); + $response = $app->handle($request); - // Prepare request and response objects - $req = $this->createServerRequest('/foo/bar', 'GET'); - $res = $this->createResponse(); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('2', (string) $response->getBody()); - // add another argument + // Add Another Argument To Route $route->setArgument('one', '1'); - // Invoke process with optional arg - $resOut2 = $app($req, $res); + $request = $this->createServerRequest('/foo/bar', 'GET'); + $response = $app->handle($request); - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut2); - $this->assertEquals('3', (string)$resOut2->getBody()); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals('3', (string) $response->getBody()); } } diff --git a/tests/CallableResolverTest.php b/tests/CallableResolverTest.php index e3cb5373e..601e0c8fa 100644 --- a/tests/CallableResolverTest.php +++ b/tests/CallableResolverTest.php @@ -10,6 +10,7 @@ use Pimple\Container as Pimple; use Pimple\Psr11\Container; +use Psr\Container\ContainerInterface; use Slim\CallableResolver; use Slim\Tests\Mocks\CallableTest; use Slim\Tests\Mocks\InvokableTest; @@ -18,7 +19,7 @@ class CallableResolverTest extends TestCase { /** - * @var Container + * @var ContainerInterface */ private $container; @@ -40,29 +41,24 @@ public function setUp() public function testClosure() { $test = function () { - static $called_count = 0; - return $called_count++; + return true; }; $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve($test); - $callable(); - $this->assertEquals(1, $callable()); + + $this->assertEquals(true, $callable()); } public function testFunctionName() { - // @codingStandardsIgnoreStart function testCallable() { - static $called_count = 0; - return $called_count++; - }; - // @codingStandardsIgnoreEnd - + return true; + } $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve(__NAMESPACE__ . '\testCallable'); - $callable(); - $this->assertEquals(1, $callable()); + + $this->assertEquals(true, $callable()); } public function testObjMethodArray() @@ -71,6 +67,7 @@ public function testObjMethodArray() $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve([$obj, 'toCall']); $callable(); + $this->assertEquals(1, CallableTest::$CalledCount); } @@ -79,6 +76,7 @@ public function testSlimCallable() $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); $callable(); + $this->assertEquals(1, CallableTest::$CalledCount); } @@ -86,6 +84,7 @@ public function testSlimCallableContainer() { $resolver = new CallableResolver($this->container); $resolver->resolve('Slim\Tests\Mocks\CallableTest:toCall'); + $this->assertEquals($this->container, CallableTest::$CalledContainer); } @@ -95,6 +94,7 @@ public function testContainer() $resolver = new CallableResolver($this->container); $callable = $resolver->resolve('callable_service:toCall'); $callable(); + $this->assertEquals(1, CallableTest::$CalledCount); } @@ -106,6 +106,7 @@ public function testResolutionToAnInvokableClassInContainer() $resolver = new CallableResolver($this->container); $callable = $resolver->resolve('an_invokable'); $callable(); + $this->assertEquals(1, InvokableTest::$CalledCount); } @@ -114,6 +115,7 @@ public function testResolutionToAnInvokableClass() $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve('Slim\Tests\Mocks\InvokableTest'); $callable(); + $this->assertEquals(1, InvokableTest::$CalledCount); } @@ -123,6 +125,7 @@ public function testResolutionToAPsrRequestHandlerClass() $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve(RequestHandlerTest::class); $callable($request); + $this->assertEquals("1", RequestHandlerTest::$CalledCount); } @@ -133,6 +136,7 @@ public function testObjPsrRequestHandlerClass() $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve($obj); $callable($request); + $this->assertEquals("1", RequestHandlerTest::$CalledCount); } @@ -143,6 +147,7 @@ public function testObjPsrRequestHandlerClassInContainer() $resolver = new CallableResolver($this->container); $callable = $resolver->resolve('a_requesthandler'); $callable($request); + $this->assertEquals("1", RequestHandlerTest::$CalledCount); } @@ -153,7 +158,7 @@ public function testMethodNotFoundThrowException() { $this->pimple['callable_service'] = new CallableTest(); $resolver = new CallableResolver($this->container); - $resolver->resolve('callable_service:noFound'); + $resolver->resolve('callable_service:notFound'); } /** @@ -162,7 +167,7 @@ public function testMethodNotFoundThrowException() public function testFunctionNotFoundThrowException() { $resolver = new CallableResolver($this->container); - $resolver->resolve('noFound'); + $resolver->resolve('notFound'); } /** diff --git a/tests/DeferredCallableTest.php b/tests/DeferredCallableTest.php index b4d88815e..8d3633978 100644 --- a/tests/DeferredCallableTest.php +++ b/tests/DeferredCallableTest.php @@ -20,7 +20,8 @@ public function testItResolvesCallable() { $pimple = new Pimple(); $pimple['CallableTest'] = new CallableTest; - $resolver = new CallableResolver(new Container($pimple)); + $container = new Container($pimple); + $resolver = new CallableResolver($container); $deferred = new DeferredCallable('CallableTest:toCall', $resolver); $deferred(); @@ -38,8 +39,8 @@ public function testItBindsClosuresToContainer() $pimple = new Pimple(); $container = new Container($pimple); $resolver = new CallableResolver($container); - $test = $this; + $test = $this; $closure = function () use ($container, $test, $assertCalled) { $assertCalled->foo(); $test->assertSame($container, $this); @@ -52,7 +53,9 @@ public function testItBindsClosuresToContainer() public function testItReturnsInvokedCallableResponse() { $pimple = new Pimple(); - $resolver = new CallableResolver(new Container($pimple)); + $container = new Container($pimple); + $resolver = new CallableResolver($container); + $test = $this; $foo = 'foo'; $bar = 'bar'; diff --git a/tests/Exception/HttpUnauthorizedExceptionTest.php b/tests/Exception/HttpUnauthorizedExceptionTest.php index 09c68e31b..06fa79c95 100644 --- a/tests/Exception/HttpUnauthorizedExceptionTest.php +++ b/tests/Exception/HttpUnauthorizedExceptionTest.php @@ -8,7 +8,6 @@ */ namespace Slim\Tests\Exception; -use Psr\Http\Message\ServerRequestInterface; use Slim\Exception\HttpUnauthorizedException; use Slim\Tests\TestCase; diff --git a/tests/Middleware/ContentLengthMiddlewareTest.php b/tests/Middleware/ContentLengthMiddlewareTest.php index 229c1de4c..f9a7797e9 100644 --- a/tests/Middleware/ContentLengthMiddlewareTest.php +++ b/tests/Middleware/ContentLengthMiddlewareTest.php @@ -8,27 +8,30 @@ */ namespace Slim\Tests\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; +use Slim\Middleware\ClosureMiddleware; use Slim\Middleware\ContentLengthMiddleware; +use Slim\MiddlewareRunner; use Slim\Tests\TestCase; class ContentLengthMiddlewareTest extends TestCase { - public function testAddsContentLenght() + public function testAddsContentLength() { - $mw = new ContentLengthMiddleware(); + $request = $this->createServerRequest('/'); + $responseFactory = $this->getResponseFactory(); - $request = $this->createServerRequest('https://example.com:443/foo/bar?abc=123'); - $response = $this->createResponse(); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $mw = new ClosureMiddleware(function ($request, $handler) use ($responseFactory) { + $response = $responseFactory->createResponse(); $response->getBody()->write('Body'); return $response; - }; + }); + $mw2 = new ContentLengthMiddleware(); - $newResponse = $mw($request, $response, $next); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $response = $middlewareRunner->run($request); - $this->assertEquals(4, $newResponse->getHeaderLine('Content-Length')); + $this->assertEquals(4, $response->getHeaderLine('Content-Length')); } } diff --git a/tests/Middleware/DeferredResolutionMiddlewareTest.php b/tests/Middleware/DeferredResolutionMiddlewareTest.php new file mode 100644 index 000000000..ff5b49a0b --- /dev/null +++ b/tests/Middleware/DeferredResolutionMiddlewareTest.php @@ -0,0 +1,129 @@ +handle($request); + } + + $deferredResolutionMiddleware = new DeferredResolutionMiddleware(__NAMESPACE__ . '\testProcessRequest'); + + $reflection = new ReflectionClass(DeferredResolutionMiddleware::class); + $method = $reflection->getMethod('resolve'); + $method->setAccessible(true); + + /** @var MiddlewareInterface $result */ + $result = $method->invoke($deferredResolutionMiddleware); + $this->assertInstanceOf(ClosureMiddleware::class, $result); + + $request = $this->createServerRequest('/'); + $handler = new MockRequestHandler(); + + $result->process($request, $handler); + $this->assertEquals(1, $handler->getCalledCount()); + } + + public function testDeferredResolvedCallableGetsWrappedInsideClosureMiddleware() + { + $pimple = new Pimple(); + $pimple['callable'] = function () { + return function (ServerRequestInterface $request, RequestHandlerInterface $handler) { + return $handler->handle($request); + }; + }; + $container = new Psr11Container($pimple); + + $deferredResolutionMiddleware = new DeferredResolutionMiddleware('callable', $container); + + $reflection = new ReflectionClass(DeferredResolutionMiddleware::class); + $method = $reflection->getMethod('resolve'); + $method->setAccessible(true); + + /** @var MiddlewareInterface $result */ + $result = $method->invoke($deferredResolutionMiddleware); + $this->assertInstanceOf(ClosureMiddleware::class, $result); + + $request = $this->createServerRequest('/'); + $handler = new MockRequestHandler(); + + $result->process($request, $handler); + $this->assertEquals(1, $handler->getCalledCount()); + } + + public function testResolvableReturnsInstantiatedObject() + { + $reflection = new ReflectionClass(DeferredResolutionMiddleware::class); + $deferredResolutionMiddlewareWrapper = new DeferredResolutionMiddleware( + MockMiddlewareWithoutConstructor::class + ); + + $method = $reflection->getMethod('resolve'); + $method->setAccessible(true); + $result = $method->invoke($deferredResolutionMiddlewareWrapper); + + $this->assertInstanceOf(MockMiddlewareWithoutConstructor::class, $result); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage MiddlewareInterfaceNotImplemented is not resolvable + */ + public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface() + { + $pimple = new Pimple(); + $pimple['MiddlewareInterfaceNotImplemented'] = new stdClass(); + $container = new Psr11Container($pimple); + + $reflection = new ReflectionClass(DeferredResolutionMiddleware::class); + $deferredResolutionMiddlewareWrapper = new DeferredResolutionMiddleware( + 'MiddlewareInterfaceNotImplemented', + $container + ); + + $method = $reflection->getMethod('resolve'); + $method->setAccessible(true); + $method->invoke($deferredResolutionMiddlewareWrapper); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Unresolvable::class is not resolvable + */ + public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass() + { + $reflection = new ReflectionClass(DeferredResolutionMiddleware::class); + $deferredResolutionMiddlewareWrapper = new DeferredResolutionMiddleware('Unresolvable::class'); + + $method = $reflection->getMethod('resolve'); + $method->setAccessible(true); + $method->invoke($deferredResolutionMiddlewareWrapper); + } +} diff --git a/tests/Middleware/DispatchMiddlewareTest.php b/tests/Middleware/DispatchMiddlewareTest.php new file mode 100644 index 000000000..d549fc468 --- /dev/null +++ b/tests/Middleware/DispatchMiddlewareTest.php @@ -0,0 +1,42 @@ +getAttribute('routingResults'); + $this->assertInstanceOf(RoutingResults::class, $routingResults); + return $response; + })->bindTo($this); + + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $router = new Router($responseFactory, $callableResolver); + $router->map(['GET'], '/hello/{name}', $handler); + + $request = $this->createServerRequest('https://example.com:443/hello/foo', 'GET'); + $mw = new DispatchMiddleware($router); + + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->run($request); + } +} diff --git a/tests/Middleware/ErrorMiddlewareTest.php b/tests/Middleware/ErrorMiddlewareTest.php index 9761d04b1..26d51268f 100644 --- a/tests/Middleware/ErrorMiddlewareTest.php +++ b/tests/Middleware/ErrorMiddlewareTest.php @@ -8,7 +8,6 @@ */ namespace Slim\Tests\Middleware; -use Closure; use Error; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -36,12 +35,11 @@ public function testSetErrorHandler() $app->add($mw); $exception = HttpNotFoundException::class; - $handler = function () { + $handler = (function () { $response = $this->createResponse(500); $response->getBody()->write('Oops..'); return $response; - }; - Closure::bind($handler, $this); + })->bindTo($this); $mw2 = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw2->setErrorHandler($exception, $handler); @@ -62,12 +60,11 @@ public function testSetDefaultErrorHandler() $mw = new RoutingMiddleware($app->getRouter()); $app->add($mw); - $handler = function () { + $handler = (function () { $response = $this->createResponse(); $response->getBody()->write('Oops..'); return $response; - }; - Closure::bind($handler, $this); + })->bindTo($this); $mw2 = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw2->setDefaultErrorHandler($handler); @@ -111,17 +108,15 @@ public function testErrorHandlerHandlesThrowables() $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); - $mw2 = function () { + $app->add(function ($request, $handler) { throw new Error('Oops..'); - }; - $app->add($mw2); + }); - $handler = function (ServerRequestInterface $request, $exception) { + $handler = (function (ServerRequestInterface $request, $exception) { $response = $this->createResponse(); $response->getBody()->write($exception->getMessage()); return $response; - }; - Closure::bind($handler, $this); + })->bindTo($this); $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw->setDefaultErrorHandler($handler); diff --git a/tests/Middleware/MethodOverrideMiddlewareTest.php b/tests/Middleware/MethodOverrideMiddlewareTest.php index 74d7202dc..28ec63320 100644 --- a/tests/Middleware/MethodOverrideMiddlewareTest.php +++ b/tests/Middleware/MethodOverrideMiddlewareTest.php @@ -8,10 +8,9 @@ */ namespace Slim\Tests\Middleware; -use Closure; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; +use Slim\Middleware\ClosureMiddleware; use Slim\Middleware\MethodOverrideMiddleware; +use Slim\MiddlewareRunner; use Slim\Tests\TestCase; /** @@ -21,72 +20,84 @@ class MethodOverrideMiddlewareTest extends TestCase { public function testHeader() { - $mw = new MethodOverrideMiddleware(); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $callable = (function ($request, $handler) use ($responseFactory) { $this->assertEquals('PUT', $request->getMethod()); - return $response; - }; - Closure::bind($next, $this); + return $responseFactory->createResponse(); + })->bindTo($this); + + $mw = new ClosureMiddleware($callable); + $mw2 = new MethodOverrideMiddleware(); $request = $this ->createServerRequest('/', 'POST') ->withHeader('X-Http-Method-Override', 'PUT'); - $response = $this->createResponse(); - $mw($request, $response, $next); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $middlewareRunner->run($request); } public function testBodyParam() { - $mw = new MethodOverrideMiddleware(); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $callable = (function ($request, $handler) use ($responseFactory) { $this->assertEquals('PUT', $request->getMethod()); - return $response; - }; - Closure::bind($next, $this); + return $responseFactory->createResponse(); + })->bindTo($this); + + $mw = new ClosureMiddleware($callable); + $mw2 = new MethodOverrideMiddleware(); $request = $this ->createServerRequest('/', 'POST') ->withParsedBody(['_METHOD' => 'PUT']); - $response = $this->createResponse(); - $mw($request, $response, $next); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $middlewareRunner->run($request); } public function testHeaderPreferred() { - $mw = new MethodOverrideMiddleware(); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $callable = (function ($request, $handler) use ($responseFactory) { $this->assertEquals('DELETE', $request->getMethod()); - return $response; - }; - Closure::bind($next, $this); + return $responseFactory->createResponse(); + })->bindTo($this); + + $mw = new ClosureMiddleware($callable); + $mw2 = new MethodOverrideMiddleware(); $request = $this ->createServerRequest('/', 'POST') ->withHeader('X-Http-Method-Override', 'DELETE') ->withParsedBody((object) ['_METHOD' => 'PUT']); - $response = $this->createResponse(); - $mw($request, $response, $next); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $middlewareRunner->run($request); } public function testNoOverride() { - $mw = new MethodOverrideMiddleware(); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $callable = (function ($request, $handler) use ($responseFactory) { $this->assertEquals('POST', $request->getMethod()); - return $response; - }; - Closure::bind($next, $this); + return $responseFactory->createResponse(); + })->bindTo($this); + + $mw = new ClosureMiddleware($callable); + $mw2 = new MethodOverrideMiddleware(); $request = $this->createServerRequest('/', 'POST'); - $response = $this->createResponse(); - $mw($request, $response, $next); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $middlewareRunner->run($request); } } diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index a7f6727d5..bce9f89c1 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -8,9 +8,10 @@ */ namespace Slim\Tests\Middleware; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; +use Exception; +use Slim\Middleware\ClosureMiddleware; use Slim\Middleware\OutputBufferingMiddleware; +use Slim\MiddlewareRunner; use Slim\Tests\TestCase; class OutputBufferingMiddlewareTest extends TestCase @@ -32,42 +33,73 @@ public function testStyleCustomValid() */ public function testStyleCustomInvalid() { - $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'foo'); + new OutputBufferingMiddleware($this->getStreamFactory(), 'foo'); } public function testAppend() { - $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'append'); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $mw = new ClosureMiddleware(function ($request, $handler) use ($responseFactory) { + $response = $responseFactory->createResponse(); $response->getBody()->write('Body'); echo 'Test'; return $response; - }; + }); + $mw2 = new OutputBufferingMiddleware($this->getStreamFactory(), 'append'); $request = $this->createServerRequest('/', 'GET'); - $response = $this->createResponse(); - $result = $mw($request, $response, $next); - $this->assertEquals('BodyTest', $result->getBody()); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $response = $middlewareRunner->run($request); + + $this->assertEquals('BodyTest', $response->getBody()); } public function testPrepend() { - $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $mw = new ClosureMiddleware(function ($request, $handler) use ($responseFactory) { + $response = $responseFactory->createResponse(); $response->getBody()->write('Body'); echo 'Test'; return $response; - }; + }); + $mw2 = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); + + $request = $this->createServerRequest('/', 'GET'); + + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $response = $middlewareRunner->run($request); + + $this->assertEquals('TestBody', $response->getBody()); + } + + public function testOutputBufferIsCleanedWhenThrowableIsCaught() + { + $responseFactory = $this->getResponseFactory(); + $mw = new ClosureMiddleware((function ($request, $handler) use ($responseFactory) { + echo "Test"; + $this->assertEquals('Test', ob_get_contents()); + throw new Exception('Oops...'); + })->bindTo($this)); + $mw2 = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); $request = $this->createServerRequest('/', 'GET'); - $response = $this->createResponse(); - $result = $mw($request, $response, $next); - $this->assertEquals('TestBody', $result->getBody()); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + + try { + $middlewareRunner->run($request); + } catch (Exception $e) { + $this->assertEquals('', ob_get_contents()); + } } } diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index 77037c3f5..e18825d53 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -8,12 +8,12 @@ */ namespace Slim\Tests\Middleware; -use Closure; use FastRoute\Dispatcher; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Slim\RoutingResults; +use Slim\CallableResolver; +use Slim\Middleware\ClosureMiddleware; use Slim\Middleware\RoutingMiddleware; +use Slim\MiddlewareRunner; +use Slim\RoutingResults; use Slim\Router; use Slim\Tests\TestCase; @@ -21,17 +21,17 @@ class RoutingMiddlewareTest extends TestCase { protected function getRouter() { - $router = new Router(); + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $router = new Router($responseFactory, $callableResolver); $router->map(['GET'], '/hello/{name}', null); return $router; } public function testRouteIsStoredOnSuccessfulMatch() { - $router = $this->getRouter(); - $mw = new RoutingMiddleware($router); - - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $callable = (function ($request, $handler) use ($responseFactory) { // route is available $route = $request->getAttribute('route'); $this->assertNotNull($route); @@ -40,13 +40,19 @@ public function testRouteIsStoredOnSuccessfulMatch() // routingResults is available $routingResults = $request->getAttribute('routingResults'); $this->assertInstanceOf(RoutingResults::class, $routingResults); - return $response; - }; - Closure::bind($next, $this); + return $responseFactory->createResponse(); + })->bindTo($this); + + $router = $this->getRouter(); + $mw = new ClosureMiddleware($callable); + $mw2 = new RoutingMiddleware($router); - $request = $this->createServerRequest('https://example.com:443/hello/foo'); - $response = $this->createResponse(); - $mw($request, $response, $next); + $request = $this->createServerRequest('https://example.com:443/hello/foo', 'GET'); + + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $middlewareRunner->run($request); } /** @@ -54,10 +60,9 @@ public function testRouteIsStoredOnSuccessfulMatch() */ public function testRouteIsNotStoredOnMethodNotAllowed() { - $router = $this->getRouter(); - $mw = new RoutingMiddleware($router); - $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $responseFactory = $this->getResponseFactory(); + $callable = (function ($request, $handler) use ($responseFactory) { // route is not available $route = $request->getAttribute('route'); $this->assertNull($route); @@ -67,12 +72,18 @@ public function testRouteIsNotStoredOnMethodNotAllowed() $this->assertInstanceOf(RoutingResults::class, $routingResults); $this->assertEquals(Dispatcher::METHOD_NOT_ALLOWED, $routingResults->getRouteStatus()); - return $response; - }; - Closure::bind($next, $this); + return $responseFactory->createResponse(); + })->bindTo($this); + + $router = $this->getRouter(); + $mw = new ClosureMiddleware($callable); + $mw2 = new RoutingMiddleware($router); $request = $this->createServerRequest('https://example.com:443/hello/foo', 'POST'); - $response = $this->createResponse(); - $mw($request, $response, $next); + + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->add($mw); + $middlewareRunner->add($mw2); + $middlewareRunner->run($request); } } diff --git a/tests/MiddlewareAwareTest.php b/tests/MiddlewareAwareTest.php deleted file mode 100644 index 02b681597..000000000 --- a/tests/MiddlewareAwareTest.php +++ /dev/null @@ -1,136 +0,0 @@ -add(function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { - $bottom = $next; - return $response; - }); - - $request = $this->createServerRequest('https://example.com:443/foo/bar?abc=123'); - $response = $this->createResponse(); - - $stack->callMiddlewareStack($request, $response); - - $this->assertSame($stack, $bottom); - } - - public function testCallMiddlewareStack() - { - $stack = new Stackable; - $stack->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In1'); - $response = $next($request, $response); - $response->getBody()->write('Out1'); - return $response; - })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In2'); - $response = $next($request, $response); - $response->getBody()->write('Out2'); - return $response; - }); - - $request = $this->createServerRequest('/'); - $response = $stack->callMiddlewareStack($request, $this->createResponse()); - - $this->assertEquals('In2In1CenterOut1Out2', (string) $response->getBody()); - } - - public function testMiddlewareStackWithAStatic() - { - // Build middleware stack - $stack = new Stackable; - $stack - ->add('Slim\Tests\Mocks\StaticCallable::run') - ->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - $response->getBody()->write('In2'); - $response = $next($request, $response); - $response->getBody()->write('Out2'); - return $response; - }); - - $request = $this->createServerRequest('/'); - $response = $stack->callMiddlewareStack($request, $this->createResponse()); - - $this->assertEquals('In2In1CenterOut1Out2', (string) $response->getBody()); - } - - /** - * @expectedException \RuntimeException - */ - public function testMiddlewareBadReturnValue() - { - $stack = new Stackable; - $stack->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { - // Return Nothing - }); - - $request = $this->createServerRequest('/'); - $response = $stack->callMiddlewareStack($request, $this->createResponse()); - - $stack->callMiddlewareStack($request, $response); - } - - public function testAlternativeSeedMiddlewareStack() - { - $stack = new Stackable; - $stack->alternativeSeed(); - $bottom = null; - - $stack->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { - $bottom = $next; - return $response; - }); - - $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - - $stack->callMiddlewareStack($request, $response); - - $this->assertSame([$stack, 'testMiddlewareKernel'], $bottom); - } - - /** - * @expectedException \RuntimeException - */ - public function testAddMiddlewareWhileStackIsRunningThrowException() - { - $stack = new Stackable; - $stack->add(function () use ($stack) { - $stack->add(function () { - }); - }); - - $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - - $stack->callMiddlewareStack($request, $response); - } - - /** - * @expectedException \RuntimeException - */ - public function testSeedTwiceThrowException() - { - $stack = new Stackable; - $stack->alternativeSeed(); - $stack->alternativeSeed(); - } -} diff --git a/tests/MiddlewareRunnerTest.php b/tests/MiddlewareRunnerTest.php new file mode 100644 index 000000000..3371eb082 --- /dev/null +++ b/tests/MiddlewareRunnerTest.php @@ -0,0 +1,109 @@ +getResponseFactory(); + $callable = function ($request, $handler) use ($responseFactory) { + return $responseFactory->createResponse(); + }; + + $mw = new ClosureMiddleware($callable); + + $middleware = [$mw]; + $middlewareRunner = new MiddlewareRunner($middleware); + + $this->assertEquals($middleware, $middlewareRunner->getMiddleware()); + } + + public function testSetMiddleware() + { + $responseFactory = $this->getResponseFactory(); + $callable = function ($request, $handler) use ($responseFactory) { + return $responseFactory->createResponse(); + }; + + $mw = new ClosureMiddleware($callable); + + $middleware = [$mw]; + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->setMiddleware($middleware); + + $this->assertEquals($middleware, $middlewareRunner->getMiddleware()); + } + + public function testSetStages() + { + $stages = new SplObjectStorage(); + + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->setStages($stages); + + $reflectionProperty = new ReflectionProperty(MiddlewareRunner::class, 'stages'); + $reflectionProperty->setAccessible(true); + + $this->assertEquals($stages, $reflectionProperty->getValue($middlewareRunner)); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Middleware queue should not be empty. + */ + public function testEmptyMiddlewarePipelineThrowsException() + { + $request = $this->createServerRequest('/'); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->run($request); + } + + /** + * @expectedException Error + * @expectedExceptionMessage + * All middleware should implement `MiddlewareInterface`. + * For PSR-7 middleware use the `Psr7MiddlewareAdapter` class. + */ + public function testMiddlewareNotImplementingInterfaceThrowsException() + { + $mw = function (ServerRequestInterface $request, ResponseInterface $response) { + return $response; + }; + + $request = $this->createServerRequest('/'); + $middlewareRunner = new MiddlewareRunner([$mw]); + $middlewareRunner->run($request); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage + * Middleware queue stages have not been set yet. + * Please use the `MiddlewareRunner::run()` method. + */ + public function testMiddlewareWithoutStagesBuiltThrowsException() + { + $request = $this->createServerRequest('/'); + $middlewareRunner = new MiddlewareRunner(); + $middlewareRunner->handle($request); + } +} diff --git a/tests/Mocks/MockMiddleware.php b/tests/Mocks/MockMiddleware.php new file mode 100644 index 000000000..73764c4e2 --- /dev/null +++ b/tests/Mocks/MockMiddleware.php @@ -0,0 +1,47 @@ +responseFactory = $responseFactory; + } + + /** + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $this->responseFactory->createResponse(200); + $response->getBody()->write('Hello World'); + return $response; + } +} diff --git a/tests/Mocks/MockMiddlewareWithoutConstructor.php b/tests/Mocks/MockMiddlewareWithoutConstructor.php new file mode 100644 index 000000000..c23238e09 --- /dev/null +++ b/tests/Mocks/MockMiddlewareWithoutConstructor.php @@ -0,0 +1,35 @@ +getAttribute('appendToOutput'); + if ($appendToOutput !== null) { + $appendToOutput('Hello World'); + } + + return $handler->handle($request); + } +} diff --git a/tests/Mocks/MiddlewareStub.php b/tests/Mocks/MockMiddlewareWithoutInterface.php similarity index 60% rename from tests/Mocks/MiddlewareStub.php rename to tests/Mocks/MockMiddlewareWithoutInterface.php index cb2708390..e40e767e5 100644 --- a/tests/Mocks/MiddlewareStub.php +++ b/tests/Mocks/MockMiddlewareWithoutInterface.php @@ -9,12 +9,9 @@ namespace Slim\Tests\Mocks; /** - * Mock object for Slim\Tests\RouteTest + * Class MockMiddlewareWithoutInterface + * @package Slim\Tests\Mocks */ -class MiddlewareStub +class MockMiddlewareWithoutInterface { - public function run($request, $response, $next) - { - return $response; //$next($request, $response); - } } diff --git a/tests/Mocks/MockRequestHandler.php b/tests/Mocks/MockRequestHandler.php new file mode 100644 index 000000000..04218ce00 --- /dev/null +++ b/tests/Mocks/MockRequestHandler.php @@ -0,0 +1,43 @@ +getResponseFactory(); + + $this->calledCount += 1; + return $responseFactory->createResponse(); + } + + /** + * @return int + */ + public function getCalledCount(): int + { + return $this->calledCount; + } +} diff --git a/tests/Mocks/Stackable.php b/tests/Mocks/Stackable.php deleted file mode 100644 index be542b59a..000000000 --- a/tests/Mocks/Stackable.php +++ /dev/null @@ -1,44 +0,0 @@ -getBody()->write('Center'); - return $response; - } - - public function alternativeSeed() - { - $this->seedMiddlewareStack([$this, 'testMiddlewareKernel']); - } - - public function testMiddlewareKernel(ServerRequestInterface $request, ResponseInterface $response) - { - $response->getBody()->write('hello from testMiddlewareKernel'); - return $response; - } - - public function add($callable) - { - $this->addMiddleware($callable); - return $this; - } -} diff --git a/tests/RouteTest.php b/tests/RouteTest.php index 4ababa981..572e09fdc 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -8,41 +8,56 @@ */ namespace Slim\Tests; +use Closure; +use Exception; use Pimple\Container as Pimple; use Pimple\Psr11\Container as Psr11Container; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use ReflectionClass; use Slim\CallableResolver; use Slim\DeferredCallable; +use Slim\Handlers\Strategies\RequestHandler; +use Slim\Handlers\Strategies\RequestResponse; use Slim\Interfaces\InvocationStrategyInterface; +use Slim\Middleware\ClosureMiddleware; use Slim\Route; +use Slim\RouteGroup; use Slim\Tests\Mocks\CallableTest; use Slim\Tests\Mocks\InvocationStrategyTest; -use Slim\Tests\Mocks\MiddlewareStub; +use Slim\Tests\Mocks\MockMiddlewareWithoutConstructor; +use Slim\Tests\Mocks\MockMiddlewareWithoutInterface; use Slim\Tests\Mocks\RequestHandlerTest; -use Exception; class RouteTest extends TestCase { - public function routeFactory() + /** + * @param string|array $methods + * @param string $pattern + * @param Closure|string|null $callable + * @return Route + */ + public function createRoute($methods = 'GET', string $pattern = '/', $callable = null): Route { - $methods = ['GET', 'POST']; - $pattern = '/hello/{name}'; - $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $callable = $callable ?? function (ServerRequestInterface $request, ResponseInterface $response, $args) { return $response; }; - return new Route($methods, $pattern, $callable); + $responseFactory = $this->getResponseFactory(); + $callableResolver = new CallableResolver(); + + $methods = is_string($methods) ? [$methods] : $methods; + return new Route($methods, $pattern, $callable, $responseFactory, $callableResolver); } public function testConstructor() { $methods = ['GET', 'POST']; $pattern = '/hello/{name}'; - $callable = function ($req, $res, $args) { - // Do something + $callable = function ($request, $response, $args) { + return $response; }; - $route = new Route($methods, $pattern, $callable); + $route = $this->createRoute($methods, $pattern, $callable); $this->assertAttributeEquals($methods, 'methods', $route); $this->assertAttributeEquals($pattern, 'pattern', $route); @@ -51,37 +66,89 @@ public function testConstructor() public function testGetMethodsReturnsArrayWhenContructedWithString() { - $route = new Route('GET', '/hello', function ($req, $res, $args) { - // Do something - }); + $route = $this->createRoute(); $this->assertEquals(['GET'], $route->getMethods()); } public function testGetMethods() { - $this->assertEquals(['GET', 'POST'], $this->routeFactory()->getMethods()); + $methods = ['GET', 'POST']; + $route = $this->createRoute($methods); + + $this->assertEquals($methods, $route->getMethods()); } public function testGetPattern() { - $this->assertEquals('/hello/{name}', $this->routeFactory()->getPattern()); + $route = $this->createRoute(); + + $this->assertEquals('/', $route->getPattern()); } public function testGetCallable() { - $callable = $this->routeFactory()->getCallable(); + $route = $this->createRoute(); + + $this->assertTrue(is_callable($route->getCallable())); + } + + public function testGetCallableResolver() + { + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + return $response; + }; + + $responseFactory = $this->getResponseFactory(); + $callableResolver = new CallableResolver(); + + $route = new Route(['GET'], '/', $callable, $responseFactory, $callableResolver); + + $this->assertEquals($callableResolver, $route->getCallableResolver()); + } + + public function testGetInvocationStrategy() + { + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + return $response; + }; + + $responseFactory = $this->getResponseFactory(); + $callableResolver = new CallableResolver(); + $invocationStrategy = new RequestResponse(); + + $route = new Route(['GET'], '/', $callable, $responseFactory, $callableResolver, $invocationStrategy); + + $this->assertEquals($invocationStrategy, $route->getInvocationStrategy()); + } + + public function testGetGroups() + { + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + return $response; + }; - $this->assertTrue(is_callable($callable)); + $responseFactory = $this->getResponseFactory(); + $callableResolver = new CallableResolver(); + $invocationStrategy = new RequestResponse(); + + $routeGroup = new RouteGroup('/group', $callable, $responseFactory, $callableResolver); + $groups = [$routeGroup]; + + $route = new Route(['GET'], '/', $callable, $responseFactory, $callableResolver, $invocationStrategy, $groups); + + $this->assertEquals($groups, $route->getGroups()); } public function testArgumentSetting() { - $route = $this->routeFactory(); + $route = $this->createRoute(); $route->setArguments(['foo' => 'FOO', 'bar' => 'BAR']); $this->assertSame($route->getArguments(), ['foo' => 'FOO', 'bar' => 'BAR']); + $route->setArgument('bar', 'bar'); $this->assertSame($route->getArguments(), ['foo' => 'FOO', 'bar' => 'bar']); + $route->setArgument('baz', 'BAZ'); $this->assertSame($route->getArguments(), ['foo' => 'FOO', 'bar' => 'bar', 'baz' => 'BAZ']); @@ -91,128 +158,161 @@ public function testArgumentSetting() $this->assertSame($route->getArgument('b', 'default'), 'default'); } - public function testBottomMiddlewareIsRoute() { - $route = $this->routeFactory(); - $bottom = null; - $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { - $bottom = $next; - return $response; - }; - $route->add($mw); + $route = $this->createRoute(); + + $reflection = new ReflectionClass(Route::class); + $property = $reflection->getProperty('middlewareRunner'); + $property->setAccessible(true); + $middlewareRunner = $property->getValue($route); + + $responseFactory = $this->getResponseFactory(); + $route->add(function ($request, $handler) use (&$bottom, $responseFactory) { + return $responseFactory->createResponse(); + }); $route->finalize(); - $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $route->callMiddlewareStack($request, $response); + /** @var array $middleware */ + $middleware = $middlewareRunner->getMiddleware(); + $bottom = $middleware[1]; - $this->assertEquals($route, $bottom); + $this->assertInstanceOf(Route::class, $bottom); } public function testAddMiddleware() { - $route = $this->routeFactory(); + $route = $this->createRoute(); $called = 0; - $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$called) { + $mw = new ClosureMiddleware(function ($request, $handler) use (&$called) { $called++; + return $handler->handle($request); + }); + $route->addMiddleware($mw); + + $request = $this->createServerRequest('/'); + $route->run($request); + + $this->assertSame($called, 1); + } + + public function testAddMiddlewareOnGroup() + { + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { return $response; }; - $route->add($mw); - $route->finalize(); + $responseFactory = $this->getResponseFactory(); + $callableResolver = new CallableResolver(); + $invocationStrategy = new RequestResponse(); + + $called = 0; + $mw = new ClosureMiddleware(function ($request, $handler) use (&$called) { + $called++; + return $handler->handle($request); + }); + $routeGroup = new RouteGroup('/group', $callable, $responseFactory, $callableResolver); + $routeGroup->addMiddleware($mw); + $groups = [$routeGroup]; + + $route = new Route(['GET'], '/', $callable, $responseFactory, $callableResolver, $invocationStrategy, $groups); $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $route->callMiddlewareStack($request, $response); + $route->run($request); $this->assertSame($called, 1); } - public function testRefinalizing() + public function testAddClosureMiddleware() { - $route = $this->routeFactory(); + $route = $this->createRoute(); $called = 0; - $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$called) { + $route->add(function ($request, $handler) use (&$called) { $called++; - return $response; - }; - - $route->add($mw); - - $route->finalize(); - $route->finalize(); + return $handler->handle($request); + }); $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $route->callMiddlewareStack($request, $response); + $route->run($request); $this->assertSame($called, 1); } - - public function testIdentifier() + public function testAddMiddlewareUsingDeferredResolution() { - $route = $this->routeFactory(); - $this->assertEquals('route0', $route->getIdentifier()); + $route = $this->createRoute(); + $route->add(MockMiddlewareWithoutConstructor::class); + + $output = ''; + $appendToOutput = function (string $value) use (&$output) { + $output .= $value; + }; + $request = $this->createServerRequest('/')->withAttribute('appendToOutput', $appendToOutput); + $route->run($request); + + $this->assertSame('Hello World', $output); } - public function testSetName() + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage + * Parameter 1 of `Slim\Route::add()` must be a closure or an object/class name + * referencing an implementation of MiddlewareInterface. + */ + public function testAddMiddlewareAsStringNotImplementingInterfaceThrowsException() { - $route = $this->routeFactory(); - $this->assertEquals($route, $route->setName('foo')); - $this->assertEquals('foo', $route->getName()); + $route = $this->createRoute(); + $route->add(new MockMiddlewareWithoutInterface()); } - public function testAddMiddlewareAsStringResolvesWithoutContainer() + public function testRefinalizing() { - $route = $this->routeFactory(); + $route = $this->createRoute(); + $called = 0; - $resolver = new CallableResolver(); - $route->setCallableResolver($resolver); - $route->add('MiddlewareStub:run'); + $route->add(function ($request, $handler) use (&$called) { + $called++; + return $handler->handle($request); + }); + + $route->finalize(); + $route->finalize(); $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertSame($called, 1); } - public function testAddMiddlewareAsStringResolvesWithContainer() + public function testIdentifier() { - $route = $this->routeFactory(); - - $pimple = new Pimple(); - $pimple['MiddlewareStub'] = new MiddlewareStub(); - $resolver = new CallableResolver(new Psr11Container($pimple)); - $route->setCallableResolver($resolver); - $route->add('MiddlewareStub:run'); - - $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $route = $this->createRoute(); + $this->assertEquals('route0', $route->getIdentifier()); + } - $this->assertInstanceOf(ResponseInterface::class, $result); + public function testSetName() + { + $route = $this->createRoute(); + $this->assertEquals($route, $route->setName('foo')); + $this->assertEquals('foo', $route->getName()); } public function testControllerMethodAsStringResolvesWithoutContainer() { - $resolver = new CallableResolver(); - $deferred = new DeferredCallable('\Slim\Tests\Mocks\CallableTest:toCall', $resolver); + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); - $route = new Route(['GET'], '/', $deferred); - $route->setCallableResolver($resolver); + $deferred = new DeferredCallable('\Slim\Tests\Mocks\CallableTest:toCall', $callableResolver); + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver); CallableTest::$CalledCount = 0; $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $response = $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertEquals(1, CallableTest::$CalledCount); } @@ -220,20 +320,19 @@ public function testControllerMethodAsStringResolvesWithContainer() { $pimple = new Pimple(); $pimple['CallableTest'] = new CallableTest(); - $resolver = new CallableResolver(new Psr11Container($pimple)); - - $deferred = new DeferredCallable('CallableTest:toCall', $resolver); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $responseFactory = $this->getResponseFactory(); - $route = new Route(['GET'], '/', $deferred); - $route->setCallableResolver($resolver); + $deferred = new DeferredCallable('CallableTest:toCall', $callableResolver); + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver); CallableTest::$CalledCount = 0; $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $response = $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertEquals(1, CallableTest::$CalledCount); } @@ -241,18 +340,18 @@ public function testControllerMethodAsStringResolvesWithContainer() * Ensure that the response returned by a route callable is the response * object that is returned by __invoke(). */ - public function testInvokeWhenReturningAResponse() + public function testProcessWhenReturningAResponse() { $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { $response->getBody()->write('foo'); return $response; }; - $route = new Route(['GET'], '/', $callable); + $route = $this->createRoute(['GET'], '/', $callable); CallableTest::$CalledCount = 0; $request = $this->createServerRequest('/'); - $response = $route->__invoke($request, $this->createResponse()); + $response = $route->run($request); $this->assertEquals('foo', (string) $response->getBody()); } @@ -261,19 +360,19 @@ public function testInvokeWhenReturningAResponse() * Ensure that anything echo'd in a route callable is, by default, NOT * added to the response object body. */ - public function testInvokeWhenEchoingOutput() + public function testRouteCallableDoesNotAppendEchoedOutput() { $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { echo "foo"; return $response->withStatus(201); }; - $route = new Route(['GET'], '/', $callable); + $route = $this->createRoute(['GET'], '/', $callable); $request = $this->createServerRequest('/'); // We capture output buffer here only to clean test CLI output ob_start(); - $response = $route->__invoke($request, $this->createResponse()); + $response = $route->run($request); ob_end_clean(); // Output buffer is ignored without optional middleware @@ -285,16 +384,16 @@ public function testInvokeWhenEchoingOutput() * Ensure that if a string is returned by a route callable, then it is * added to the response object that is returned by __invoke(). */ - public function testInvokeWhenReturningAString() + public function testRouteCallableAppendsCorrectOutputToResponse() { $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { $response->getBody()->write('foo'); return $response; }; - $route = new Route(['GET'], '/', $callable); + $route = $this->createRoute(['GET'], '/', $callable); $request = $this->createServerRequest('/'); - $response = $route->__invoke($request, $this->createResponse()); + $response = $route->run($request); $this->assertEquals('foo', (string) $response->getBody()); } @@ -307,12 +406,10 @@ public function testInvokeWithException() $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { throw new Exception(); }; - $route = new Route(['GET'], '/', $callable); + $route = $this->createRoute(['GET'], '/', $callable); $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - - $route->__invoke($request, $response); + $route->run($request); } /** @@ -320,17 +417,17 @@ public function testInvokeWithException() */ public function testInvokeDeferredCallableWithNoContainer() { - $resolver = new CallableResolver(); + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $invocationStrategy = new InvocationStrategyTest(); - $route = new Route(['GET'], '/', '\Slim\Tests\Mocks\CallableTest:toCall'); - $route->setCallableResolver($resolver); - $route->setInvocationStrategy(new InvocationStrategyTest()); + $deferred = '\Slim\Tests\Mocks\CallableTest:toCall'; + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver, $invocationStrategy); $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $response = $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertEquals([new CallableTest(), 'toCall'], InvocationStrategyTest::$LastCalledFor); } @@ -341,17 +438,18 @@ public function testInvokeDeferredCallableWithContainer() { $pimple = new Pimple(); $pimple['CallableTest'] = new CallableTest; - $resolver = new CallableResolver(new Psr11Container($pimple)); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $responseFactory = $this->getResponseFactory(); + $invocationStrategy = new InvocationStrategyTest(); - $route = new Route(['GET'], '/', '\Slim\Tests\Mocks\CallableTest:toCall'); - $route->setCallableResolver($resolver); - $route->setInvocationStrategy(new InvocationStrategyTest()); + $deferred = '\Slim\Tests\Mocks\CallableTest:toCall'; + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver, $invocationStrategy); $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $response = $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertEquals([new CallableTest(), 'toCall'], InvocationStrategyTest::$LastCalledFor); } @@ -359,18 +457,19 @@ public function testInvokeUsesRequestHandlerStrategyForRequestHandlers() { $pimple = new Pimple(); $pimple[RequestHandlerTest::class] = new RequestHandlerTest(); - $resolver = new CallableResolver(new Psr11Container($pimple)); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $responseFactory = $this->getResponseFactory(); - $route = new Route(['GET'], '/', RequestHandlerTest::class); - $route->setCallableResolver($resolver); + $deferred = RequestHandlerTest::class; + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver); $request = $this->createServerRequest('/', 'GET'); - $response = $this->createResponse(); - $route->callMiddlewareStack($request, $response); + $route->run($request); /** @var InvocationStrategyInterface $strategy */ $strategy = $pimple[RequestHandlerTest::class]::$strategy; - $this->assertEquals('Slim\Handlers\Strategies\RequestHandler', $strategy); + $this->assertEquals(RequestHandler::class, $strategy); } /** @@ -378,8 +477,9 @@ public function testInvokeUsesRequestHandlerStrategyForRequestHandlers() */ public function testPatternCanBeChanged() { - $route = $this->routeFactory(); + $route = $this->createRoute(); $route->setPattern('/hola/{nombre}'); + $this->assertEquals('/hola/{nombre}', $route->getPattern()); } @@ -388,19 +488,17 @@ public function testPatternCanBeChanged() */ public function testChangingCallableWithNoContainer() { - $resolver = new CallableResolver(); - - $route = new Route(['GET'], '/', 'NonExistent:toCall'); //Note that this doesn't actually exist - $route->setCallableResolver($resolver); - $route->setInvocationStrategy(new InvocationStrategyTest()); + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $deferred = 'NonExistent:toCall'; + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver); $route->setCallable('\Slim\Tests\Mocks\CallableTest:toCall'); //Then we fix it here. $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $response = $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertEquals([new CallableTest(), 'toCall'], InvocationStrategyTest::$LastCalledFor); } @@ -411,19 +509,44 @@ public function testChangingCallableWithContainer() { $pimple = new Pimple(); $pimple['CallableTest2'] = new CallableTest; - $resolver = new CallableResolver(new Psr11Container($pimple)); - - $route = new Route(['GET'], '/', 'NonExistent:toCall'); //Note that this doesn't actually exist - $route->setCallableResolver($resolver); - $route->setInvocationStrategy(new InvocationStrategyTest()); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $responseFactory = $this->getResponseFactory(); + $invocationStrategy = new InvocationStrategyTest(); + $deferred = 'NonExistent:toCall'; + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver, $invocationStrategy); $route->setCallable('CallableTest2:toCall'); //Then we fix it here. $request = $this->createServerRequest('/'); - $response = $this->createResponse(); - $result = $route->callMiddlewareStack($request, $response); + $response = $route->run($request); - $this->assertInstanceOf(ResponseInterface::class, $result); + $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertEquals([$pimple['CallableTest2'], 'toCall'], InvocationStrategyTest::$LastCalledFor); } + + public function testRouteCallableIsResolvedUsingContainerWhenCallableResolverIsPresent() + { + $responseFactory = $this->getResponseFactory(); + + $pimple = new Pimple(); + $pimple['CallableTest3'] = new CallableTest; + $pimple['ClosureMiddleware'] = new ClosureMiddleware(function ($request, $handler) use ($responseFactory) { + $response = $responseFactory->createResponse(); + $response->getBody()->write('Hello'); + return $response; + }); + $container = new Psr11Container($pimple); + $callableResolver = new CallableResolver($container); + $invocationStrategy = new InvocationStrategyTest(); + + $deferred = 'CallableTest3'; + $route = new Route(['GET'], '/', $deferred, $responseFactory, $callableResolver, $invocationStrategy); + $route->add('ClosureMiddleware'); + + $request = $this->createServerRequest('/'); + $response = $route->run($request); + + $this->assertEquals('Hello', (string) $response->getBody()); + } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 86706299d..8f52f8ab0 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -8,10 +8,15 @@ */ namespace Slim\Tests; +use FastRoute\RouteCollector; +use ReflectionClass; +use Slim\CallableResolver; use Slim\Dispatcher; +use Slim\Interfaces\RouteInterface; use Slim\Route; -use Slim\RoutingResults; use Slim\Router; +use Slim\RoutingResults; +use Slim\Tests\Mocks\InvocationStrategyTest; class RouterTest extends TestCase { @@ -20,7 +25,9 @@ class RouterTest extends TestCase public function setUp() { - $this->router = new Router; + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $this->router = new Router($responseFactory, $callableResolver); } public function testMap() @@ -32,7 +39,7 @@ public function testMap() }; $route = $this->router->map($methods, $pattern, $callable); - $this->assertInstanceOf('\Slim\Interfaces\RouteInterface', $route); + $this->assertInstanceOf(RouteInterface::class, $route); $this->assertAttributeContains($route, 'routes', $this->router); } @@ -182,7 +189,7 @@ public function testPathForRouteNotExists() public function testCreateDispatcher() { - $class = new \ReflectionClass($this->router); + $class = new ReflectionClass($this->router); $method = $class->getMethod('createDispatcher'); $method->setAccessible(true); $this->assertInstanceOf(Dispatcher::class, $method->invoke($this->router)); @@ -190,14 +197,35 @@ public function testCreateDispatcher() public function testSetDispatcher() { - $this->router->setDispatcher(\FastRoute\simpleDispatcher(function ($r) { - $r->addRoute('GET', '/', function () { - }); - }, ['dispatcher' => Dispatcher::class])); - $class = new \ReflectionClass($this->router); + /** @var Dispatcher $dispatcher */ + $dispatcher = \FastRoute\simpleDispatcher(function (RouteCollector $r) { + }, ['dispatcher' => Dispatcher::class]); + $this->router->setDispatcher($dispatcher); + + $class = new ReflectionClass($this->router); $prop = $class->getProperty('dispatcher'); $prop->setAccessible(true); - $this->assertInstanceOf(Dispatcher::class, $prop->getValue($this->router)); + + $this->assertEquals($dispatcher, $prop->getValue($this->router)); + } + + public function testGetRouteInvocationStrategy() + { + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $invocationStrategy = new InvocationStrategyTest(); + $router = new Router($responseFactory, $callableResolver, $invocationStrategy); + + $this->assertEquals($invocationStrategy, $router->getDefaultInvocationStrategy()); + } + + public function testGetCallableResolver() + { + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $router = new Router($responseFactory, $callableResolver); + + $this->assertEquals($callableResolver, $router->getCallableResolver()); } /** @@ -286,31 +314,6 @@ public function testPathForWithModifiedRoutePattern() ); } - /** - * Test cacheFile may be set to false - */ - public function testSettingCacheFileToFalse() - { - $this->router->setCacheFile(false); - - $class = new \ReflectionClass($this->router); - $property = $class->getProperty('cacheFile'); - $property->setAccessible(true); - - $this->assertFalse($property->getValue($this->router)); - } - - /** - * Test cacheFile should be a string or false - * - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Router cacheFile must be a string - */ - public function testSettingInvalidCacheFileValue() - { - $this->router->setCacheFile(['invalid']); - } - /** * Test if cacheFile is not a writable directory * @@ -338,7 +341,7 @@ public function testRouteCacheFileCanBeDispatched() $cacheFile = dirname(__FILE__) . '/' . uniqid(microtime(true)); $this->router->setCacheFile($cacheFile); - $class = new \ReflectionClass($this->router); + $class = new ReflectionClass($this->router); $method = $class->getMethod('createDispatcher'); $method->setAccessible(true); @@ -348,18 +351,18 @@ public function testRouteCacheFileCanBeDispatched() // instantiate a new router & load the cached routes file & see if // we can dispatch to the route we cached. - $router2 = new Router(); + $callableResolver = new CallableResolver(); + $responseFactory = $this->getResponseFactory(); + $router2 = new Router($responseFactory, $callableResolver); $router2->setCacheFile($cacheFile); - $class = new \ReflectionClass($router2); + $class = new ReflectionClass($router2); $method = $class->getMethod('createDispatcher'); $method->setAccessible(true); $dispatcher2 = $method->invoke($this->router); - /** - * @var RoutingResults $result - */ + /** @var RoutingResults $result */ $result = $dispatcher2->dispatch('GET', '/hello/josh/lockhart'); $this->assertSame(Dispatcher::FOUND, $result->getRouteStatus()); @@ -372,7 +375,7 @@ public function testRouteCacheFileCanBeDispatched() */ public function testCreateDispatcherReturnsSameDispatcherASecondTime() { - $class = new \ReflectionClass($this->router); + $class = new ReflectionClass($this->router); $method = $class->getMethod('createDispatcher'); $method->setAccessible(true); @@ -399,7 +402,12 @@ public function testUrlForAliasesPathFor() $data = ['name' => 'josh']; $queryParams = ['a' => 'b', 'c' => 'd']; - $router = $this->getMockBuilder(Router::class)->setMethods(['pathFor'])->getMock(); + /** @var Router $router */ + $router = $this + ->getMockBuilder(Router::class) + ->setConstructorArgs([$this->getResponseFactory(), new CallableResolver()]) + ->setMethods(['pathFor']) + ->getMock(); $router->expects($this->once())->method('pathFor')->with($name, $data, $queryParams); $router->urlFor($name, $data, $queryParams);