diff --git a/.travis.yml b/.travis.yml index 0ba04d33e..d2488d5b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,24 +6,26 @@ dist: trusty matrix: include: - - php: 7.0 - - php: 7.1 - env: ANALYSIS='true' - - php: 7.2 - - php: nightly + - php: 7.1 + env: ANALYSIS='true' + - php: 7.2 + - php: 7.3 + - php: nightly allow_failures: - - php: nightly + - php: nightly before_script: - - composer update +- composer require php-coveralls/php-coveralls:^2.1.0 +- composer install -n script: - - if [[ "$ANALYSIS" != 'true' ]]; then vendor/bin/phpunit ; fi - - if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpunit --coverage-clover clover.xml ; fi - - if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpcs ; fi +- if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpunit --coverage-clover clover.xml ; fi +- vendor/bin/phpunit +- vendor/bin/phpcs +- vendor/bin/phpstan analyse Slim -after_script: - - if [[ "$ANALYSIS" == 'true' ]]; then php vendor/bin/coveralls --coverage_clover=clover.xml -v ; fi +after_success: +- if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/php-coveralls --coverage_clover=clover.xml -v ; fi notifications: slack: slimphp:0RNzx2JuhkAqIf0MXcUZ0asT diff --git a/CHANGELOG.md b/CHANGELOG.md index d7de6998b..1dab9c166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added +- [#2529](https://github.com/slimphp/Slim/pull/2529) Slim no longer ships with a PSR-7 implementation. You need to provide a PSR-7 ServerRequest and a PSR-17 ResponseFactory to run Slim. - [#2497](https://github.com/slimphp/Slim/pull/2497) PSR-15 RequestHandlers can now be used as route callables - [#2496](https://github.com/slimphp/Slim/pull/2496) A Slim App can now be used as PSR-15 Request Handler - [#2405](https://github.com/slimphp/Slim/pull/2405) RoutingMiddleware now adds the `routingResults` request attribute to hold the results of routing diff --git a/README.md b/README.md index b0ce1dbc2..abec36a3a 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,162 @@ It's recommended that you use [Composer](https://getcomposer.org/) to install Sl $ composer require slim/slim "^4.0" ``` -This will install Slim and all required dependencies. Slim requires PHP 7.0.0 or newer. +This will install Slim and all required dependencies. Slim requires PHP 7.1 or newer. -## Usage +## Choose a PSR-7 Implementation -Create an index.php file with the following contents: +Before you can get up and running with Slim you will need to choose a PSR-7 implementation that best fits your application. A few notable ones: +- [Nyholm/psr7](https://github.com/Nyholm/psr7) - This is the fastest, strictest and most lightweight implementation at the moment +- [Guzzle/psr7](https://github.com/guzzle/psr7) - This is the implementation used by the Guzzle Client. It is not as strict but adds some nice functionality for Streams and file handling. It is the second fastest implementation but is a bit bulkier +- [zend-diactoros](https://github.com/zendframework/zend-diactoros) - This is the Zend implementation. It is the slowest implementation of the 3. +## Example Usage With Nyholm/psr7 and Nyholm/psr7-server ```php get('/hello/{name}', function ($request, $response, $args) { + return $response->getBody()->write("Hello, " . $args['name']); +}); + +/** + * The App::run() method takes 1 parameter + * @param ServerRequestInterface An instantiation of a ServerRequest + */ +$request = $serverRequestFactory->fromGlobals(); +$app->run($request); +``` + +## Example Usage With Zend Diactoros & Zend HttpHandleRunner Response Emitter +```php +get('/hello/{name}', function ($request, $response, $args) { + return $response->getBody()->write("Hello, " . $args['name']); +}); +/** + * The App::handle() method takes 1 parameter + * Note we are using handle() and not run() since we want to emit the response using Zend's Response Emitter + * @param ServerRequestInterface An instantiation of a ServerRequest + */ +$request = ServerRequestFactory::fromGlobals(); +$response = $app->handle($request); + +/** + * Once you have obtained the ResponseInterface from App::handle() + * You will need to emit the response by using an emitter of your choice + * We will use Zend HttpHandleRunner SapiEmitter for this example + */ +$responseEmitter = new SapiEmitter(); +$responseEmitter->emit($response); +``` + +## Example Usage With Slim-Http Decorators and Zend Diactoros +```php +get('/hello/{name}', function ($request, $response, $args) { + return $response->withJson(['Hello' => 'World']); +}); + +/** + * The App::run() method takes 1 parameter + * Note that we pass in the decorated server request object which will give us access to the Slim\Http + * decorated ServerRequest methods like withRedirect() + * @param ServerRequestInterface An instantiation of a ServerRequest + */ +$request = ServerRequestFactory::fromGlobals(); +$decoratedServerRequest = new ServerRequestDecorator($request); +$app->run($decoratedServerRequest); +``` +## Example Usage With Guzzle PSR-7 and Guzzle HTTP Factory +```php +get('/hello/{name}', function ($request, $response, $args) { - return $response->write("Hello, " . $args['name']); + return $response->getBody()->write("Hello, " . $args['name']); }); -$app->run(); +/** + * The App::run() method takes 1 parameter + * @param ServerRequestInterface An instantiation of a ServerRequest + */ +$request = ServerRequest::fromGlobals(); +$app->run($request); ``` You may quickly test this using the built-in PHP server: @@ -45,11 +183,12 @@ Going to http://localhost:8000/hello/world will now display "Hello, world". For more information on how to configure your web server, see the [Documentation](https://www.slimframework.com/docs/start/web-servers.html). ## Tests - -To execute the test suite, you'll need phpunit. +To execute the test suite, you'll need to install all development dependencies. ```bash -$ phpunit +$ git clone https://github.com/slimphp/Slim-Http +$ composer install +$ composer test ``` ## Contributing @@ -75,6 +214,7 @@ If you discover security related issues, please email security@slimframework.com - [Josh Lockhart](https://github.com/codeguy) - [Andrew Smith](https://github.com/silentworks) - [Rob Allen](https://github.com/akrabat) +- [Pierre Bérubé](https://github.com/l0gicgate) - [Gabriel Manricks](https://github.com/gmanricks) - [All Contributors](../../contributors) diff --git a/Slim/App.php b/Slim/App.php index e3806e2ec..8fcea09b6 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -12,16 +12,13 @@ namespace Slim; use Psr\Container\ContainerInterface; -use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; +use Psr\Http\Server\RequestHandlerInterface; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; -use Psr\Http\Server\RequestHandlerInterface; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\RouteGroupInterface; use Slim\Interfaces\RouteInterface; @@ -49,7 +46,7 @@ class App implements RequestHandlerInterface /** * Container * - * @var ContainerInterface + * @var ContainerInterface|null */ private $container; @@ -63,12 +60,16 @@ class App implements RequestHandlerInterface */ protected $router; + /** + * @var ResponseFactoryInterface + */ + protected $responseFactory; + /** * @var array */ protected $settings = [ 'httpVersion' => '1.1', - 'responseChunkSize' => 4096, 'routerCacheFile' => false, ]; @@ -79,13 +80,18 @@ class App implements RequestHandlerInterface /** * Create new application * - * @param array $settings + * @param ResponseFactoryInterface $responseFactory * @param ContainerInterface|null $container + * @param array $settings */ - public function __construct(array $settings = [], ContainerInterface $container = null) - { - $this->addSettings($settings); + public function __construct( + ResponseFactoryInterface $responseFactory, + ContainerInterface $container = null, + array $settings = [] + ) { + $this->responseFactory = $responseFactory; $this->container = $container; + $this->addSettings($settings); } /** @@ -353,8 +359,9 @@ public function map(array $methods, string $pattern, $callable): RouteInterface $callable = $callable->bindTo($this->container); } - // Create route - $route = $this->getRouter()->map($methods, $pattern, $callable); + /** @var Router $router */ + $router = $this->getRouter(); + $route = $router->map($methods, $pattern, $callable); return $route; } @@ -391,12 +398,14 @@ public function redirect(string $from, $to, int $status = 302): RouteInterface */ public function group(string $pattern, $callable): RouteGroupInterface { - /** @var RouteGroup $group */ $router = $this->getRouter(); + + /** @var RouteGroup $group */ $group = $router->pushGroup($pattern, $callable); if ($this->callableResolver instanceof CallableResolverInterface) { $group->setCallableResolver($this->callableResolver); } + $group($this); $router->popGroup(); @@ -413,17 +422,13 @@ public function group(string $pattern, $callable): RouteGroupInterface * This method traverses the application middleware stack and then sends the * resultant Response object to the HTTP client. * - * @param RequestInterface|null $request - * @return ResponseInterface + * @param ServerRequestInterface $request */ - public function run(RequestInterface $request = null): ResponseInterface + public function run(ServerRequestInterface $request): void { - // create request - if ($request === null) { - $request = Request::createFromGlobals($_SERVER); - } - - return $this->handle($request); + $response = $this->handle($request); + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); } /** @@ -433,91 +438,17 @@ public function run(RequestInterface $request = null): ResponseInterface * resultant Response object. * * @param ServerRequestInterface $request - * @param ResponseInterface $response * @return ResponseInterface */ public function handle(ServerRequestInterface $request): ResponseInterface { - // create response - $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']); $httpVersion = $this->getSetting('httpVersion'); - $response = new Response(200, $headers); - $response = $response->withProtocolVersion($httpVersion); + $response = $this->responseFactory + ->createResponse(200, '') + ->withProtocolVersion($httpVersion) + ->withHeader('Content-Type', 'text/html; charset=UTF-8'); - // call middleware stack - $response = $this->callMiddlewareStack($request, $response); - $response = $this->finalize($response); - - return $this->respond($response); - } - - /** - * Send the response the client - * - * @param ResponseInterface $response - * @return ResponseInterface - */ - public function respond(ResponseInterface $response): ResponseInterface - { - // Send response - if (!headers_sent()) { - // Headers - foreach ($response->getHeaders() as $name => $values) { - $first = stripos($name, 'Set-Cookie') === 0 ? false : true; - foreach ($values as $value) { - header(sprintf('%s: %s', $name, $value), $first); - $first = false; - } - } - - // Set the status _after_ the headers, because of PHP's "helpful" behavior with location headers. - // See https://github.com/slimphp/Slim/issues/1730 - - // Status - header(sprintf( - 'HTTP/%s %s %s', - $response->getProtocolVersion(), - $response->getStatusCode(), - $response->getReasonPhrase() - ), true, $response->getStatusCode()); - } - - // Body - if (!$this->isEmptyResponse($response)) { - $body = $response->getBody(); - if ($body->isSeekable()) { - $body->rewind(); - } - $chunkSize = $this->getSetting('responseChunkSize', 4096); - $contentLength = $response->getHeaderLine('Content-Length'); - if (!$contentLength) { - $contentLength = $body->getSize(); - } - - - if (isset($contentLength)) { - $amountToRead = $contentLength; - while ($amountToRead > 0 && !$body->eof()) { - $data = $body->read(min($chunkSize, $amountToRead)); - echo $data; - - $amountToRead -= strlen($data); - - if (connection_status() != CONNECTION_NORMAL) { - break; - } - } - } else { - while (!$body->eof()) { - echo $body->read($chunkSize); - if (connection_status() != CONNECTION_NORMAL) { - break; - } - } - } - } - - return $response; + return $this->callMiddlewareStack($request, $response); } /** @@ -538,9 +469,7 @@ public function respond(ResponseInterface $response): ResponseInterface */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { - /** - * @var RoutingResults $routingResults - */ + /** @var RoutingResults|null $routingResults */ $routingResults = $request->getAttribute('routingResults'); // If routing hasn't been done, then do it now so we can dispatch @@ -553,39 +482,4 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res $route = $request->getAttribute('route'); return $route->run($request, $response); } - - /** - * Finalize response - * - * @param ResponseInterface $response - * @return ResponseInterface - * - * @throws \RuntimeException - */ - protected function finalize(ResponseInterface $response): ResponseInterface - { - if ($this->isEmptyResponse($response)) { - return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length'); - } - - return $response; - } - - /** - * Helper method, which returns true if the provided response must not output a body and false - * if the response could have a body. - * - * @see https://tools.ietf.org/html/rfc7231 - * - * @param ResponseInterface $response - * @return bool - */ - protected function isEmptyResponse(ResponseInterface $response): bool - { - if (method_exists($response, 'isEmpty')) { - return $response->isEmpty(); - } - - return in_array($response->getStatusCode(), [204, 205, 304]); - } } diff --git a/Slim/CallableResolver.php b/Slim/CallableResolver.php index 7c5f04a36..f25d99426 100644 --- a/Slim/CallableResolver.php +++ b/Slim/CallableResolver.php @@ -23,7 +23,7 @@ final class CallableResolver implements CallableResolverInterface { /** - * @var ContainerInterface + * @var ContainerInterface|null */ private $container; diff --git a/Slim/DeferredCallable.php b/Slim/DeferredCallable.php index 560c60d2f..3e5ced23d 100644 --- a/Slim/DeferredCallable.php +++ b/Slim/DeferredCallable.php @@ -39,6 +39,7 @@ public function __construct($callable, CallableResolverInterface $resolver = nul public function __invoke() { + /** @var callable $callable */ $callable = $this->callable; if ($this->callableResolver) { $callable = $this->callableResolver->resolve($callable); diff --git a/Slim/Error/AbstractErrorRenderer.php b/Slim/Error/AbstractErrorRenderer.php index e0d3b9f5b..6a8aff907 100644 --- a/Slim/Error/AbstractErrorRenderer.php +++ b/Slim/Error/AbstractErrorRenderer.php @@ -11,9 +11,7 @@ namespace Slim\Error; -use Slim\Http\Body; use Slim\Interfaces\ErrorRendererInterface; -use Throwable; /** * Abstract Slim application error renderer @@ -23,16 +21,4 @@ */ abstract class AbstractErrorRenderer implements ErrorRendererInterface { - /** - * @param Throwable $exception - * @param bool $displayErrorDetails - * @return Body - */ - public function renderWithBody(Throwable $exception, bool $displayErrorDetails): Body - { - $output = $this->render($exception, $displayErrorDetails); - $body = new Body(fopen('php://temp', 'r+')); - $body->write($output); - return $body; - } } diff --git a/Slim/Error/Renderers/JsonErrorRenderer.php b/Slim/Error/Renderers/JsonErrorRenderer.php index e27d6f159..3ce2e21e3 100644 --- a/Slim/Error/Renderers/JsonErrorRenderer.php +++ b/Slim/Error/Renderers/JsonErrorRenderer.php @@ -35,7 +35,7 @@ public function render(Throwable $exception, bool $displayErrorDetails): string } while ($exception = $exception->getPrevious()); } - return json_encode($error, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + return (string) json_encode($error, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); } /** diff --git a/Slim/Handlers/ErrorHandler.php b/Slim/Handlers/ErrorHandler.php index 4ef5f1e96..be8ed9e73 100644 --- a/Slim/Handlers/ErrorHandler.php +++ b/Slim/Handlers/ErrorHandler.php @@ -11,6 +11,7 @@ namespace Slim\Handlers; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; @@ -20,7 +21,6 @@ use Slim\Error\Renderers\XmlErrorRenderer; use Slim\Exception\HttpException; use Slim\Exception\HttpMethodNotAllowedException; -use Slim\Http\Response; use Slim\Interfaces\ErrorHandlerInterface; use Slim\Interfaces\ErrorRendererInterface; use Throwable; @@ -30,6 +30,9 @@ * * It outputs the error message and diagnostic information in one of the following formats: * JSON, XML, Plain Text or HTML based on the Accept header. + * + * @package Slim + * @since 4.0.0 */ class ErrorHandler implements ErrorHandlerInterface { @@ -91,6 +94,20 @@ class ErrorHandler implements ErrorHandlerInterface */ protected $statusCode; + /** + * @var ResponseFactoryInterface + */ + protected $responseFactory; + + /** + * ErrorHandler constructor. + * @param ResponseFactoryInterface $responseFactory + */ + public function __construct(ResponseFactoryInterface $responseFactory) + { + $this->responseFactory = $responseFactory; + } + /** * Invoke error handler * @@ -180,15 +197,14 @@ protected function determineRenderer(): ErrorRendererInterface if ($renderer !== null && ( - !class_exists($renderer) - || !in_array('Slim\Interfaces\ErrorRendererInterface', class_implements($renderer)) + (is_string($renderer) && !class_exists($renderer)) + || !in_array(ErrorRendererInterface::class, class_implements($renderer)) ) ) { - throw new RuntimeException(sprintf( + throw new RuntimeException( 'Non compliant error renderer provided (%s). ' . - 'Renderer must implement the ErrorRendererInterface', - $renderer - )); + 'Renderer must implement the ErrorRendererInterface' + ); } if ($renderer === null) { @@ -237,25 +253,27 @@ protected function determineStatusCode(): int */ protected function respond(): ResponseInterface { - $response = new Response(); - $body = $this->renderer->renderWithBody($this->exception, $this->displayErrorDetails); + $response = $this->responseFactory->createResponse($this->statusCode); + $response = $response->withHeader('Content-type', $this->contentType); if ($this->exception instanceof HttpMethodNotAllowedException) { $allowedMethods = implode(', ', $this->exception->getAllowedMethods()); $response = $response->withHeader('Allow', $allowedMethods); } - return $response - ->withStatus($this->statusCode) - ->withHeader('Content-type', $this->contentType) - ->withBody($body); + /** @var ErrorRendererInterface $renderer */ + $renderer = $this->renderer; + $body = $renderer->render($this->exception, $this->displayErrorDetails); + $response->getBody()->write($body); + + return $response; } /** * Write to the error log if $logErrors has been set to true * @return void */ - protected function writeToErrorLog() + protected function writeToErrorLog(): void { $renderer = new PlainTextErrorRenderer(); $error = $renderer->render($this->exception, $this->logErrorDetails); diff --git a/Slim/Handlers/Strategies/RequestHandler.php b/Slim/Handlers/Strategies/RequestHandler.php index eea5b72ef..226ef52e4 100644 --- a/Slim/Handlers/Strategies/RequestHandler.php +++ b/Slim/Handlers/Strategies/RequestHandler.php @@ -25,6 +25,8 @@ class RequestHandler implements InvocationStrategyInterface * * @param callable $callable * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param array $routeArguments * * @return ResponseInterface */ diff --git a/Slim/Interfaces/ErrorRendererInterface.php b/Slim/Interfaces/ErrorRendererInterface.php index 50f4893c4..b7f274f36 100644 --- a/Slim/Interfaces/ErrorRendererInterface.php +++ b/Slim/Interfaces/ErrorRendererInterface.php @@ -11,7 +11,6 @@ namespace Slim\Interfaces; -use Slim\Http\Body; use Throwable; /** @@ -28,11 +27,4 @@ interface ErrorRendererInterface * @return string */ public function render(Throwable $exception, bool $displayErrorDetails): string; - - /** - * @param Throwable $exception - * @param bool $displayErrorDetails - * @return Body - */ - public function renderWithBody(Throwable $exception, bool $displayErrorDetails): Body; } diff --git a/Slim/Middleware/ErrorMiddleware.php b/Slim/Middleware/ErrorMiddleware.php index 803880453..8f488d949 100644 --- a/Slim/Middleware/ErrorMiddleware.php +++ b/Slim/Middleware/ErrorMiddleware.php @@ -11,6 +11,7 @@ namespace Slim\Middleware; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\Exception\HttpException; @@ -26,6 +27,11 @@ class ErrorMiddleware */ protected $callableResolver; + /** + * @var ResponseFactoryInterface + */ + protected $responseFactory; + /** * @var bool */ @@ -54,17 +60,20 @@ class ErrorMiddleware /** * ErrorMiddleware constructor. * @param CallableResolverInterface $callableResolver + * @param ResponseFactoryInterface $responseFactory * @param bool $displayErrorDetails * @param bool $logErrors * @param bool $logErrorDetails */ public function __construct( CallableResolverInterface $callableResolver, + ResponseFactoryInterface $responseFactory, bool $displayErrorDetails, bool $logErrors, bool $logErrorDetails ) { $this->callableResolver = $callableResolver; + $this->responseFactory = $responseFactory; $this->displayErrorDetails = $displayErrorDetails; $this->logErrors = $logErrors; $this->logErrorDetails = $logErrorDetails; @@ -186,6 +195,6 @@ public function getDefaultErrorHandler() return $this->callableResolver->resolve($this->defaultErrorHandler); } - return new ErrorHandler(); + return new ErrorHandler($this->responseFactory); } } diff --git a/Slim/Middleware/MethodOverrideMiddleware.php b/Slim/Middleware/MethodOverrideMiddleware.php index a298bc495..75beba633 100644 --- a/Slim/Middleware/MethodOverrideMiddleware.php +++ b/Slim/Middleware/MethodOverrideMiddleware.php @@ -36,10 +36,10 @@ public function __invoke( if ($methodHeader) { $request = $request->withMethod($methodHeader); - } elseif (strtoupper($request->getMethod()) == 'POST') { + } elseif (strtoupper($request->getMethod()) === 'POST') { $body = $request->getParsedBody(); - if (!empty($body['_METHOD'])) { + if (is_array($body) && !empty($body['_METHOD'])) { $request = $request->withMethod($body['_METHOD']); } diff --git a/Slim/Middleware/OutputBufferingMiddleware.php b/Slim/Middleware/OutputBufferingMiddleware.php index 073fa3bac..1398debb4 100644 --- a/Slim/Middleware/OutputBufferingMiddleware.php +++ b/Slim/Middleware/OutputBufferingMiddleware.php @@ -13,7 +13,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Http\Body; +use Psr\Http\Message\StreamFactoryInterface; use Throwable; class OutputBufferingMiddleware @@ -21,6 +21,11 @@ class OutputBufferingMiddleware const APPEND = 'append'; const PREPEND = 'prepend'; + /** + * @var StreamFactoryInterface + */ + protected $streamFactory; + /** * @var string */ @@ -29,15 +34,17 @@ class OutputBufferingMiddleware /** * Constructor * + * @param StreamFactoryInterface $streamFactory * @param string $style Either "append" or "prepend" */ - public function __construct(string $style = 'append') + public function __construct(StreamFactoryInterface $streamFactory, string $style = 'append') { + $this->streamFactory = $streamFactory; + $this->style = $style; + if (!in_array($style, [static::APPEND, static::PREPEND])) { throw new \InvalidArgumentException('Invalid style. Must be one of: append, prepend'); } - - $this->style = $style; } /** @@ -56,6 +63,7 @@ public function __invoke( ): ResponseInterface { try { ob_start(); + /** @var ResponseInterface $newResponse */ $newResponse = $next($request, $response); $output = ob_get_clean(); } catch (\Throwable $e) { @@ -65,7 +73,7 @@ public function __invoke( if (!empty($output) && $newResponse->getBody()->isWritable()) { if ($this->style === static::PREPEND) { - $body = new Body(fopen('php://temp', 'r+')); + $body = $this->streamFactory->createStream(); $body->write($output . $newResponse->getBody()); $newResponse = $newResponse->withBody($body); } elseif ($this->style === static::APPEND) { diff --git a/Slim/Middleware/RoutingMiddleware.php b/Slim/Middleware/RoutingMiddleware.php index 9d1b9fe5c..eb0f64d5c 100644 --- a/Slim/Middleware/RoutingMiddleware.php +++ b/Slim/Middleware/RoutingMiddleware.php @@ -77,7 +77,8 @@ public function performRouting(ServerRequestInterface $request): ServerRequestIn switch ($routeStatus) { case Dispatcher::FOUND: $routeArguments = $routingResults->getRouteArguments(); - $route = $this->router->lookupRoute($routingResults->getRouteIdentifier()); + $routeIdentifier = $routingResults->getRouteIdentifier() ?? ''; + $route = $this->router->lookupRoute($routeIdentifier); $route->prepare($request, $routeArguments); return $request ->withAttribute('route', $route) diff --git a/Slim/MiddlewareAwareTrait.php b/Slim/MiddlewareAwareTrait.php index cfa9192fe..5c02149d7 100644 --- a/Slim/MiddlewareAwareTrait.php +++ b/Slim/MiddlewareAwareTrait.php @@ -28,7 +28,7 @@ trait MiddlewareAwareTrait /** * Tip of the middleware call stack * - * @var callable + * @var callable|null */ protected $tip; diff --git a/Slim/ResponseEmitter.php b/Slim/ResponseEmitter.php new file mode 100644 index 000000000..8d6a79221 --- /dev/null +++ b/Slim/ResponseEmitter.php @@ -0,0 +1,144 @@ +responseChunkSize = $responseChunkSize; + } + + /** + * Asserts response body is empty or status code is 204, 205 or 304 + * + * @param ResponseInterface $response + * @return bool + */ + public function isResponseEmpty(ResponseInterface $response): bool + { + $contents = (string) $response->getBody(); + return empty($contents) || in_array($response->getStatusCode(), [204, 205, 304]); + } + + /** + * Send the response the client + * + * @param ResponseInterface $response + * @return void + */ + public function emit(ResponseInterface $response): void + { + if (headers_sent() === false) { + if ($this->isResponseEmpty($response)) { + $response = $response + ->withoutHeader('Content-Type') + ->withoutHeader('Content-Length'); + } + $this->emitHeaders($response); + $this->emitStatusLine($response); + } + + if (!$this->isResponseEmpty($response)) { + $this->emitBody($response); + } + } + + /** + * Emit Response Headers + * + * @param ResponseInterface $response + */ + private function emitHeaders(ResponseInterface $response): void + { + foreach ($response->getHeaders() as $name => $values) { + $first = $name !== 'Set-Cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + } + + /** + * Emit Status Line + * + * @param ResponseInterface $response + */ + private function emitStatusLine(ResponseInterface $response): void + { + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + } + + /** + * Emit Body + * + * @param ResponseInterface $response + */ + private function emitBody(ResponseInterface $response): void + { + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + $amountToRead = (int) $response->getHeaderLine('Content-Length'); + if (!$amountToRead) { + $amountToRead = $body->getSize(); + } + + if ($amountToRead) { + while ($amountToRead > 0 && !$body->eof()) { + $length = min($this->responseChunkSize, $amountToRead); + $data = $body->read($length); + echo $data; + + $amountToRead -= strlen($data); + + if (connection_status() != CONNECTION_NORMAL) { + break; + } + } + } else { + while (!$body->eof()) { + echo $body->read($this->responseChunkSize); + if (connection_status() != CONNECTION_NORMAL) { + break; + } + } + } + } +} diff --git a/Slim/Routable.php b/Slim/Routable.php index 4ced5f60e..1ca59fd6c 100644 --- a/Slim/Routable.php +++ b/Slim/Routable.php @@ -29,7 +29,7 @@ abstract class Routable protected $callable; /** - * @var \Slim\Interfaces\CallableResolverInterface + * @var CallableResolverInterface|null */ protected $callableResolver; diff --git a/Slim/Route.php b/Slim/Route.php index 461ae97ea..ad68572a9 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -312,12 +312,13 @@ public function run(ServerRequestInterface $request, ResponseInterface $response */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { - // Resolve route callable + /** @var callable $callable */ $callable = $this->callable; if ($this->callableResolver) { $callable = $this->callableResolver->resolve($callable); } + /** @var InvocationStrategyInterface|RequestHandler $handler */ $handler = $this->routeInvocationStrategy; if (is_array($callable) && $callable[0] instanceof RequestHandlerInterface) { // callables that implement RequestHandlerInterface use the RequestHandler strategy diff --git a/Slim/RouteGroup.php b/Slim/RouteGroup.php index ddb3ca2a2..25a8ea050 100644 --- a/Slim/RouteGroup.php +++ b/Slim/RouteGroup.php @@ -39,7 +39,7 @@ public function __construct(string $pattern, $callable) */ public function __invoke(App $app = null) { - // Resolve route callable + /** @var callable $callable */ $callable = $this->callable; if ($this->callableResolver) { $callable = $this->callableResolver->resolve($callable); diff --git a/Slim/Router.php b/Slim/Router.php index 89e58fc44..c4523f21b 100644 --- a/Slim/Router.php +++ b/Slim/Router.php @@ -11,12 +11,12 @@ namespace Slim; -use InvalidArgumentException; -use RuntimeException; -use Psr\Http\Message\ServerRequestInterface; use FastRoute\RouteCollector; use FastRoute\RouteParser; use FastRoute\RouteParser\Std as StdParser; +use InvalidArgumentException; +use Psr\Http\Message\ServerRequestInterface; +use RuntimeException; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\InvocationStrategyInterface; use Slim\Interfaces\RouteGroupInterface; @@ -43,12 +43,12 @@ class Router implements RouterInterface /** * Callable resolver * - * @var \Slim\Interfaces\CallableResolverInterface + * @var CallableResolverInterface|null */ protected $callableResolver; /** - * @var \Slim\Interfaces\InvocationStrategyInterface + * @var InvocationStrategyInterface|null */ protected $routeInvocationStrategy; @@ -69,7 +69,7 @@ class Router implements RouterInterface /** * Routes * - * @var Route[] + * @var array */ protected $routes = []; @@ -87,7 +87,7 @@ class Router implements RouterInterface protected $routeGroups = []; /** - * @var Dispatcher + * @var Dispatcher|null */ protected $dispatcher; @@ -138,7 +138,7 @@ public function setBasePath(string $basePath): self /** * Set path to fast route cache file. If this is false then route caching is disabled. * - * @param string|false $cacheFile + * @param string|bool $cacheFile * * @return self * @@ -176,7 +176,7 @@ public function setCallableResolver(CallableResolverInterface $resolver) * * @param string[] $methods Array of HTTP methods * @param string $pattern The route pattern - * @param callable $handler The route callable + * @param callable|string $handler The route callable * * @return RouteInterface */ @@ -190,7 +190,7 @@ public function map(array $methods, string $pattern, $handler): RouteInterface // According to RFC methods are defined in uppercase (See RFC 7231) $methods = array_map("strtoupper", $methods); - // Add route + /** @var Route $route */ $route = $this->createRoute($methods, $pattern, $handler); $this->routes[$route->getIdentifier()] = $route; $this->routeCounter++; @@ -216,7 +216,7 @@ public function dispatch(ServerRequestInterface $request): RoutingResults * * @param string[] $methods Array of HTTP methods * @param string $pattern The route pattern - * @param callable $callable The route callable + * @param callable|string $callable The route callable * * @return RouteInterface */ @@ -249,18 +249,21 @@ protected function createDispatcher(): Dispatcher }; if ($this->cacheFile) { - $this->dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [ + /** @var Dispatcher $dispatcher */ + $dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [ 'dispatcher' => Dispatcher::class, 'routeParser' => $this->routeParser, 'cacheFile' => $this->cacheFile, ]); } else { - $this->dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [ + /** @var Dispatcher $dispatcher */ + $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [ 'dispatcher' => Dispatcher::class, 'routeParser' => $this->routeParser, ]); } + $this->dispatcher = $dispatcher; return $this->dispatcher; } @@ -310,9 +313,8 @@ public function getNamedRoute(string $name): RouteInterface */ public function removeNamedRoute(string $name) { + /** @var Route $route */ $route = $this->getNamedRoute($name); - - // no exception, route exists, now remove by id unset($this->routes[$route->getIdentifier()]); } @@ -392,6 +394,7 @@ public function relativePathFor(string $name, array $data = [], array $queryPara $routeDatas = array_reverse($routeDatas); $segments = []; + $segmentName = ''; foreach ($routeDatas as $routeData) { foreach ($routeData as $item) { if (is_string($item)) { diff --git a/composer.json b/composer.json index 93d8eceff..56650641c 100644 --- a/composer.json +++ b/composer.json @@ -28,38 +28,41 @@ } ], "require": { - "php": ">=7.0.0", - "slim/http": "0.4", - "psr/http-message": "^1.0", + "ext-json": "*", "nikic/fast-route": "^1.0", + "php": "^7.1", "psr/container": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", "psr/http-server-handler": "^1.0" }, "require-dev": { + "ext-simplexml": "*", + "nyholm/psr7": "^1.0", + "phpunit/phpunit": "^7.0", + "phpstan/phpstan": "^0.10.3", "pimple/pimple": "^3.2", - "squizlabs/php_codesniffer": "^2.9", - "phpunit/phpunit": "^6.2", - "php-coveralls/php-coveralls": "^1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" + "squizlabs/php_codesniffer": "^3.3.2" }, "autoload": { "psr-4": { - "Slim\\": "Slim" + "Slim\\": "Slim", + "Slim\\Tests\\": "tests" } }, "autoload-dev": { "files": [ - "tests/Assets/HeaderFunctions.php" + "tests/Assets/HeaderFunctions.php" ] }, "scripts": { "test": [ "@phpunit", - "@phpcs" + "@phpcs", + "@phpstan" ], - "phpunit": "php vendor/bin/phpunit", - "phpcs": "php vendor/bin/phpcs" + "phpunit": "php vendor/bin/phpunit --process-isolation", + "phpcs": "php vendor/bin/phpcs", + "phpstan": "php -d memory_limit=-1 vendor/bin/phpstan analyse Slim" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000..ab0ce4d9e --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,6 @@ +parameters: + level: max + ignoreErrors: + # This error is because of line 329 in Route. PHPStan cannot compute the logic of a callable array of strings [class, method] + - '^Parameter #1 $callable of callable Slim\\Interfaces\\InvocationStrategyInterface expects callable, array|callable given.^' + diff --git a/tests/AppTest.php b/tests/AppTest.php index 12d30c625..d100e7ccf 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -8,137 +8,87 @@ */ namespace Slim\Tests; -use PHPUnit\Framework\TestCase; use Pimple\Container as Pimple; use Pimple\Psr11\Container as Psr11Container; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; use Slim\App; use Slim\CallableResolver; use Slim\Error\Renderers\HtmlErrorRenderer; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Handlers\Strategies\RequestResponseArgs; -use Slim\HeaderStackTestAsset; -use Slim\Http\Body; -use Slim\Http\Environment; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; use Slim\Router; use Slim\Tests\Mocks\MockAction; -/** - * Emit a header, without creating actual output artifacts - * - * @param string $value - */ -function header($value, $replace = true) -{ - \Slim\header($value, $replace); -} - class AppTest extends TestCase { - public function setUp() - { - HeaderStackTestAsset::reset(); - } - - public function tearDown() - { - HeaderStackTestAsset::reset(); - } - public static function setupBeforeClass() { - // ini_set('log_errors', 0); ini_set('error_log', tempnam(sys_get_temp_dir(), 'slim')); } - public static function tearDownAfterClass() - { - // ini_set('log_errors', 1); - } - - /** - * helper to create a request object - * @return Request - */ - private function requestFactory($requestUri, $method = 'GET', $data = []) - { - $defaults = [ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => $requestUri, - 'REQUEST_METHOD' => $method, - ]; - - $data = array_merge($defaults, $data); - - $env = Environment::mock($data); - $uri = Uri::createFromGlobals($env); - $headers = Headers::createFromGlobals($env); - $cookies = []; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - return $request; - } - /******************************************************************************** * Settings management methods *******************************************************************************/ public function testHasSetting() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $this->assertTrue($app->hasSetting('httpVersion')); $this->assertFalse($app->hasSetting('foo')); } public function testGetSettings() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $appSettings = $app->getSettings(); $this->assertAttributeEquals($appSettings, 'settings', $app); } public function testGetSettingExists() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $this->assertEquals('1.1', $app->getSetting('httpVersion')); } public function testGetSettingNotExists() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $this->assertNull($app->getSetting('foo')); } public function testGetSettingNotExistsWithDefault() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $this->assertEquals('what', $app->getSetting('foo', 'what')); } public function testAddSettings() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->addSettings(['foo' => 'bar']); $this->assertAttributeContains('bar', 'settings', $app); } public function testAddSetting() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->addSetting('foo', 'bar'); $this->assertAttributeContains('bar', 'settings', $app); } public function testSetContainer() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $pimple = new Pimple(); $container = new Psr11Container($pimple); $app->setContainer($container); @@ -147,7 +97,8 @@ public function testSetContainer() public function testSetCallableResolver() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $callableResolver = new CallableResolver(); $app->setCallableResolver($callableResolver); $this->assertSame($callableResolver, $app->getCallableResolver()); @@ -155,7 +106,8 @@ public function testSetCallableResolver() public function testSetRouter() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $router = new Router(); $app->setRouter($router); $this->assertSame($router, $app->getRouter()); @@ -168,10 +120,11 @@ public function testSetRouter() public function testGetRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->get($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -181,10 +134,11 @@ public function testGetRoute() public function testPostRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->post($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -194,10 +148,12 @@ public function testPostRoute() public function testPutRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->put($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -207,10 +163,12 @@ public function testPutRoute() public function testPatchRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->patch($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -220,10 +178,12 @@ public function testPatchRoute() public function testDeleteRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->delete($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -233,10 +193,12 @@ public function testDeleteRoute() public function testOptionsRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->options($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -246,10 +208,12 @@ public function testOptionsRoute() public function testAnyRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->any($path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -264,10 +228,12 @@ public function testAnyRoute() public function testMapRoute() { $path = '/foo'; - $callable = function ($req, $res) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }; - $app = new App(); + + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->map(['GET', 'POST'], $path, $callable); $this->assertInstanceOf('\Slim\Route', $route); @@ -280,39 +246,41 @@ public function testRedirectRoute() $source = '/foo'; $destination = '/bar'; - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $route = $app->redirect($source, $destination, 301); $this->assertInstanceOf('\Slim\Route', $route); $this->assertAttributeContains('GET', 'methods', $route); - $response = $route->run($this->requestFactory($source), new Response()); + $response = $route->run($this->createServerRequest($source), $this->createResponse()); $this->assertEquals(301, $response->getStatusCode()); $this->assertEquals($destination, $response->getHeaderLine('Location')); $routeWithDefaultStatus = $app->redirect($source, $destination); - $response = $routeWithDefaultStatus->run($this->requestFactory($source), new Response()); + $response = $routeWithDefaultStatus->run($this->createServerRequest($source), $this->createResponse()); $this->assertEquals(302, $response->getStatusCode()); $uri = $this->getMockBuilder(UriInterface::class)->getMock(); $uri->expects($this->once())->method('__toString')->willReturn($destination); $routeToUri = $app->redirect($source, $uri); - $response = $routeToUri->run($this->requestFactory($source), new Response()); + $response = $routeToUri->run($this->createServerRequest($source), $this->createResponse()); $this->assertEquals($destination, $response->getHeaderLine('Location')); } public function testRouteWithInternationalCharacters() { - $app = new App(); - $app->get('/новости', function ($req, $res) { - $res->write('Hello'); - return $res; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/новости', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Hello'); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/новости'); - $response = new Response(); + $request = $this->createServerRequest('/новости'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -326,8 +294,9 @@ public function testRouteWithInternationalCharacters() *******************************************************************************/ public function testSegmentRouteThatDoesNotEndInASlash() { - $app = new App(); - $app->get('/foo', function ($req, $res) { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); /** @var \Slim\Router $router */ @@ -337,8 +306,9 @@ public function testSegmentRouteThatDoesNotEndInASlash() public function testSegmentRouteThatEndsInASlash() { - $app = new App(); - $app->get('/foo/', function ($req, $res) { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); /** @var \Slim\Router $router */ @@ -348,8 +318,9 @@ public function testSegmentRouteThatEndsInASlash() public function testSegmentRouteThatDoesNotStartWithASlash() { - $app = new App(); - $app->get('foo', function ($req, $res) { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('foo', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); /** @var \Slim\Router $router */ @@ -359,8 +330,9 @@ public function testSegmentRouteThatDoesNotStartWithASlash() public function testSingleSlashRoute() { - $app = new App(); - $app->get('/', function ($req, $res) { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); /** @var \Slim\Router $router */ @@ -370,8 +342,9 @@ public function testSingleSlashRoute() public function testEmptyRoute() { - $app = new App(); - $app->get('', function ($req, $res) { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); /** @var \Slim\Router $router */ @@ -384,9 +357,10 @@ public function testEmptyRoute() *******************************************************************************/ public function testGroupSegmentWithSegmentRouteThatDoesNotEndInASlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -397,9 +371,10 @@ public function testGroupSegmentWithSegmentRouteThatDoesNotEndInASlash() public function testGroupSegmentWithSegmentRouteThatEndsInASlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { - $app->get('/bar/', function ($req, $res) { + $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -410,9 +385,10 @@ public function testGroupSegmentWithSegmentRouteThatEndsInASlash() public function testGroupSegmentWithSingleSlashRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { - $app->get('/', function ($req, $res) { + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -423,9 +399,10 @@ public function testGroupSegmentWithSingleSlashRoute() public function testGroupSegmentWithEmptyRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { - $app->get('', function ($req, $res) { + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -436,10 +413,11 @@ public function testGroupSegmentWithEmptyRoute() public function testTwoGroupSegmentsWithSingleSlashRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/baz', function ($app) { - $app->get('/', function ($req, $res) { + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -451,10 +429,11 @@ public function testTwoGroupSegmentsWithSingleSlashRoute() public function testTwoGroupSegmentsWithAnEmptyRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/baz', function ($app) { - $app->get('', function ($req, $res) { + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -466,10 +445,11 @@ public function testTwoGroupSegmentsWithAnEmptyRoute() public function testTwoGroupSegmentsWithSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/baz', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -481,10 +461,11 @@ public function testTwoGroupSegmentsWithSegmentRoute() public function testTwoGroupSegmentsWithSegmentRouteThatHasATrailingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/baz', function ($app) { - $app->get('/bar/', function ($req, $res) { + $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -496,10 +477,11 @@ public function testTwoGroupSegmentsWithSegmentRouteThatHasATrailingSlash() public function testGroupSegmentWithSingleSlashNestedGroupAndSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -511,10 +493,11 @@ public function testGroupSegmentWithSingleSlashNestedGroupAndSegmentRoute() public function testGroupSegmentWithSingleSlashGroupAndSegmentRouteWithoutLeadingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/', function ($app) { - $app->get('bar', function ($req, $res) { + $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -526,10 +509,11 @@ public function testGroupSegmentWithSingleSlashGroupAndSegmentRouteWithoutLeadin public function testGroupSegmentWithEmptyNestedGroupAndSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -541,10 +525,11 @@ public function testGroupSegmentWithEmptyNestedGroupAndSegmentRoute() public function testGroupSegmentWithEmptyNestedGroupAndSegmentRouteWithoutLeadingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('', function ($app) { - $app->get('bar', function ($req, $res) { + $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -556,9 +541,10 @@ public function testGroupSegmentWithEmptyNestedGroupAndSegmentRouteWithoutLeadin public function testGroupSingleSlashWithSegmentRouteThatDoesNotEndInASlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -569,9 +555,10 @@ public function testGroupSingleSlashWithSegmentRouteThatDoesNotEndInASlash() public function testGroupSingleSlashWithSegmentRouteThatEndsInASlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { - $app->get('/bar/', function ($req, $res) { + $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -582,9 +569,10 @@ public function testGroupSingleSlashWithSegmentRouteThatEndsInASlash() public function testGroupSingleSlashWithSingleSlashRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { - $app->get('/', function ($req, $res) { + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -595,9 +583,10 @@ public function testGroupSingleSlashWithSingleSlashRoute() public function testGroupSingleSlashWithEmptyRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { - $app->get('', function ($req, $res) { + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -608,10 +597,11 @@ public function testGroupSingleSlashWithEmptyRoute() public function testGroupSingleSlashWithNestedGroupSegmentWithSingleSlashRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('/baz', function ($app) { - $app->get('/', function ($req, $res) { + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -623,10 +613,11 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithSingleSlashRoute() public function testGroupSingleSlashWithNestedGroupSegmentWithAnEmptyRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('/baz', function ($app) { - $app->get('', function ($req, $res) { + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -638,10 +629,11 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithAnEmptyRoute() public function testGroupSingleSlashWithNestedGroupSegmentWithSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('/baz', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -653,10 +645,11 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithSegmentRoute() public function testGroupSingleSlashWithNestedGroupSegmentWithSegmentRouteThatHasATrailingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('/baz', function ($app) { - $app->get('/bar/', function ($req, $res) { + $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -668,10 +661,11 @@ public function testGroupSingleSlashWithNestedGroupSegmentWithSegmentRouteThatHa public function testGroupSingleSlashWithSingleSlashNestedGroupAndSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('/', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -683,10 +677,11 @@ public function testGroupSingleSlashWithSingleSlashNestedGroupAndSegmentRoute() public function testGroupSingleSlashWithSingleSlashGroupAndSegmentRouteWithoutLeadingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('/', function ($app) { - $app->get('bar', function ($req, $res) { + $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -698,10 +693,11 @@ public function testGroupSingleSlashWithSingleSlashGroupAndSegmentRouteWithoutLe public function testGroupSingleSlashWithEmptyNestedGroupAndSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -713,10 +709,11 @@ public function testGroupSingleSlashWithEmptyNestedGroupAndSegmentRoute() public function testGroupSingleSlashWithEmptyNestedGroupAndSegmentRouteWithoutLeadingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/', function ($app) { $app->group('', function ($app) { - $app->get('bar', function ($req, $res) { + $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -728,9 +725,10 @@ public function testGroupSingleSlashWithEmptyNestedGroupAndSegmentRouteWithoutLe public function testEmptyGroupWithSegmentRouteThatDoesNotEndInASlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -741,9 +739,10 @@ public function testEmptyGroupWithSegmentRouteThatDoesNotEndInASlash() public function testEmptyGroupWithSegmentRouteThatEndsInASlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { - $app->get('/bar/', function ($req, $res) { + $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -754,9 +753,10 @@ public function testEmptyGroupWithSegmentRouteThatEndsInASlash() public function testEmptyGroupWithSingleSlashRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { - $app->get('/', function ($req, $res) { + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -767,9 +767,10 @@ public function testEmptyGroupWithSingleSlashRoute() public function testEmptyGroupWithEmptyRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { - $app->get('', function ($req, $res) { + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -780,10 +781,11 @@ public function testEmptyGroupWithEmptyRoute() public function testEmptyGroupWithNestedGroupSegmentWithSingleSlashRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('/baz', function ($app) { - $app->get('/', function ($req, $res) { + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -795,10 +797,11 @@ public function testEmptyGroupWithNestedGroupSegmentWithSingleSlashRoute() public function testEmptyGroupWithNestedGroupSegmentWithAnEmptyRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('/baz', function ($app) { - $app->get('', function ($req, $res) { + $app->get('', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -810,10 +813,11 @@ public function testEmptyGroupWithNestedGroupSegmentWithAnEmptyRoute() public function testEmptyGroupWithNestedGroupSegmentWithSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('/baz', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -825,10 +829,11 @@ public function testEmptyGroupWithNestedGroupSegmentWithSegmentRoute() public function testEmptyGroupWithNestedGroupSegmentWithSegmentRouteThatHasATrailingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('/baz', function ($app) { - $app->get('/bar/', function ($req, $res) { + $app->get('/bar/', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -840,10 +845,11 @@ public function testEmptyGroupWithNestedGroupSegmentWithSegmentRouteThatHasATrai public function testEmptyGroupWithSingleSlashNestedGroupAndSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('/', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -855,10 +861,11 @@ public function testEmptyGroupWithSingleSlashNestedGroupAndSegmentRoute() public function testEmptyGroupWithSingleSlashGroupAndSegmentRouteWithoutLeadingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('/', function ($app) { - $app->get('bar', function ($req, $res) { + $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -870,10 +877,11 @@ public function testEmptyGroupWithSingleSlashGroupAndSegmentRouteWithoutLeadingS public function testEmptyGroupWithEmptyNestedGroupAndSegmentRoute() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('', function ($app) { - $app->get('/bar', function ($req, $res) { + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -885,10 +893,11 @@ public function testEmptyGroupWithEmptyNestedGroupAndSegmentRoute() public function testEmptyGroupWithEmptyNestedGroupAndSegmentRouteWithoutLeadingSlash() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('', function ($app) { $app->group('', function ($app) { - $app->get('bar', function ($req, $res) { + $app->get('bar', function (ServerRequestInterface $request, ResponseInterface $response) { // Do something }); }); @@ -904,11 +913,13 @@ public function testEmptyGroupWithEmptyNestedGroupAndSegmentRouteWithoutLeadingS public function testBottomMiddlewareIsApp() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $bottom = null; - $mw = function ($req, $res, $next) use (&$bottom) { + $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { $bottom = $next; - return $res; + return $response; }; $app->add($mw); @@ -922,12 +933,13 @@ public function testBottomMiddlewareIsApp() public function testAddMiddleware() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $called = 0; - $mw = function ($req, $res, $next) use (&$called) { + $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$called) { $called++; - return $res; + return $response; }; $app->add($mw); @@ -941,27 +953,27 @@ public function testAddMiddleware() public function testAddMiddlewareOnRoute() { - $app = new App(); - - $app->get('/', function ($req, $res) { - return $res->write('Center'); - })->add(function ($req, $res, $next) { - $res->write('In1'); - $res = $next($req, $res); - $res->write('Out1'); - - return $res; - })->add(function ($req, $res, $next) { - $res->write('In2'); - $res = $next($req, $res); - $res->write('Out2'); - - return $res; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Center'); + return $response; + })->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; }); // Prepare request and response objects - $request = $this->requestFactory('/'); - $response = new Response(); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); // Invoke app $response = $app($request, $response); @@ -972,29 +984,29 @@ public function testAddMiddlewareOnRoute() public function testAddMiddlewareOnRouteGroup() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { - $app->get('/', function ($req, $res) { - return $res->write('Center'); + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Center'); + return $response; }); - })->add(function ($req, $res, $next) { - $res->write('In1'); - $res = $next($req, $res); - $res->write('Out1'); - - return $res; - })->add(function ($req, $res, $next) { - $res->write('In2'); - $res = $next($req, $res); - $res->write('Out2'); - - return $res; + })->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; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/'); - $response = new Response(); + $request = $this->createServerRequest('/foo/'); + $response = $this->createResponse(); // Invoke app $response = $app($request, $response); @@ -1004,31 +1016,31 @@ public function testAddMiddlewareOnRouteGroup() public function testAddMiddlewareOnTwoRouteGroup() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/baz', function ($app) { - $app->get('/', function ($req, $res) { - return $res->write('Center'); + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Center'); + return $response; }); - })->add(function ($req, $res, $next) { - $res->write('In2'); - $res = $next($req, $res); - $res->write('Out2'); - - return $res; + })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { + $response->getBody()->write('In2'); + $response = $next($request, $response); + $response->getBody()->write('Out2'); + return $response; }); - })->add(function ($req, $res, $next) { - $res->write('In1'); - $res = $next($req, $res); - $res->write('Out1'); - - return $res; + })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { + $response->getBody()->write('In1'); + $response = $next($request, $response); + $response->getBody()->write('Out1'); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/baz/'); - $response = new Response(); + $request = $this->createServerRequest('/foo/baz/'); + $response = $this->createResponse(); // Invoke app $response = $app($request, $response); @@ -1038,37 +1050,36 @@ public function testAddMiddlewareOnTwoRouteGroup() public function testAddMiddlewareOnRouteAndOnTwoRouteGroup() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->group('/foo', function ($app) { $app->group('/baz', function ($app) { - $app->get('/', function ($req, $res) { - return $res->write('Center'); - })->add(function ($req, $res, $next) { - $res->write('In3'); - $res = $next($req, $res); - $res->write('Out3'); - - return $res; + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Center'); + return $response; + })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { + $response->getBody()->write('In3'); + $response = $next($request, $response); + $response->getBody()->write('Out3'); + return $response; }); - })->add(function ($req, $res, $next) { - $res->write('In2'); - $res = $next($req, $res); - $res->write('Out2'); - - return $res; + })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { + $response->getBody()->write('In2'); + $response = $next($request, $response); + $response->getBody()->write('Out2'); + return $response; }); - })->add(function ($req, $res, $next) { - $res->write('In1'); - $res = $next($req, $res); - $res->write('Out1'); - - return $res; + })->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { + $response->getBody()->write('In1'); + $response = $next($request, $response); + $response->getBody()->write('Out1'); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/baz/'); - $response = new Response(); + $request = $this->createServerRequest('/foo/baz/'); + $response = $this->createResponse(); // Invoke app $response = $app($request, $response); @@ -1086,16 +1097,16 @@ public function testAddMiddlewareOnRouteAndOnTwoRouteGroup() */ public function testInvokeReturnMethodNotAllowed() { - $app = new App(); - $app->get('/foo', function ($req, $res) { - $res->write('Hello'); - - return $res; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Hello'); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo', 'POST'); - $response = new Response(); + $request = $this->createServerRequest('/foo', 'POST'); + $response = $this->createResponse(); // Create Html Renderer and Assert Output $exception = new HttpMethodNotAllowedException($request); @@ -1116,15 +1127,16 @@ public function testInvokeReturnMethodNotAllowed() public function testInvokeWithMatchingRoute() { - $app = new App(); - $app->get('/foo', function ($req, $res) { - $res->write('Hello'); - return $res; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Hello'); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1135,14 +1147,16 @@ public function testInvokeWithMatchingRoute() public function testInvokeWithMatchingRouteWithSetArgument() { - $app = new App(); - $app->get('/foo/bar', function ($req, $res, $args) { - return $res->write("Hello {$args['attribute']}"); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo/bar', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write("Hello {$args['attribute']}"); + return $response; })->setArgument('attribute', 'world!'); // Prepare request and response objects - $request = $this->requestFactory('/foo/bar'); - $response = new Response(); + $request = $this->createServerRequest('/foo/bar'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1153,14 +1167,16 @@ public function testInvokeWithMatchingRouteWithSetArgument() public function testInvokeWithMatchingRouteWithSetArguments() { - $app = new App(); - $app->get('/foo/bar', function ($req, $res, $args) { - return $res->write("Hello {$args['attribute1']} {$args['attribute2']}"); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo/bar', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write("Hello {$args['attribute1']} {$args['attribute2']}"); + return $response; })->setArguments(['attribute1' => 'there', 'attribute2' => 'world!']); // Prepare request and response objects - $request = $this->requestFactory('/foo/bar'); - $response = new Response(); + $request = $this->createServerRequest('/foo/bar'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1171,14 +1187,16 @@ public function testInvokeWithMatchingRouteWithSetArguments() public function testInvokeWithMatchingRouteWithNamedParameter() { - $app = new App(); - $app->get('/foo/{name}', function ($req, $res, $args) { - return $res->write("Hello {$args['name']}"); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo/{name}', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write("Hello {$args['name']}"); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/test!'); - $response = new Response(); + $request = $this->createServerRequest('/foo/test!'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1189,15 +1207,17 @@ public function testInvokeWithMatchingRouteWithNamedParameter() public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseArgStrategy() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->getRouter()->setDefaultInvocationStrategy(new RequestResponseArgs()); - $app->get('/foo/{name}', function ($req, $res, $name) { - return $res->write("Hello {$name}"); + $app->get('/foo/{name}', function (ServerRequestInterface $request, ResponseInterface $response, $name) { + $response->getBody()->write("Hello {$name}"); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/test!'); - $response = new Response(); + $request = $this->createServerRequest('/foo/test!'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1208,14 +1228,16 @@ public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseArgS public function testInvokeWithMatchingRouteWithNamedParameterOverwritesSetArgument() { - $app = new App(); - $app->get('/foo/{name}', function ($req, $res, $args) { - return $res->write("Hello {$args['extra']} {$args['name']}"); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo/{name}', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write("Hello {$args['extra']} {$args['name']}"); + return $response; })->setArguments(['extra' => 'there', 'name' => 'world!']); // Prepare request and response objects - $request = $this->requestFactory('/foo/test!'); - $response = new Response(); + $request = $this->createServerRequest('/foo/test!'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1229,16 +1251,16 @@ public function testInvokeWithMatchingRouteWithNamedParameterOverwritesSetArgume */ public function testInvokeWithoutMatchingRoute() { - $app = new App(); - $app->get('/bar', function ($req, $res) { - $res->write('Hello'); - - return $res; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/bar', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Hello'); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1250,22 +1272,23 @@ public function testInvokeWithoutMatchingRoute() public function testInvokeWithCallableRegisteredInContainer() { // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); $mock = $this->getMockBuilder('StdClass')->setMethods(['bar'])->getMock(); $pimple = new Pimple(); $pimple['foo'] = function () use ($mock, $response) { - $mock->method('bar') - ->willReturn( - $response->write('Hello') - ); + $response->getBody()->write('Hello'); + $mock + ->method('bar') + ->willReturn($response); return $mock; }; - $app = new App(); - $app->setContainer(new Psr11Container($pimple)); + $responseFactory = $this->getResponseFactory(); + $container = new Psr11Container($pimple); + $app = new App($responseFactory, $container); $app->get('/foo', 'foo:bar'); // Invoke app @@ -1281,8 +1304,8 @@ public function testInvokeWithCallableRegisteredInContainer() public function testInvokeWithNonExistentMethodOnCallableRegisteredInContainer() { // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); $mock = $this->getMockBuilder('StdClass')->getMock(); @@ -1291,8 +1314,9 @@ public function testInvokeWithNonExistentMethodOnCallableRegisteredInContainer() return $mock; }; - $app = new App(); - $app->setContainer(new Psr11Container($pimple)); + $responseFactory = $this->getResponseFactory(); + $container = new Psr11Container($pimple); + $app = new App($responseFactory, $container); $app->get('/foo', 'foo:bar'); // Invoke app @@ -1302,8 +1326,8 @@ public function testInvokeWithNonExistentMethodOnCallableRegisteredInContainer() public function testInvokeWithCallableInContainerViaMagicMethod() { // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); $mock = new MockAction(); @@ -1312,8 +1336,9 @@ public function testInvokeWithCallableInContainerViaMagicMethod() return $mock; }; - $app = new App(); - $app->setContainer(new Psr11Container($pimple)); + $responseFactory = $this->getResponseFactory(); + $container = new Psr11Container($pimple); + $app = new App($responseFactory, $container); $app->get('/foo', 'foo:bar'); // Invoke app @@ -1325,22 +1350,23 @@ public function testInvokeWithCallableInContainerViaMagicMethod() public function testInvokeFunctionName() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); // @codingStandardsIgnoreStart - function handle($req, $res) + function handle(ServerRequestInterface $request, ResponseInterface $response) { - $res->write('foo'); + $response->getBody()->write('foo'); - return $res; + return $response; } // @codingStandardsIgnoreEnd $app->get('/foo', __NAMESPACE__ . '\handle'); // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1350,15 +1376,17 @@ function handle($req, $res) public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArguments() { - $app = new App(); - $app->get('/foo/{name}', function ($req, $res, $args) { - return $res->write($req->getAttribute('one') . $args['name']); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/foo/{name}', function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write($request->getAttribute('one') . $args['name']); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/rob'); + $request = $this->createServerRequest('/foo/rob'); $request = $request->withAttribute("one", 1); - $response = new Response(); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); @@ -1367,325 +1395,37 @@ public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArguments() public function testCurrentRequestAttributesAreNotLostWhenAddingRouteArgumentsRequestResponseArg() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->getRouter()->setDefaultInvocationStrategy(new RequestResponseArgs()); - $app->get('/foo/{name}', function ($req, $res, $name) { - return $res->write($req->getAttribute('one') . $name); + $app->get('/foo/{name}', function (ServerRequestInterface $request, ResponseInterface $response, $name) { + $response->getBody()->write($request->getAttribute('one') . $name); + return $response; }); // Prepare request and response objects - $request = $this->requestFactory('/foo/rob'); + $request = $this->createServerRequest('/foo/rob'); $request = $request->withAttribute("one", 1); - $response = new Response(); + $response = $this->createResponse(); // Invoke app $resOut = $app($request, $response); $this->assertEquals('1rob', (string)$resOut->getBody()); } - // TODO: Test finalize() - - // TODO: Test run() public function testRun() { - $currentServer = $_SERVER; // backup $_SERVER - - $app = new App(); - $_SERVER = [ - 'HTTP_HOST' => 'example.com', - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'GET', - ]; - - $app->get('/foo', function ($req, $res) { - $res->write('bar'); - - return $res; - }); - - ob_start(); - $app->run(); - $resOut = ob_get_clean(); - - $this->assertEquals('bar', (string)$resOut); - - $_SERVER = $currentServer; // restore $_SERVER - } - - - public function testRespond() - { - $app = new App(); - $app->get('/foo', function ($req, $res) { - $res->write('Hello'); - - return $res; - }); - - // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); - - // Invoke app - $resOut = $app($request, $response); - - $app->respond($resOut); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->expectOutputString('Hello'); - } - - public function testRespondWithHeaderNotSent() - { - $app = new App(); - $app->get('/foo', function ($req, $res) { - $res->write('Hello'); - - return $res; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Hello World'); + return $response; }); - // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/'); + $app->run($request); - // Invoke app - $resOut = $app($request, $response); - - $app->respond($resOut); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->expectOutputString('Hello'); - } - - public function testRespondNoContent() - { - $app = new App(); - $app->get('/foo', function ($req, $res) { - $res = $res->withStatus(204); - return $res; - }); - - // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); - - // Invoke app - $resOut = $app($request, $response); - - $app->respond($resOut); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->assertEquals([], $resOut->getHeader('Content-Type')); - $this->assertEquals([], $resOut->getHeader('Content-Length')); - $this->expectOutputString(''); - } - - public function testRespondWithPaddedStreamFilterOutput() - { - $availableFilter = stream_get_filters(); - - if (version_compare(phpversion(), '7.0.0', '>=')) { - $filterName = 'string.rot13'; - $unfilterName = 'string.rot13'; - $specificFilterName = 'string.rot13'; - $specificUnfilterName = 'string.rot13'; - } else { - $filterName = 'mcrypt.*'; - $unfilterName = 'mdecrypt.*'; - $specificFilterName = 'mcrypt.rijndael-128'; - $specificUnfilterName = 'mdecrypt.rijndael-128'; - } - - if (in_array($filterName, $availableFilter) && in_array($unfilterName, $availableFilter)) { - $app = new App(); - $app->get('/foo', function ($req, $res) use ($specificFilterName, $specificUnfilterName) { - $key = base64_decode('xxxxxxxxxxxxxxxx'); - $iv = base64_decode('Z6wNDk9LogWI4HYlRu0mng=='); - - $data = 'Hello'; - $length = strlen($data); - - $stream = fopen('php://temp', 'r+'); - - $filter = stream_filter_append($stream, $specificFilterName, STREAM_FILTER_WRITE, [ - 'key' => $key, - 'iv' => $iv - ]); - - fwrite($stream, $data); - rewind($stream); - stream_filter_remove($filter); - - stream_filter_append($stream, $specificUnfilterName, STREAM_FILTER_READ, [ - 'key' => $key, - 'iv' => $iv - ]); - - return $res->withHeader('Content-Length', $length)->withBody(new Body($stream)); - }); - - // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); - - // Invoke app - $resOut = $app($request, $response); - $app->respond($resOut); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->expectOutputString('Hello'); - } else { - $this->assertTrue(true); - } - } - - public function testRespondIndeterminateLength() - { - $app = new App(); - $body_stream = fopen('php://temp', 'r+'); - $response = new Response(); - $body = $this->getMockBuilder("\Slim\Http\Body") - ->setMethods(["getSize"]) - ->setConstructorArgs([$body_stream]) - ->getMock(); - fwrite($body_stream, "Hello"); - rewind($body_stream); - $body->method("getSize")->willReturn(null); - $response = $response->withBody($body); - $app->respond($response); - $this->expectOutputString("Hello"); - } - - public function testResponseWithStreamReadYieldingLessBytesThanAsked() - { - $app = new App([ - 'settings' => ['responseChunkSize' => Mocks\SmallChunksStream::CHUNK_SIZE * 2] - ]); - $app->get('/foo', function ($req, $res) { - $res->write('Hello'); - - return $res; - }); - - // Prepare request and response objects - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'GET', - ]); - $uri = Uri::createFromGlobals($env); - $headers = Headers::createFromGlobals($env); - $cookies = []; - $serverParams = $env; - $body = new Mocks\SmallChunksStream(); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = (new Response())->withBody($body); - - // Invoke app - $resOut = $app($request, $response); - - $app->respond($resOut); - - $this->assertInstanceOf('\Psr\Http\Message\ResponseInterface', $resOut); - $this->expectOutputString(str_repeat('.', Mocks\SmallChunksStream::SIZE)); - } - - public function testResponseReplacesPreviouslySetHeaders() - { - $app = new App(); - $app->get('/foo', function ($req, $res) { - return $res - ->withHeader('X-Foo', 'baz1') - ->withAddedHeader('X-Foo', 'baz2') - ; - }); - - // Prepare request and response objects - $serverParams = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'GET', - ]); - $uri = Uri::createFromGlobals($serverParams); - $headers = Headers::createFromGlobals($serverParams); - $cookies = []; - $body = new Body(fopen('php://temp', 'r+')); - $req = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $res = new Response(); - - // Invoke app - $resOut = $app($req, $res); - $app->respond($resOut); - - $expectedStack = [ - ['header' => 'X-Foo: baz1', 'replace' => true, 'status_code' => null], - ['header' => 'X-Foo: baz2', 'replace' => false, 'status_code' => null], - ['header' => 'HTTP/1.1 200 OK', 'replace' => true, 'status_code' => 200], - ]; - - $this->assertSame($expectedStack, HeaderStackTestAsset::stack()); - } - - public function testResponseDoesNotReplacePreviouslySetSetCookieHeaders() - { - $app = new App(); - $app->get('/foo', function ($req, $res) { - return $res - ->withHeader('Set-Cookie', 'foo=bar') - ->withAddedHeader('Set-Cookie', 'bar=baz') - ; - }); - - // Prepare request and response objects - $serverParams = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'GET', - ]); - $uri = Uri::createFromGlobals($serverParams); - $headers = Headers::createFromGlobals($serverParams); - $cookies = []; - $body = new Body(fopen('php://temp', 'r+')); - $req = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $res = new Response(); - - // Invoke app - $resOut = $app($req, $res); - $app->respond($resOut); - - $expectedStack = [ - ['header' => 'Set-Cookie: foo=bar', 'replace' => false, 'status_code' => null], - ['header' => 'Set-Cookie: bar=baz', 'replace' => false, 'status_code' => null], - ['header' => 'HTTP/1.1 200 OK', 'replace' => true, 'status_code' => 200], - ]; - - $this->assertSame($expectedStack, HeaderStackTestAsset::stack()); - } - - public function testFinalize() - { - $method = new \ReflectionMethod('Slim\App', 'finalize'); - $method->setAccessible(true); - - $response = new Response(); - $response->getBody()->write('foo'); - $response = $response->withHeader('Content-Type', 'text/plain'); - - $response = $method->invoke(new App(), $response); - - $this->assertTrue($response->hasHeader('Content-Type')); - } - - public function testFinalizeWithoutBody() - { - $method = new \ReflectionMethod('Slim\App', 'finalize'); - $method->setAccessible(true); - - $response = $method->invoke(new App(), new Response(304)); - - $this->assertFalse($response->hasHeader('Content-Length')); - $this->assertFalse($response->hasHeader('Content-Type')); + $this->expectOutputString('Hello World'); } // TODO: Re-add testUnsupportedMethodWithoutRoute @@ -1695,8 +1435,8 @@ public function testFinalizeWithoutBody() public function testContainerSetToRoute() { // Prepare request and response objects - $request = $this->requestFactory('/foo'); - $response = new Response(); + $request = $this->createServerRequest('/foo'); + $response = $this->createResponse(); $mock = new MockAction(); @@ -1705,10 +1445,11 @@ public function testContainerSetToRoute() return $mock; }; - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $app->setContainer(new Psr11Container($pimple)); - /** @var $router Router */ + /** @var Router $router */ $router = $app->getRouter(); $router->map(['get'], '/foo', 'foo:bar'); @@ -1719,42 +1460,10 @@ public function testContainerSetToRoute() $this->assertEquals(json_encode(['name'=>'bar', 'arguments' => []]), (string)$resOut->getBody()); } - public function testIsEmptyResponseWithEmptyMethod() - { - $method = new \ReflectionMethod('Slim\App', 'isEmptyResponse'); - $method->setAccessible(true); - - $response = new Response(); - $response = $response->withStatus(204); - - $result = $method->invoke(new App(), $response); - $this->assertTrue($result); - } - - public function testIsEmptyResponseWithoutEmptyMethod() - { - $method = new \ReflectionMethod('Slim\App', 'isEmptyResponse'); - $method->setAccessible(true); - - /** @var Response $response */ - $response = $this->getMockBuilder(ResponseInterface::class)->getMock(); - $response->method('getStatusCode') - ->willReturn(204); - - $result = $method->invoke(new App(), $response); - $this->assertTrue($result); - } - public function testAppIsARequestHandler() { - $app = new App; + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $this->assertInstanceof('Psr\Http\Server\RequestHandlerInterface', $app); } - - protected function skipIfPhp70() - { - if (version_compare(PHP_VERSION, '7.0', '>=')) { - $this->markTestSkipped("Test is for PHP 5.6 or lower"); - } - } } diff --git a/tests/Assets/HeaderFunctions.php b/tests/Assets/HeaderFunctions.php index 99e9218bd..3ab2be304 100644 --- a/tests/Assets/HeaderFunctions.php +++ b/tests/Assets/HeaderFunctions.php @@ -49,7 +49,7 @@ public static function reset() /** * Push a header on the stack * - * @param string[] $header + * @param array $header */ public static function push(array $header) { diff --git a/tests/CallableResolverTest.php b/tests/CallableResolverTest.php index 6ea1d5225..8976e3f02 100644 --- a/tests/CallableResolverTest.php +++ b/tests/CallableResolverTest.php @@ -8,14 +8,12 @@ */ namespace Slim\Tests; -use PHPUnit\Framework\TestCase; use Pimple\Container as Pimple; use Pimple\Psr11\Container; use Slim\CallableResolver; use Slim\Tests\Mocks\CallableTest; use Slim\Tests\Mocks\InvokableTest; use Slim\Tests\Mocks\RequestHandlerTest; -use Slim\Http\Request; class CallableResolverTest extends TestCase { @@ -120,11 +118,11 @@ public function testResolutionToAnInvokableClass() public function testResolutionToAPsrRequestHandlerClass() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); + $request = $this->createServerRequest('/', 'GET'); $resolver = new CallableResolver(); // No container injected $callable = $resolver->resolve(RequestHandlerTest::class); $callable($request); - $this->assertEquals(1, RequestHandlerTest::$CalledCount); + $this->assertEquals("1", RequestHandlerTest::$CalledCount); } /** diff --git a/tests/DeferredCallableTest.php b/tests/DeferredCallableTest.php index 0f9afb2a4..b4d88815e 100644 --- a/tests/DeferredCallableTest.php +++ b/tests/DeferredCallableTest.php @@ -8,7 +8,6 @@ */ namespace Slim\Tests; -use PHPUnit\Framework\TestCase; use Pimple\Container as Pimple; use Pimple\Psr11\Container; use Slim\CallableResolver; diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index 8700ee83d..c75df5562 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -3,7 +3,6 @@ use FastRoute\DataGenerator\GroupCountBased; use FastRoute\RouteCollector; -use PHPUnit\Framework\TestCase; use Slim\Dispatcher; use Slim\RoutingResults; diff --git a/tests/Error/AbstractErrorRendererTest.php b/tests/Error/AbstractErrorRendererTest.php index 03ea43c9e..8276559bb 100644 --- a/tests/Error/AbstractErrorRendererTest.php +++ b/tests/Error/AbstractErrorRendererTest.php @@ -8,14 +8,15 @@ */ namespace Slim\Tests\Error; -use PHPUnit\Framework\TestCase; +use Exception; +use ReflectionClass; +use RuntimeException; use Slim\Error\Renderers\HtmlErrorRenderer; use Slim\Error\Renderers\JsonErrorRenderer; use Slim\Error\Renderers\PlainTextErrorRenderer; use Slim\Error\Renderers\XmlErrorRenderer; -use Exception; -use ReflectionClass; -use RuntimeException; +use Slim\Tests\TestCase; +use stdClass; class AbstractErrorRendererTest extends TestCase { @@ -99,6 +100,8 @@ public function testXMLErrorRendererDisplaysErrorDetails() $exception = new Exception('Ooops...', 0, $previousException); $renderer = new XmlErrorRenderer(); + + /** @var stdClass $output */ $output = simplexml_load_string($renderer->render($exception, true)); $this->assertEquals($output->message[0], 'Ooops...'); diff --git a/tests/Exception/HttpExceptionTest.php b/tests/Exception/HttpExceptionTest.php index 1f7dccf06..ff9ec9a99 100644 --- a/tests/Exception/HttpExceptionTest.php +++ b/tests/Exception/HttpExceptionTest.php @@ -6,26 +6,26 @@ * @copyright Copyright (c) 2011-2018 Josh Lockhart * @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License) */ -namespace Slim\Tests\Http; +namespace Slim\Tests\Exception; -use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; -use Slim\Http\Request; +use Slim\Tests\TestCase; class HttpExceptionTest extends TestCase { public function testHttpExceptionRequestReponseGetterSetters() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); + $request = $this->createServerRequest('/'); $exception = new HttpNotFoundException($request); - $this->assertInstanceOf(Request::class, $exception->getRequest()); + $this->assertInstanceOf(ServerRequestInterface::class, $exception->getRequest()); } public function testHttpExceptionAttributeGettersSetters() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); + $request = $this->createServerRequest('/'); $exception = new HttpNotFoundException($request); $exception->setTitle('Title'); @@ -37,7 +37,7 @@ public function testHttpExceptionAttributeGettersSetters() public function testHttpNotAllowedExceptionGetAllowedMethods() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); + $request = $this->createServerRequest('/'); $exception = new HttpMethodNotAllowedException($request); $exception->setAllowedMethods(['GET']); diff --git a/tests/Handlers/ErrorHandlerTest.php b/tests/Handlers/ErrorHandlerTest.php index d3c38ac15..fdeb99318 100644 --- a/tests/Handlers/ErrorHandlerTest.php +++ b/tests/Handlers/ErrorHandlerTest.php @@ -8,24 +8,26 @@ */ namespace Slim\Tests\Handlers; -use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use ReflectionClass; use Slim\Error\Renderers\JsonErrorRenderer; use Slim\Error\Renderers\PlainTextErrorRenderer; use Slim\Error\Renderers\XmlErrorRenderer; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; use Slim\Handlers\ErrorHandler; -use Slim\Http\Request; -use Slim\Http\Response; use Slim\Tests\Mocks\MockCustomException; use Slim\Tests\Mocks\MockErrorRenderer; -use ReflectionClass; +use Slim\Tests\TestCase; class ErrorHandlerTest extends TestCase { public function testDetermineContentTypeMethodDoesNotThrowExceptionWhenPassedValidRenderer() { - $handler = $this->getMockBuilder(ErrorHandler::class)->getMock(); + $handler = $this + ->getMockBuilder(ErrorHandler::class) + ->disableOriginalConstructor() + ->getMock(); $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('renderer'); @@ -44,7 +46,10 @@ public function testDetermineContentTypeMethodDoesNotThrowExceptionWhenPassedVal */ public function testDetermineContentTypeMethodThrowsExceptionWhenPassedAnInvalidRenderer() { - $handler = $this->getMockBuilder(ErrorHandler::class)->getMock(); + $handler = $this + ->getMockBuilder(ErrorHandler::class) + ->disableOriginalConstructor() + ->getMock(); $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('renderer'); @@ -58,7 +63,10 @@ public function testDetermineContentTypeMethodThrowsExceptionWhenPassedAnInvalid public function testDetermineRenderer() { - $handler = $this->getMockBuilder(ErrorHandler::class)->getMock(); + $handler = $this + ->getMockBuilder(ErrorHandler::class) + ->disableOriginalConstructor() + ->getMock(); $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('contentType'); @@ -82,10 +90,17 @@ public function testDetermineRenderer() public function testDetermineStatusCode() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); - $handler = $this->getMockBuilder(ErrorHandler::class)->getMock(); + $request = $this->createServerRequest('/'); + $handler = $this + ->getMockBuilder(ErrorHandler::class) + ->disableOriginalConstructor() + ->getMock(); $class = new ReflectionClass(ErrorHandler::class); + $reflectionProperty = $class->getProperty('responseFactory'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($handler, $this->getResponseFactory()); + $reflectionProperty = $class->getProperty('exception'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($handler, new HttpNotFoundException($request)); @@ -104,10 +119,14 @@ public function testDetermineStatusCode() public function testHalfValidContentType() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); - $request->expects($this->any())->method('getHeaderLine')->will($this->returnValue('unknown/+json')); + $request = $this + ->createServerRequest('/', 'GET') + ->withHeader('Content-Type', 'unknown/json+'); - $handler = $this->getMockBuilder(ErrorHandler::class)->getMock(); + $handler = $this + ->getMockBuilder(ErrorHandler::class) + ->disableOriginalConstructor() + ->getMock(); $newTypes = [ 'application/xml', 'text/xml', @@ -116,6 +135,10 @@ public function testHalfValidContentType() $class = new ReflectionClass(ErrorHandler::class); + $reflectionProperty = $class->getProperty('responseFactory'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($handler, $this->getResponseFactory()); + $reflectionProperty = $class->getProperty('knownContentTypes'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($handler, $newTypes); @@ -134,18 +157,24 @@ public function testHalfValidContentType() */ public function testAcceptableMediaTypeIsNotFirstInList() { - $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); - $request->expects($this->any()) - ->method('getHeaderLine') - ->willReturn('text/plain,text/html'); + $request = $this + ->createServerRequest('/', 'GET') + ->withHeader('Content-Type', 'text/plain,text/html'); // provide access to the determineContentType() as it's a protected method $class = new ReflectionClass(ErrorHandler::class); $method = $class->getMethod('determineContentType'); $method->setAccessible(true); + $reflectionProperty = $class->getProperty('responseFactory'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($class, $this->getResponseFactory()); + // use a mock object here as ErrorHandler cannot be directly instantiated - $handler = $this->getMockBuilder(ErrorHandler::class)->getMock(); + $handler = $this + ->getMockBuilder(ErrorHandler::class) + ->disableOriginalConstructor() + ->getMock(); // call determineContentType() $return = $method->invoke($handler, $request); @@ -155,12 +184,12 @@ public function testAcceptableMediaTypeIsNotFirstInList() public function testOptions() { - $request = $this->getRequest('OPTIONS'); - $handler = new ErrorHandler(); + $request = $this->createServerRequest('/', 'OPTIONS'); + $handler = new ErrorHandler($this->getResponseFactory()); $exception = new HttpMethodNotAllowedException($request); $exception->setAllowedMethods(['POST', 'PUT']); - /** @var Response $res */ + /** @var ResponseInterface $res */ $res = $handler->__invoke($request, $exception, true, true, true); $this->assertSame(200, $res->getStatusCode()); @@ -170,12 +199,12 @@ public function testOptions() public function testWriteToErrorLog() { - $request = $this->getMockBuilder(Request::class) - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any())->method('getHeaderLine')->with('Accept')->willReturn('application/json'); + $request = $this + ->createServerRequest('/', 'GET') + ->withHeader('Accept', 'application/json'); $handler = $this->getMockBuilder(ErrorHandler::class) + ->setConstructorArgs(['responseFactory' => $this->getResponseFactory()]) ->setMethods(['writeToErrorLog', 'logError']) ->getMock(); @@ -186,16 +215,4 @@ public function testWriteToErrorLog() $handler->__invoke($request, $exception, true, true, true); } - - /** - * @param string $method - * @return \PHPUnit_Framework_MockObject_MockObject|\Slim\Http\Request - */ - protected function getRequest($method, $contentType = 'text/html') - { - $req = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); - $req->expects($this->once())->method('getMethod')->will($this->returnValue($method)); - $req->expects($this->any())->method('getHeaderLine')->will($this->returnValue($contentType)); - return $req; - } } diff --git a/tests/Middleware/ContentLengthMiddlewareTest.php b/tests/Middleware/ContentLengthMiddlewareTest.php index d2237c644..229c1de4c 100644 --- a/tests/Middleware/ContentLengthMiddlewareTest.php +++ b/tests/Middleware/ContentLengthMiddlewareTest.php @@ -8,33 +8,23 @@ */ namespace Slim\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Http\Body; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; use Slim\Middleware\ContentLengthMiddleware; +use Slim\Tests\TestCase; class ContentLengthMiddlewareTest extends TestCase { public function testAddsContentLenght() { - $mw = new ContentLengthMiddleware('append'); + $mw = new ContentLengthMiddleware(); - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response(); + $request = $this->createServerRequest('https://example.com:443/foo/bar?abc=123'); + $response = $this->createResponse(); - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $res->write('Body'); - return $res; + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Body'); + return $response; }; $newResponse = $mw($request, $response, $next); diff --git a/tests/Middleware/ErrorMiddlewareTest.php b/tests/Middleware/ErrorMiddlewareTest.php index ec1d2d933..9761d04b1 100644 --- a/tests/Middleware/ErrorMiddlewareTest.php +++ b/tests/Middleware/ErrorMiddlewareTest.php @@ -8,20 +8,17 @@ */ namespace Slim\Tests\Middleware; -use PHPUnit\Framework\TestCase; +use Closure; +use Error; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Slim\App; use Slim\Exception\HttpNotFoundException; use Slim\Handlers\ErrorHandler; -use Slim\Http\Body; -use Slim\Http\Environment; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; use Slim\Middleware\ErrorMiddleware; use Slim\Middleware\RoutingMiddleware; use Slim\Tests\Mocks\MockCustomException; -use Error; +use Slim\Tests\TestCase; /** * Class ErrorMiddlewareTest @@ -31,7 +28,8 @@ class ErrorMiddlewareTest extends TestCase { public function testSetErrorHandler() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); $mw = new RoutingMiddleware($app->getRouter()); @@ -39,39 +37,46 @@ public function testSetErrorHandler() $exception = HttpNotFoundException::class; $handler = function () { - return (new Response())->withJson('Oops..'); + $response = $this->createResponse(500); + $response->getBody()->write('Oops..'); + return $response; }; - $mw2 = new ErrorMiddleware($callableResolver, false, false, false); + Closure::bind($handler, $this); + + $mw2 = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw2->setErrorHandler($exception, $handler); $app->add($mw2); - $request = $this->requestFactory('/foo/baz/'); + $request = $this->createServerRequest('/foo/baz/'); $app->run($request); - $expectedOutput = json_encode('Oops..'); - $this->expectOutputString($expectedOutput); + $this->expectOutputString('Oops..'); } public function testSetDefaultErrorHandler() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); $mw = new RoutingMiddleware($app->getRouter()); $app->add($mw); $handler = function () { - return (new Response())->withJson('Oops..'); + $response = $this->createResponse(); + $response->getBody()->write('Oops..'); + return $response; }; - $mw2 = new ErrorMiddleware($callableResolver, false, false, false); + Closure::bind($handler, $this); + + $mw2 = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw2->setDefaultErrorHandler($handler); $app->add($mw2); - $request = $this->requestFactory('/foo/baz/'); + $request = $this->createServerRequest('/foo/baz/'); $app->run($request); - $expectedOutput = json_encode('Oops..'); - $this->expectOutputString($expectedOutput); + $this->expectOutputString('Oops..'); } /** @@ -79,31 +84,31 @@ public function testSetDefaultErrorHandler() */ public function testSetDefaultErrorHandlerThrowsException() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); - $mw = new ErrorMiddleware($callableResolver, false, false, false); + $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw->setDefaultErrorHandler('Uncallable'); $mw->getDefaultErrorHandler(); } public function testGetErrorHandlerWillReturnDefaultErrorHandlerForUnhandledExceptions() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); - $middleware = new ErrorMiddleware($callableResolver, false, false, false); + $middleware = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $exception = MockCustomException::class; $handler = $middleware->getErrorHandler($exception); $this->assertInstanceOf(ErrorHandler::class, $handler); } - /** - * @requires PHP 7.0 - */ public function testErrorHandlerHandlesThrowables() { - $app = new App(); + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); $mw2 = function () { @@ -111,46 +116,25 @@ public function testErrorHandlerHandlesThrowables() }; $app->add($mw2); - $handler = function ($req, $exception) { - return (new Response())->withJson($exception->getMessage()); + $handler = function (ServerRequestInterface $request, $exception) { + $response = $this->createResponse(); + $response->getBody()->write($exception->getMessage()); + return $response; }; - $mw = new ErrorMiddleware($callableResolver, false, false, false); + Closure::bind($handler, $this); + + $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); $mw->setDefaultErrorHandler($handler); $app->add($mw); - $app->get('/foo', function () { - return (new Response())->withJson('...'); + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('...'); + return $response; }); - $request = $this->requestFactory('/foo'); + $request = $this->createServerRequest('/foo'); $app->run($request); - $expectedOutput = json_encode('Oops..'); - $this->expectOutputString($expectedOutput); - } - - /** - * helper to create a request object - * @return Request - */ - private function requestFactory($requestUri, $method = 'GET', $data = []) - { - $defaults = [ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => $requestUri, - 'REQUEST_METHOD' => $method, - ]; - - $data = array_merge($defaults, $data); - - $env = Environment::mock($data); - $uri = Uri::createFromGlobals($env); - $headers = Headers::createFromGlobals($env); - $cookies = []; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - return $request; + $this->expectOutputString('Oops..'); } } diff --git a/tests/Middleware/MethodOverrideMiddlewareTest.php b/tests/Middleware/MethodOverrideMiddlewareTest.php index 5594b6bd1..74d7202dc 100644 --- a/tests/Middleware/MethodOverrideMiddlewareTest.php +++ b/tests/Middleware/MethodOverrideMiddlewareTest.php @@ -8,15 +8,11 @@ */ namespace Slim\Tests\Middleware; -use PHPUnit\Framework\TestCase; +use Closure; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\RequestBody; -use Slim\Http\Response; -use Slim\Http\Uri; use Slim\Middleware\MethodOverrideMiddleware; +use Slim\Tests\TestCase; /** * @covers \Slim\Middleware\MethodOverrideMiddleware @@ -27,19 +23,16 @@ public function testHeader() { $mw = new MethodOverrideMiddleware(); - $uri = new Uri('http', 'example.com'); - $headers = new Headers([ - 'HTTP_X_HTTP_METHOD_OVERRIDE' => 'PUT', - ]); - $request = new Request('GET', $uri, $headers, [], [], new RequestBody()); - $response = new Response(); - - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $this->assertEquals('PUT', $req->getMethod()); - - return $res; + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $this->assertEquals('PUT', $request->getMethod()); + return $response; }; - \Closure::bind($next, $this); + Closure::bind($next, $this); + + $request = $this + ->createServerRequest('/', 'POST') + ->withHeader('X-Http-Method-Override', 'PUT'); + $response = $this->createResponse(); $mw($request, $response, $next); } @@ -48,21 +41,16 @@ public function testBodyParam() { $mw = new MethodOverrideMiddleware(); - $uri = new Uri('http', 'example.com'); - $body = new RequestBody(); - $body->write('_METHOD=PUT'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded', - ]); - $request = new Request('POST', $uri, $headers, [], [], $body); - $response = new Response(); - - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $this->assertEquals('PUT', $req->getMethod()); - - return $res; + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $this->assertEquals('PUT', $request->getMethod()); + return $response; }; - \Closure::bind($next, $this); + Closure::bind($next, $this); + + $request = $this + ->createServerRequest('/', 'POST') + ->withParsedBody(['_METHOD' => 'PUT']); + $response = $this->createResponse(); $mw($request, $response, $next); } @@ -71,22 +59,17 @@ public function testHeaderPreferred() { $mw = new MethodOverrideMiddleware(); - $uri = new Uri('http', 'example.com'); - $body = new RequestBody(); - $body->write('_METHOD=PUT'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded', - 'HTTP_X_HTTP_METHOD_OVERRIDE' => 'DELETE', - ]); - $request = new Request('POST', $uri, $headers, [], [], $body); - $response = new Response(); - - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $this->assertEquals('DELETE', $req->getMethod()); - - return $res; + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $this->assertEquals('DELETE', $request->getMethod()); + return $response; }; - \Closure::bind($next, $this); + Closure::bind($next, $this); + + $request = $this + ->createServerRequest('/', 'POST') + ->withHeader('X-Http-Method-Override', 'DELETE') + ->withParsedBody((object) ['_METHOD' => 'PUT']); + $response = $this->createResponse(); $mw($request, $response, $next); } @@ -95,16 +78,14 @@ public function testNoOverride() { $mw = new MethodOverrideMiddleware(); - $uri = new Uri('http', 'example.com'); - $request = new Request('POST', $uri, new Headers(), [], [], new RequestBody()); - $response = new Response(); - - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $this->assertEquals('POST', $req->getMethod()); - - return $res; + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $this->assertEquals('POST', $request->getMethod()); + return $response; }; - \Closure::bind($next, $this); + Closure::bind($next, $this); + + $request = $this->createServerRequest('/', 'POST'); + $response = $this->createResponse(); $mw($request, $response, $next); } diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index b9b204c82..a7f6727d5 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -8,29 +8,22 @@ */ namespace Slim\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Http\Body; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; use Slim\Middleware\OutputBufferingMiddleware; +use Slim\Tests\TestCase; class OutputBufferingMiddlewareTest extends TestCase { public function testStyleDefaultValid() { - $mw = new OutputBufferingMiddleware(); - + $mw = new OutputBufferingMiddleware($this->getStreamFactory()); $this->assertAttributeEquals('append', 'style', $mw); } public function testStyleCustomValid() { - $mw = new OutputBufferingMiddleware('prepend'); - + $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); $this->assertAttributeEquals('prepend', 'style', $mw); } @@ -39,27 +32,22 @@ public function testStyleCustomValid() */ public function testStyleCustomInvalid() { - $mw = new OutputBufferingMiddleware('foo'); + $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'foo'); } public function testAppend() { - $mw = new OutputBufferingMiddleware('append'); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response(); + $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'append'); - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $res->write('Body'); + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Body'); echo 'Test'; - return $res; + return $response; }; + + $request = $this->createServerRequest('/', 'GET'); + $response = $this->createResponse(); $result = $mw($request, $response, $next); $this->assertEquals('BodyTest', $result->getBody()); @@ -67,22 +55,17 @@ public function testAppend() public function testPrepend() { - $mw = new OutputBufferingMiddleware('prepend'); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response(); + $mw = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); - $next = function (ServerRequestInterface $req, ResponseInterface $res) { - $res->write('Body'); + $next = function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Body'); echo 'Test'; - return $res; + return $response; }; + + $request = $this->createServerRequest('/', 'GET'); + $response = $this->createResponse(); $result = $mw($request, $response, $next); $this->assertEquals('TestBody', $result->getBody()); diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index d70654e3d..77037c3f5 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -10,17 +10,12 @@ use Closure; use FastRoute\Dispatcher; -use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\RoutingResults; -use Slim\Http\Body; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; use Slim\Middleware\RoutingMiddleware; use Slim\Router; +use Slim\Tests\TestCase; class RoutingMiddlewareTest extends TestCase { @@ -28,7 +23,6 @@ protected function getRouter() { $router = new Router(); $router->map(['GET'], '/hello/{name}', null); - return $router; } @@ -37,25 +31,22 @@ public function testRouteIsStoredOnSuccessfulMatch() $router = $this->getRouter(); $mw = new RoutingMiddleware($router); - $uri = Uri::createFromString('https://example.com:443/hello/foo'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], [], $body); - $response = new Response(); - - $next = function (ServerRequestInterface $req, ResponseInterface $res) { + $next = function (ServerRequestInterface $request, ResponseInterface $response) { // route is available - $route = $req->getAttribute('route'); + $route = $request->getAttribute('route'); $this->assertNotNull($route); $this->assertEquals('foo', $route->getArgument('name')); // routingResults is available - $routingResults = $req->getAttribute('routingResults'); + $routingResults = $request->getAttribute('routingResults'); $this->assertInstanceOf(RoutingResults::class, $routingResults); - return $res; + return $response; }; - Closure::bind($next, $this); // bind test class so we can test request object + Closure::bind($next, $this); - $result = $mw($request, $response, $next); + $request = $this->createServerRequest('https://example.com:443/hello/foo'); + $response = $this->createResponse(); + $mw($request, $response, $next); } /** @@ -66,24 +57,22 @@ public function testRouteIsNotStoredOnMethodNotAllowed() $router = $this->getRouter(); $mw = new RoutingMiddleware($router); - $uri = Uri::createFromString('https://example.com:443/hello/foo'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('POST', $uri, new Headers(), [], [], $body); - $response = new Response(); - - $next = function (ServerRequestInterface $req, ResponseInterface $res) { + $next = function (ServerRequestInterface $request, ResponseInterface $response) { // route is not available - $route = $req->getAttribute('route'); + $route = $request->getAttribute('route'); $this->assertNull($route); // routingResults is available - $routingResults = $req->getAttribute('routingResults'); + $routingResults = $request->getAttribute('routingResults'); $this->assertInstanceOf(RoutingResults::class, $routingResults); $this->assertEquals(Dispatcher::METHOD_NOT_ALLOWED, $routingResults->getRouteStatus()); - return $res; + return $response; }; - Closure::bind($next, $this); // bind test class so we can test request object - $result = $mw($request, $response, $next); + Closure::bind($next, $this); + + $request = $this->createServerRequest('https://example.com:443/hello/foo', 'POST'); + $response = $this->createResponse(); + $mw($request, $response, $next); } } diff --git a/tests/MiddlewareAwareTest.php b/tests/MiddlewareAwareTest.php index 7fcb3b6a0..02b681597 100644 --- a/tests/MiddlewareAwareTest.php +++ b/tests/MiddlewareAwareTest.php @@ -8,99 +8,68 @@ */ namespace Slim\Tests; -use PHPUnit\Framework\TestCase; -use ReflectionProperty; -use SebastianBergmann\GlobalState\RuntimeException; -use Slim\Http\Body; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Slim\Tests\Mocks\Stackable; class MiddlewareAwareTest extends TestCase { public function testSeedsMiddlewareStack() { - $stack = new Stackable; $bottom = null; - $stack->add(function ($req, $res, $next) use (&$bottom) { + $stack = new Stackable; + $stack->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { $bottom = $next; - return $res; + return $response; }); - $stack->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $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() { - // Build middleware stack $stack = new Stackable; - $stack->add(function ($req, $res, $next) { - $res->write('In1'); - $res = $next($req, $res); - $res->write('Out1'); - - return $res; - })->add(function ($req, $res, $next) { - $res->write('In2'); - $res = $next($req, $res); - $res->write('Out2'); - - return $res; + $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 - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); + $request = $this->createServerRequest('/'); + $response = $stack->callMiddlewareStack($request, $this->createResponse()); - // Response - $response = new Response(); - - // Invoke call stack - $res = $stack->callMiddlewareStack($request, $response); - - $this->assertEquals('In2In1CenterOut1Out2', (string)$res->getBody()); + $this->assertEquals('In2In1CenterOut1Out2', (string) $response->getBody()); } public function testMiddlewareStackWithAStatic() { // Build middleware stack $stack = new Stackable; - $stack->add('Slim\Tests\Mocks\StaticCallable::run') - ->add(function ($req, $res, $next) { - $res->write('In2'); - $res = $next($req, $res); - $res->write('Out2'); - - return $res; + $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 - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - // Response - $response = new Response(); - - // Invoke call stack - $res = $stack->callMiddlewareStack($request, $response); + $request = $this->createServerRequest('/'); + $response = $stack->callMiddlewareStack($request, $this->createResponse()); - $this->assertEquals('In2In1CenterOut1Out2', (string)$res->getBody()); + $this->assertEquals('In2In1CenterOut1Out2', (string) $response->getBody()); } /** @@ -108,28 +77,14 @@ public function testMiddlewareStackWithAStatic() */ public function testMiddlewareBadReturnValue() { - // Build middleware stack $stack = new Stackable; - $stack->add(function ($req, $res, $next) { - $res = $res->write('In1'); - $res = $next($req, $res); - $res = $res->write('Out1'); - - // NOTE: No return value here + $stack->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) { + // Return Nothing }); - // Request - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - // Response - $response = new Response(); + $request = $this->createServerRequest('/'); + $response = $stack->callMiddlewareStack($request, $this->createResponse()); - // Invoke call stack $stack->callMiddlewareStack($request, $response); } @@ -139,15 +94,15 @@ public function testAlternativeSeedMiddlewareStack() $stack->alternativeSeed(); $bottom = null; - $stack->add(function ($req, $res, $next) use (&$bottom) { + $stack->add(function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { $bottom = $next; - return $res; + return $response; }); - $stack->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + + $stack->callMiddlewareStack($request, $response); $this->assertSame([$stack, 'testMiddlewareKernel'], $bottom); } @@ -158,20 +113,19 @@ public function testAlternativeSeedMiddlewareStack() public function testAddMiddlewareWhileStackIsRunningThrowException() { $stack = new Stackable; - $stack->add(function ($req, $resp) use ($stack) { - $stack->add(function ($req, $resp) { - return $resp; + $stack->add(function () use ($stack) { + $stack->add(function () { }); - return $resp; }); - $stack->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + + $stack->callMiddlewareStack($request, $response); } /** - * @expectedException RuntimeException + * @expectedException \RuntimeException */ public function testSeedTwiceThrowException() { diff --git a/tests/Mocks/CallableTest.php b/tests/Mocks/CallableTest.php index 3e75a8f2f..997b56c65 100644 --- a/tests/Mocks/CallableTest.php +++ b/tests/Mocks/CallableTest.php @@ -8,7 +8,7 @@ */ namespace Slim\Tests\Mocks; -use Slim\Http\Response; +use Slim\Tests\Providers\PSR7ObjectProvider; /** * Mock object for Slim\Tests\CallableResolverTest @@ -28,6 +28,7 @@ public function toCall() { static::$CalledCount++; - return new Response(); + $psr7ObjectProvider = new PSR7ObjectProvider(); + return $psr7ObjectProvider->createResponse(); } } diff --git a/tests/Mocks/InvocationStrategyTest.php b/tests/Mocks/InvocationStrategyTest.php index a88e32ad5..80fba326c 100644 --- a/tests/Mocks/InvocationStrategyTest.php +++ b/tests/Mocks/InvocationStrategyTest.php @@ -33,7 +33,6 @@ public function __invoke( array $routeArguments ): ResponseInterface { static::$LastCalledFor = $callable; - return $response; } } diff --git a/tests/Mocks/MessageStub.php b/tests/Mocks/MessageStub.php deleted file mode 100644 index dcfc88c6a..000000000 --- a/tests/Mocks/MessageStub.php +++ /dev/null @@ -1,38 +0,0 @@ -write(json_encode(compact('name') + ['arguments' => $arguments[2]])); + $response = $arguments[1]; + $contents = json_encode(compact('name') + ['arguments' => $arguments[2]]); + $arguments[1]->getBody()->write($contents); - return $arguments[1]; + return $response; } } diff --git a/tests/Mocks/MockErrorHandler.php b/tests/Mocks/MockErrorHandler.php index 536f5a7fd..869900a53 100644 --- a/tests/Mocks/MockErrorHandler.php +++ b/tests/Mocks/MockErrorHandler.php @@ -8,11 +8,11 @@ */ namespace Slim\Tests\Mocks; -use Slim\Handlers\AbstractErrorHandler; +use Slim\Handlers\ErrorHandler; /** * Mock object for Slim\Tests\AppTest */ -class MockErrorHandler extends AbstractErrorHandler +class MockErrorHandler extends ErrorHandler { } diff --git a/tests/Mocks/MockStream.php b/tests/Mocks/MockStream.php new file mode 100644 index 000000000..e3670bcda --- /dev/null +++ b/tests/Mocks/MockStream.php @@ -0,0 +1,245 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true, + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, + ], + ]; + + /** + * MockStream constructor. + * @param string|resource $body + */ + public function __construct($body = '') + { + if (\is_string($body)) { + $resource = \fopen('php://temp', 'rw+'); + \fwrite($resource, $body); + $body = $resource; + } + + if ('resource' === \gettype($body)) { + $this->stream = $body; + $meta = \stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } else { + throw new \InvalidArgumentException( + 'First argument to Stream::create() must be a string, resource or StreamInterface.' + ); + } + } + + /** + * Closes the stream when the destructed. + */ + public function __destruct() + { + $this->close(); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + public function close(): void + { + if (isset($this->stream)) { + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if (null !== $this->size) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + \clearstatcache(true, $this->uri); + } + + $stats = \fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + + return $this->size; + } + + return null; + } + + public function tell(): int + { + if (false === $result = \ftell($this->stream)) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function eof(): bool + { + return !$this->stream || \feof($this->stream); + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function seek($offset, $whence = \SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } elseif (\fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException( + 'Unable to seek to stream position ' + .$offset.' with whence '.\var_export($whence, true) + ); + } + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function write($string): int + { + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + + if (false === $result = \fwrite($this->stream, $string)) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function read($length): string + { + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return \fread($this->stream, $length); + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Unable to read stream contents'); + } + + if (false === $contents = \stream_get_contents($this->stream)) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (null === $key) { + return \stream_get_meta_data($this->stream); + } + + $meta = \stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/tests/Mocks/RequestHandlerTest.php b/tests/Mocks/RequestHandlerTest.php index ec74e3d18..d24d73d8d 100644 --- a/tests/Mocks/RequestHandlerTest.php +++ b/tests/Mocks/RequestHandlerTest.php @@ -11,7 +11,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\Http\Response; +use Slim\Tests\Providers\PSR7ObjectProvider; /** * Mock object for Slim\Tests\CallableResolverTest @@ -31,9 +31,14 @@ public function handle(ServerRequestInterface $request) : ResponseInterface static::$strategy = $trace[1]['class']; } - $response = new Response(); - $response = $response->withHeader('Content-Type', 'text/plain'); - $response->write(static::$CalledCount); + $psr7ObjectProvider = new PSR7ObjectProvider(); + $responseFactory = $psr7ObjectProvider->getResponseFactory(); + + $response = $responseFactory + ->createResponse() + ->withHeader('Content-Type', 'text/plain'); + $calledCount = static::$CalledCount; + $response->getBody()->write("{$calledCount}"); return $response; } diff --git a/tests/Mocks/SmallChunksStream.php b/tests/Mocks/SmallChunksStream.php index fc4838ba3..1376ad7bf 100644 --- a/tests/Mocks/SmallChunksStream.php +++ b/tests/Mocks/SmallChunksStream.php @@ -9,6 +9,7 @@ namespace Slim\Tests\Mocks; +use Exception; use Psr\Http\Message\StreamInterface; /** @@ -31,7 +32,7 @@ public function __construct() public function __toString() { - throw new \Exception('not implemented'); + return str_repeat('.', self::SIZE); } public function close() @@ -40,7 +41,7 @@ public function close() public function detach() { - throw new \Exception('not implemented'); + throw new Exception('not implemented'); } public function eof() @@ -50,12 +51,12 @@ public function eof() public function getContents() { - throw new \Exception('not implemented'); + throw new Exception('not implemented'); } public function getMetadata($key = null) { - throw new \Exception('not implemented'); + throw new Exception('not implemented'); } public function getSize() @@ -88,17 +89,17 @@ public function read($length) public function rewind() { - throw new \Exception('not implemented'); + throw new Exception('not implemented'); } public function seek($offset, $whence = SEEK_SET) { - throw new \Exception('not implemented'); + throw new Exception('not implemented'); } public function tell() { - throw new \Exception('not implemented'); + throw new Exception('not implemented'); } public function write($string) diff --git a/tests/Mocks/Stackable.php b/tests/Mocks/Stackable.php index 6d9f98403..be542b59a 100644 --- a/tests/Mocks/Stackable.php +++ b/tests/Mocks/Stackable.php @@ -19,9 +19,10 @@ class Stackable { use MiddlewareAwareTrait; - public function __invoke(ServerRequestInterface $req, ResponseInterface $res) + public function __invoke(ServerRequestInterface $request, ResponseInterface $response) { - return $res->write('Center'); + $response->getBody()->write('Center'); + return $response; } public function alternativeSeed() @@ -29,9 +30,10 @@ public function alternativeSeed() $this->seedMiddlewareStack([$this, 'testMiddlewareKernel']); } - public function testMiddlewareKernel(ServerRequestInterface $req, ResponseInterface $res) + public function testMiddlewareKernel(ServerRequestInterface $request, ResponseInterface $response) { - return $res->write('hello from testMiddlewareKernel'); + $response->getBody()->write('hello from testMiddlewareKernel'); + return $response; } public function add($callable) diff --git a/tests/Mocks/StaticCallable.php b/tests/Mocks/StaticCallable.php index 158b64982..9381fcfb4 100644 --- a/tests/Mocks/StaticCallable.php +++ b/tests/Mocks/StaticCallable.php @@ -8,17 +8,19 @@ */ namespace Slim\Tests\Mocks; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + /** * Mock object for Slim\Tests\RouteTest */ class StaticCallable { - public static function run($req, $res, $next) + public static function run(ServerRequestInterface $request, ResponseInterface $response, $next) { - $res->write('In1'); - $res = $next($req, $res); - $res->write('Out1'); - - return $res; + $response->getBody()->write('In1'); + $response = $next($request, $response); + $response->getBody()->write('Out1'); + return $response; } } diff --git a/tests/Providers/PSR7ObjectProvider.php b/tests/Providers/PSR7ObjectProvider.php new file mode 100644 index 000000000..da7717224 --- /dev/null +++ b/tests/Providers/PSR7ObjectProvider.php @@ -0,0 +1,105 @@ + 'HTTP/1.1', + 'REQUEST_METHOD' => $method, + 'SCRIPT_NAME' => '/index.php', + 'REQUEST_URI' => '', + 'QUERY_STRING' => '', + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', + 'HTTP_USER_AGENT' => 'Slim Framework', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), + ], $data); + + return $this + ->getServerRequestFactory() + ->createServerRequest($method, $uri, $headers); + } + + /** + * @param int $statusCode + * @param string $reasonPhrase + * @return ResponseInterface + */ + public function createResponse(int $statusCode = 200, string $reasonPhrase = ''): ResponseInterface + { + return $this + ->getResponseFactory() + ->createResponse($statusCode, $reasonPhrase); + } + + /** + * @param string $contents + * @return StreamInterface + */ + public function createStream(string $contents = ''): StreamInterface + { + return $this + ->getStreamFactory() + ->createStream($contents); + } +} diff --git a/tests/Providers/PSR7ObjectProviderInterface.php b/tests/Providers/PSR7ObjectProviderInterface.php new file mode 100644 index 000000000..1cebcd090 --- /dev/null +++ b/tests/Providers/PSR7ObjectProviderInterface.php @@ -0,0 +1,59 @@ += 5.3. However, the Slim Framework itself requires only PHP >= 5.2. - -1. Install the latest version of PHPUnit -Visit http://www.phpunit.de/ for installation instructions. - -2. Run PHPUnit -From the filesystem directory that contains the `tests` directory, you may run all unit tests or specific unit tests. Here are several examples. The '$>' in the examples below is your command prompt. - -To run all tests: -$> phpunit tests - -To run all HTTP-related tests: -$> phpunit tests/Http - -To run only the HTTP Request tests: -$> phpunit tests/Http/RequestTest \ No newline at end of file diff --git a/tests/ResponseEmitterTest.php b/tests/ResponseEmitterTest.php new file mode 100644 index 000000000..be9f88423 --- /dev/null +++ b/tests/ResponseEmitterTest.php @@ -0,0 +1,192 @@ +createResponse(); + $response->getBody()->write('Hello'); + + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + + $this->expectOutputString('Hello'); + } + + public function testRespondNoContent() + { + $response = $this->createResponse(); + + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + + $this->assertEquals(false, HeaderStackTestAsset::has('Content-Type')); + $this->assertEquals(false, HeaderStackTestAsset::has('Content-Length')); + $this->expectOutputString(''); + } + + public function testRespondWithPaddedStreamFilterOutput() + { + $availableFilter = stream_get_filters(); + + if (version_compare(phpversion(), '7.0.0', '>=')) { + $filterName = 'string.rot13'; + $unfilterName = 'string.rot13'; + $specificFilterName = 'string.rot13'; + $specificUnfilterName = 'string.rot13'; + } else { + $filterName = 'mcrypt.*'; + $unfilterName = 'mdecrypt.*'; + $specificFilterName = 'mcrypt.rijndael-128'; + $specificUnfilterName = 'mdecrypt.rijndael-128'; + } + + if (in_array($filterName, $availableFilter) && in_array($unfilterName, $availableFilter)) { + $key = base64_decode('xxxxxxxxxxxxxxxx'); + $iv = base64_decode('Z6wNDk9LogWI4HYlRu0mng=='); + + $data = 'Hello'; + $length = strlen($data); + + $stream = fopen('php://temp', 'r+'); + $filter = stream_filter_append($stream, $specificFilterName, STREAM_FILTER_WRITE, [ + 'key' => $key, + 'iv' => $iv + ]); + + fwrite($stream, $data); + rewind($stream); + stream_filter_remove($filter); + stream_filter_append($stream, $specificUnfilterName, STREAM_FILTER_READ, [ + 'key' => $key, + 'iv' => $iv + ]); + + $body = $this->getStreamFactory()->createStreamFromResource($stream); + $response = $this + ->createResponse() + ->withHeader('Content-Length', $length) + ->withBody($body); + + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + + $this->expectOutputString('Hello'); + } else { + $this->assertTrue(true); + } + } + + public function testRespondIndeterminateLength() + { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, 'Hello'); + rewind($stream); + + $body = $this + ->getMockBuilder(MockStream::class) + ->setConstructorArgs([$stream]) + ->setMethods(['getSize']) + ->getMock(); + $body->method('getSize')->willReturn(null); + + $response = $this->createResponse()->withBody($body); + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + + $this->expectOutputString('Hello'); + } + + public function testResponseWithStreamReadYieldingLessBytesThanAsked() + { + $body = new SmallChunksStream(); + $response = $this->createResponse()->withBody($body); + + $responseEmitter = new ResponseEmitter($body::CHUNK_SIZE * 2); + $responseEmitter->emit($response); + + $this->expectOutputString(str_repeat('.', $body->getSize())); + } + + public function testResponseReplacesPreviouslySetHeaders() + { + $response = $this + ->createResponse(200, 'OK') + ->withHeader('X-Foo', 'baz1') + ->withAddedHeader('X-Foo', 'baz2'); + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + + $expectedStack = [ + ['header' => 'X-Foo: baz1', 'replace' => true, 'status_code' => null], + ['header' => 'X-Foo: baz2', 'replace' => false, 'status_code' => null], + ['header' => 'HTTP/1.1 200 OK', 'replace' => true, 'status_code' => 200], + ]; + + $this->assertSame($expectedStack, HeaderStackTestAsset::stack()); + } + + public function testResponseDoesNotReplacePreviouslySetSetCookieHeaders() + { + $response = $this + ->createResponse(200, 'OK') + ->withHeader('Set-Cookie', 'foo=bar') + ->withAddedHeader('Set-Cookie', 'bar=baz'); + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + + $expectedStack = [ + ['header' => 'Set-Cookie: foo=bar', 'replace' => false, 'status_code' => null], + ['header' => 'Set-Cookie: bar=baz', 'replace' => false, 'status_code' => null], + ['header' => 'HTTP/1.1 200 OK', 'replace' => true, 'status_code' => 200], + ]; + + $this->assertSame($expectedStack, HeaderStackTestAsset::stack()); + } + + public function testIsResponseEmptyWithNonEmptyBodyAndTriggeringStatusCode() + { + $body = $this->createStream('Hello'); + $response = $this + ->createResponse(204) + ->withBody($body); + $responseEmitter = new ResponseEmitter(); + + $this->assertTrue($responseEmitter->isResponseEmpty($response)); + } + + public function testIsResponseEmptyWithEmptyBody() + { + $response = $this->createResponse(200); + $responseEmitter = new ResponseEmitter(); + + $this->assertTrue($responseEmitter->isResponseEmpty($response)); + } +} diff --git a/tests/RouteTest.php b/tests/RouteTest.php index eba5524c1..4ababa981 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -8,22 +8,19 @@ */ namespace Slim\Tests; -use PHPUnit\Framework\TestCase; use Pimple\Container as Pimple; use Pimple\Psr11\Container as Psr11Container; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Slim\CallableResolver; use Slim\DeferredCallable; -use Slim\Http\Body; -use Slim\Http\Environment; -use Slim\Http\Headers; -use Slim\Http\Request; -use Slim\Http\Response; -use Slim\Http\Uri; +use Slim\Interfaces\InvocationStrategyInterface; use Slim\Route; use Slim\Tests\Mocks\CallableTest; use Slim\Tests\Mocks\InvocationStrategyTest; use Slim\Tests\Mocks\MiddlewareStub; use Slim\Tests\Mocks\RequestHandlerTest; +use Exception; class RouteTest extends TestCase { @@ -31,8 +28,8 @@ public function routeFactory() { $methods = ['GET', 'POST']; $pattern = '/hello/{name}'; - $callable = function ($req, $res, $args) { - return $res; + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + return $response; }; return new Route($methods, $pattern, $callable); @@ -99,17 +96,16 @@ public function testBottomMiddlewareIsRoute() { $route = $this->routeFactory(); $bottom = null; - $mw = function ($req, $res, $next) use (&$bottom) { + $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$bottom) { $bottom = $next; - return $res; + return $response; }; $route->add($mw); $route->finalize(); - $route->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $route->callMiddlewareStack($request, $response); $this->assertEquals($route, $bottom); } @@ -119,18 +115,17 @@ public function testAddMiddleware() $route = $this->routeFactory(); $called = 0; - $mw = function ($req, $res, $next) use (&$called) { + $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$called) { $called++; - return $res; + return $response; }; $route->add($mw); $route->finalize(); - $route->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $route->callMiddlewareStack($request, $response); $this->assertSame($called, 1); } @@ -140,9 +135,9 @@ public function testRefinalizing() $route = $this->routeFactory(); $called = 0; - $mw = function ($req, $res, $next) use (&$called) { + $mw = function (ServerRequestInterface $request, ResponseInterface $response, $next) use (&$called) { $called++; - return $res; + return $response; }; $route->add($mw); @@ -150,10 +145,9 @@ public function testRefinalizing() $route->finalize(); $route->finalize(); - $route->callMiddlewareStack( - $this->getMockBuilder('Psr\Http\Message\ServerRequestInterface')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Psr\Http\Message\ResponseInterface')->disableOriginalConstructor()->getMock() - ); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $route->callMiddlewareStack($request, $response); $this->assertSame($called, 1); } @@ -180,21 +174,11 @@ public function testAddMiddlewareAsStringResolvesWithoutContainer() $route->setCallableResolver($resolver); $route->add('MiddlewareStub:run'); - $env = Environment::mock(); - $uri = Uri::createFromString('https://example.com:80'); - $headers = new Headers(); - $cookies = [ - 'user' => 'john', - 'id' => '123', - ]; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - $response = new Response; + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); } public function testAddMiddlewareAsStringResolvesWithContainer() @@ -207,21 +191,11 @@ public function testAddMiddlewareAsStringResolvesWithContainer() $route->setCallableResolver($resolver); $route->add('MiddlewareStub:run'); - $env = Environment::mock(); - $uri = Uri::createFromString('https://example.com:80'); - $headers = new Headers(); - $cookies = [ - 'user' => 'john', - 'id' => '123', - ]; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - $response = new Response; + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); } public function testControllerMethodAsStringResolvesWithoutContainer() @@ -232,15 +206,13 @@ public function testControllerMethodAsStringResolvesWithoutContainer() $route = new Route(['GET'], '/', $deferred); $route->setCallableResolver($resolver); - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - CallableTest::$CalledCount = 0; - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertEquals(1, CallableTest::$CalledCount); } @@ -255,15 +227,13 @@ public function testControllerMethodAsStringResolvesWithContainer() $route = new Route(['GET'], '/', $deferred); $route->setCallableResolver($resolver); - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - CallableTest::$CalledCount = 0; - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertEquals(1, CallableTest::$CalledCount); } @@ -273,23 +243,18 @@ public function testControllerMethodAsStringResolvesWithContainer() */ public function testInvokeWhenReturningAResponse() { - $callable = function ($req, $res, $args) { - return $res->write('foo'); + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write('foo'); + return $response; }; $route = new Route(['GET'], '/', $callable); - $env = Environment::mock(); - $uri = Uri::createFromString('https://example.com:80'); - $headers = new Headers(); - $cookies = []; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response; + CallableTest::$CalledCount = 0; - $response = $route->__invoke($request, $response); + $request = $this->createServerRequest('/'); + $response = $route->__invoke($request, $this->createResponse()); - $this->assertEquals('foo', (string)$response->getBody()); + $this->assertEquals('foo', (string) $response->getBody()); } /** @@ -298,27 +263,21 @@ public function testInvokeWhenReturningAResponse() */ public function testInvokeWhenEchoingOutput() { - $callable = function ($req, $res, $args) { + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { echo "foo"; - return $res->withStatus(201); + return $response->withStatus(201); }; $route = new Route(['GET'], '/', $callable); - $env = Environment::mock(); - $uri = Uri::createFromString('https://example.com:80'); - $headers = new Headers(); - $cookies = []; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response; + $request = $this->createServerRequest('/'); // We capture output buffer here only to clean test CLI output ob_start(); - $response = $route->__invoke($request, $response); + $response = $route->__invoke($request, $this->createResponse()); ob_end_clean(); - $this->assertEquals('', (string)$response->getBody()); // Output buffer ignored without optional middleware + // Output buffer is ignored without optional middleware + $this->assertEquals('', (string) $response->getBody()); $this->assertEquals(201, $response->getStatusCode()); } @@ -328,24 +287,16 @@ public function testInvokeWhenEchoingOutput() */ public function testInvokeWhenReturningAString() { - $callable = function ($req, $res, $args) { - $res->write('foo'); - return $res; + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + $response->getBody()->write('foo'); + return $response; }; $route = new Route(['GET'], '/', $callable); - $env = Environment::mock(); - $uri = Uri::createFromString('https://example.com:80'); - $headers = new Headers(); - $cookies = []; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response; + $request = $this->createServerRequest('/'); + $response = $route->__invoke($request, $this->createResponse()); - $response = $route->__invoke($request, $response); - - $this->assertEquals('foo', (string)$response->getBody()); + $this->assertEquals('foo', (string) $response->getBody()); } /** @@ -353,22 +304,15 @@ public function testInvokeWhenReturningAString() */ public function testInvokeWithException() { - $callable = function ($req, $res, $args) { - throw new \Exception(); + $callable = function (ServerRequestInterface $request, ResponseInterface $response, $args) { + throw new Exception(); }; $route = new Route(['GET'], '/', $callable); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); - $env = Environment::mock(); - $uri = Uri::createFromString('https://example.com:80'); - $headers = new Headers(); - $cookies = []; - $serverParams = $env; - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - $response = new Response; - - $response = $route->__invoke($request, $response); + $route->__invoke($request, $response); } /** @@ -382,13 +326,11 @@ public function testInvokeDeferredCallableWithNoContainer() $route->setCallableResolver($resolver); $route->setInvocationStrategy(new InvocationStrategyTest()); - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertEquals([new CallableTest(), 'toCall'], InvocationStrategyTest::$LastCalledFor); } @@ -405,13 +347,11 @@ public function testInvokeDeferredCallableWithContainer() $route->setCallableResolver($resolver); $route->setInvocationStrategy(new InvocationStrategyTest()); - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertEquals([new CallableTest(), 'toCall'], InvocationStrategyTest::$LastCalledFor); } @@ -424,12 +364,11 @@ public function testInvokeUsesRequestHandlerStrategyForRequestHandlers() $route = new Route(['GET'], '/', RequestHandlerTest::class); $route->setCallableResolver($resolver); - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/', 'GET'); + $response = $this->createResponse(); + $route->callMiddlewareStack($request, $response); + /** @var InvocationStrategyInterface $strategy */ $strategy = $pimple[RequestHandlerTest::class]::$strategy; $this->assertEquals('Slim\Handlers\Strategies\RequestHandler', $strategy); } @@ -457,13 +396,11 @@ public function testChangingCallableWithNoContainer() $route->setCallable('\Slim\Tests\Mocks\CallableTest:toCall'); //Then we fix it here. - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertEquals([new CallableTest(), 'toCall'], InvocationStrategyTest::$LastCalledFor); } @@ -482,13 +419,11 @@ public function testChangingCallableWithContainer() $route->setCallable('CallableTest2:toCall'); //Then we fix it here. - $uri = Uri::createFromString('https://example.com:80'); - $body = new Body(fopen('php://temp', 'r+')); - $request = new Request('GET', $uri, new Headers(), [], Environment::mock(), $body); - - $result = $route->callMiddlewareStack($request, new Response); + $request = $this->createServerRequest('/'); + $response = $this->createResponse(); + $result = $route->callMiddlewareStack($request, $response); - $this->assertInstanceOf('Slim\Http\Response', $result); + $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertEquals([$pimple['CallableTest2'], 'toCall'], InvocationStrategyTest::$LastCalledFor); } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index f1decb94d..86706299d 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -8,8 +8,8 @@ */ namespace Slim\Tests; -use PHPUnit\Framework\TestCase; use Slim\Dispatcher; +use Slim\Route; use Slim\RoutingResults; use Slim\Router; @@ -274,9 +274,10 @@ public function testPathForWithModifiedRoutePattern() $callable = function ($request, $response, $args) { echo sprintf('Hello %s %s', $args['voornaam'], $args['achternaam']); }; + + /** @var Route $route */ $route = $this->router->map($methods, $pattern, $callable); $route->setName('foo'); - $route->setPattern('/hallo/{voornaam:\w+}/{achternaam}'); $this->assertEquals( @@ -398,7 +399,6 @@ public function testUrlForAliasesPathFor() $data = ['name' => 'josh']; $queryParams = ['a' => 'b', 'c' => 'd']; - //create a router that mocks the pathFor with expected args $router = $this->getMockBuilder(Router::class)->setMethods(['pathFor'])->getMock(); $router->expects($this->once())->method('pathFor')->with($name, $data, $queryParams); $router->urlFor($name, $data, $queryParams); diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 000000000..0f6bcba98 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,88 @@ +getServerRequestFactory(); + } + + /** + * @return ResponseFactoryInterface + */ + protected function getResponseFactory(): ResponseFactoryInterface + { + $psr7ObjectProvider = new PSR7ObjectProvider(); + return $psr7ObjectProvider->getResponseFactory(); + } + + /** + * @return StreamFactoryInterface + */ + protected function getStreamFactory(): StreamFactoryInterface + { + $psr7ObjectProvider = new PSR7ObjectProvider(); + return $psr7ObjectProvider->getStreamFactory(); + } + + /** + * @param string $uri + * @param string $method + * @param array $data + * @return ServerRequestInterface + */ + protected function createServerRequest( + string $uri, + string $method = 'GET', + array $data = [] + ): ServerRequestInterface { + $psr7ObjectProvider = new PSR7ObjectProvider(); + return $psr7ObjectProvider->createServerRequest($uri, $method, $data); + } + + /** + * @param int $statusCode + * @param string $reasonPhrase + * @return ResponseInterface + */ + protected function createResponse(int $statusCode = 200, string $reasonPhrase = ''): ResponseInterface + { + $psr7ObjectProvider = new PSR7ObjectProvider(); + return $psr7ObjectProvider->createResponse($statusCode, $reasonPhrase); + } + + /** + * @param string $contents + * @return StreamInterface + */ + protected function createStream(string $contents = ''): StreamInterface + { + $psr7ObjectProvider = new PSR7ObjectProvider(); + return $psr7ObjectProvider->createStream($contents); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 865e7e45c..26a796a46 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,18 +1,2 @@ addPsr4('Slim\Tests\\', __DIR__); +require __DIR__ . '/../vendor/autoload.php'; diff --git a/tests/getallheaders.php b/tests/getallheaders.php deleted file mode 100644 index b1b188e4c..000000000 --- a/tests/getallheaders.php +++ /dev/null @@ -1,7 +0,0 @@ - 'electrolytes']; - } -}