diff --git a/.travis.yml b/.travis.yml index a919868..f2ad2f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,12 @@ +cache: + directories: + - $HOME/.composer/cache/files + language: php php: - - 5.6 - - 7.0 - 7.1 + - 7.2 env: - DEPS=lowest @@ -11,7 +14,6 @@ env: before_script: - phpenv config-rm xdebug.ini - - composer self-update - if [[ $DEPS == 'lowest' ]]; then composer update --prefer-stable --no-interaction --prefer-lowest ; fi - if [[ $DEPS == 'latest' ]]; then composer update --prefer-stable --no-interaction ; fi diff --git a/README.md b/README.md index e903ebf..3abdfb7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # phpdebugbar middleware [![Build Status](https://travis-ci.org/php-middleware/phpdebugbar.svg?branch=master)](https://travis-ci.org/php-middleware/phpdebugbar) -PHP Debug bar PSR-15 middleware with PSR-7 +PHP Debug bar [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware with [PSR-7](https://www.php-fig.org/psr/psr-7/). Also supports [PSR-11](https://www.php-fig.org/psr/psr-11/) This middleware provide framework-agnostic possibility to attach [PHP Debug Bar](http://phpdebugbar.com/) to your response (html on non-html!). @@ -30,17 +30,13 @@ You don't need to copy any static assets from phpdebugbar vendor! ### How to install on Zend Expressive? -Use [mtymek/expressive-config-manager](https://github.com/mtymek/expressive-config-manager) and add -`PhpMiddleware\PhpDebugBar\ConfigProvider` class name: +You need to register ConfigProvider and pipe provided middleware: ```php -$configManager = new \Zend\Expressive\ConfigManager\ConfigManager([ - \PhpMiddleware\PhpDebugBar\ConfigProvider::class, - new \Zend\Expressive\ConfigManager\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'), -]); +$app->pipe(PhpDebugBarMiddleware::class); ``` -more [about config manager](https://zendframework.github.io/zend-expressive/cookbook/modular-layout/). +For more follow Zend Expressive [documentation](https://docs.zendframework.com/zend-expressive/v3/features/modular-applications/). ### How to install on Slim 3? diff --git a/composer.json b/composer.json index 2543e54..2eabc37 100644 --- a/composer.json +++ b/composer.json @@ -8,24 +8,25 @@ "middleware", "psr", "psr-7", - "psr-15" + "psr-15", + "psr-11" ], "require": { - "php": ">=5.6", - "http-interop/http-middleware": "^0.4.1", + "php": "^7.1", "maximebf/debugbar": "^1.4", - "php-middleware/double-pass-compatibility": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", "psr/container": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0.1", "zendframework/zend-diactoros": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^5.7.19 || ^6.1.3", + "phpunit/phpunit": "^7.1.2", "mikey179/vfsStream": "^1.6.4", "slim/slim": "^3.0", - "zendframework/zend-expressive": "^1.0 || ^2.0", - "zendframework/zend-expressive-fastroute": "^1.0 || ^2.0", - "zendframework/zend-servicemanager": "^3.3" + "zendframework/zend-expressive": "^3.0", + "zendframework/zend-expressive-fastroute": "^3.0.1", + "zendframework/zend-servicemanager": "^3.3.2" }, "autoload": { "psr-4": { diff --git a/src/ConfigCollectorFactory.php b/src/ConfigCollectorFactory.php index 75bd70d..faed230 100644 --- a/src/ConfigCollectorFactory.php +++ b/src/ConfigCollectorFactory.php @@ -1,4 +1,5 @@ get('config'); diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 328b837..018c386 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -1,16 +1,17 @@ has(DebugBar::class)) { $standardDebugBarFactory = new StandardDebugBarFactory(); diff --git a/src/PhpDebugBarMiddleware.php b/src/PhpDebugBarMiddleware.php index 7f580a5..c29defd 100644 --- a/src/PhpDebugBarMiddleware.php +++ b/src/PhpDebugBarMiddleware.php @@ -1,15 +1,15 @@ */ -class PhpDebugBarMiddleware implements MiddlewareInterface +final class PhpDebugBarMiddleware implements MiddlewareInterface { - use DoublePassCompatibilityTrait; - protected $debugBarRenderer; public function __construct(DebugBarRenderer $debugbarRenderer) @@ -35,13 +33,13 @@ public function __construct(DebugBarRenderer $debugbarRenderer) /** * @inheritDoc */ - public function process(ServerRequestInterface $request, DelegateInterface $delegate) + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if ($staticFile = $this->getStaticFile($request->getUri())) { return $staticFile; } - $response = $delegate->process($request); + $response = $handler->handle($request); if (!$this->isHtmlAccepted($request)) { return $response; @@ -53,6 +51,26 @@ public function process(ServerRequestInterface $request, DelegateInterface $dele return $this->prepareHtmlResponseWithDebugBar($response); } + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface + { + $handler = new class($next, $response) implements RequestHandlerInterface { + private $next; + private $response; + + public function __construct(callable $next, ResponseInterface $response) + { + $this->next = $next; + $this->response = $response; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return ($this->next)($request, $this->response); + } + }; + return $this->process($request, $handler); + } + /** * @return HtmlResponse */ diff --git a/src/PhpDebugBarMiddlewareFactory.php b/src/PhpDebugBarMiddlewareFactory.php index 8c44595..8135d75 100644 --- a/src/PhpDebugBarMiddlewareFactory.php +++ b/src/PhpDebugBarMiddlewareFactory.php @@ -1,4 +1,5 @@ has(JavascriptRenderer::class)) { $rendererFactory = new JavascriptRendererFactory(); diff --git a/src/ResponseInjector/AlwaysInjector.php b/src/ResponseInjector/AlwaysInjector.php new file mode 100644 index 0000000..1b84a42 --- /dev/null +++ b/src/ResponseInjector/AlwaysInjector.php @@ -0,0 +1,40 @@ +renderHead(); + $debugBarBody = $debugBarRenderer->render(); + + if ($this->isHtmlResponse($outResponse)) { + $body = $outResponse->getBody(); + if (! $body->eof() && $body->isSeekable()) { + $body->seek(0, SEEK_END); + } + $body->write($debugBarHead . $debugBarBody); + + return $outResponse; + } + + $outResponseBody = Serializer::toString($outResponse); + $template = '%s

DebugBar

Response:

%s
%s'; + $escapedOutResponseBody = htmlspecialchars($outResponseBody); + $result = sprintf($template, $debugBarHead, $escapedOutResponseBody, $debugBarBody); + + return new HtmlResponse($result); + } + + private function isHtmlResponse(ResponseInterface $response): bool + { + return $this->hasHeaderContains($response, 'Content-Type', 'text/html'); + } +} diff --git a/src/ResponseInjector/ResponseInjectorInterface.php b/src/ResponseInjector/ResponseInjectorInterface.php new file mode 100644 index 0000000..2c1910e --- /dev/null +++ b/src/ResponseInjector/ResponseInjectorInterface.php @@ -0,0 +1,15 @@ + + */ +interface ResponseInjectorInterface +{ + public function injectPhpDebugBar(ResponseInterface $response, JavascriptRenderer $debugBar): ResponseInterface; +} diff --git a/src/StandardDebugBarFactory.php b/src/StandardDebugBarFactory.php index b80f0e3..aedfb83 100644 --- a/src/StandardDebugBarFactory.php +++ b/src/StandardDebugBarFactory.php @@ -1,4 +1,5 @@ dispatchApplication([ 'REQUEST_URI' => '/hello', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'text/html', ], [ - '/hello' => function (ServerRequestInterface $request, ResponseInterface $response, $next) { + '/hello' => function (ServerRequestInterface $request) { + $response = new Response(); $response->getBody()->write('Hello!'); return $response; }, @@ -29,7 +32,7 @@ final public function testAppendJsIntoHtmlContent() $this->assertContains('"/phpdebugbar/debugbar.js"', $responseBody); } - final public function testGetStatics() + final public function testGetStatics(): void { $response = $this->dispatchApplication([ 'DOCUMENT_ROOT' => __DIR__, @@ -53,8 +56,5 @@ final public function testGetStatics() $this->assertContains('text/javascript', $contentType); } - /** - * @return ResponseInterface - */ - abstract protected function dispatchApplication(array $server, array $pipe = []); + abstract protected function dispatchApplication(array $server, array $pipe = []): ResponseInterface; } diff --git a/test/PhpDebugBarMiddlewareFactoryTest.php b/test/PhpDebugBarMiddlewareFactoryTest.php index 32bd43b..0082589 100644 --- a/test/PhpDebugBarMiddlewareFactoryTest.php +++ b/test/PhpDebugBarMiddlewareFactoryTest.php @@ -1,4 +1,5 @@ middleware = new PhpDebugBarMiddleware($this->debugbarRenderer); } - public function testNotAttachIfNotAccept() + public function testTwoPassCallingForCompatibility(): void { $request = new ServerRequest(); $response = new Response(); @@ -42,92 +43,89 @@ public function testNotAttachIfNotAccept() $this->assertSame($response, $result); } - public function testAttachToNoneHtmlResponse() + public function testNotAttachIfNotAccept(): void + { + $request = new ServerRequest(); + $response = new Response(); + $requestHandler = new RequestHandlerStub($response); + + $result = $this->middleware->process($request, $requestHandler); + + $this->assertTrue($requestHandler->isCalled(), 'Request handler is not called'); + $this->assertSame($response, $result); + } + + public function testAttachToNoneHtmlResponse(): void { $request = new ServerRequest([], [], null, null, 'php://input', ['Accept' => 'text/html']); $response = new Response(); $response->getBody()->write('ResponseBody'); - $calledOut = false; - $outFunction = function ($request, $response) use (&$calledOut) { - $calledOut = true; - return $response; - }; + + $requestHandler = new RequestHandlerStub($response); $this->debugbarRenderer->expects($this->once())->method('renderHead')->willReturn('RenderHead'); $this->debugbarRenderer->expects($this->once())->method('render')->willReturn('RenderBody'); - $result = call_user_func($this->middleware, $request, $response, $outFunction); + $result = $this->middleware->process($request, $requestHandler); - $this->assertTrue($calledOut, 'Out is not called'); + $this->assertTrue($requestHandler->isCalled(), 'Request handler is not called'); $this->assertNotSame($response, $result); $this->assertSame("RenderHead

DebugBar

Response:

HTTP/1.1 200 OK\r\n\r\nResponseBody
RenderBody", (string) $result->getBody()); } - public function testAttachToHtmlResponse() + public function testAttachToHtmlResponse(): void { $request = new ServerRequest([], [], null, null, 'php://input', ['Accept' => 'text/html']); $response = new Response('php://memory', 200, ['Content-Type' => 'text/html']); $response->getBody()->write('ResponseBody'); - $calledOut = false; - $outFunction = function ($request, $response) use (&$calledOut) { - $calledOut = true; - return $response; - }; + $requestHandler = new RequestHandlerStub($response); $this->debugbarRenderer->expects($this->once())->method('renderHead')->willReturn('RenderHead'); $this->debugbarRenderer->expects($this->once())->method('render')->willReturn('RenderBody'); - $result = call_user_func($this->middleware, $request, $response, $outFunction); + $result = $this->middleware->process($request, $requestHandler); - $this->assertTrue($calledOut, 'Out is not called'); + $this->assertTrue($requestHandler->isCalled(), 'Request handler is not called'); $this->assertSame($response, $result); $this->assertSame("ResponseBodyRenderHeadRenderBody", (string) $result->getBody()); } - public function testAppendsToEndOfHtmlResponse() + public function testAppendsToEndOfHtmlResponse(): void { $html = 'FooContent'; $request = new ServerRequest([], [], null, null, 'php://input', ['Accept' => 'text/html']); $response = new Response\HtmlResponse($html); - $calledOut = false; - $outFunction = function ($request, $response) use (&$calledOut) { - $calledOut = true; - return $response; - }; + $requestHandler = new RequestHandlerStub($response); $this->debugbarRenderer->expects($this->once())->method('renderHead')->willReturn('RenderHead'); $this->debugbarRenderer->expects($this->once())->method('render')->willReturn('RenderBody'); - $result = call_user_func($this->middleware, $request, $response, $outFunction); + $result = $this->middleware->process($request, $requestHandler); - $this->assertTrue($calledOut, 'Out is not called'); + $this->assertTrue($requestHandler->isCalled(), 'Request handler is not called'); $this->assertSame($response, $result); $this->assertSame($html . 'RenderHeadRenderBody', (string) $result->getBody()); } - public function testTryToHandleNotExistingStaticFile() + public function testTryToHandleNotExistingStaticFile(): void { $this->debugbarRenderer->expects($this->any())->method('getBaseUrl')->willReturn('/phpdebugbar'); $uri = new Uri('http://example.com/phpdebugbar/boo.css'); $request = new ServerRequest([], [], $uri, null, 'php://memory'); $response = new Response\HtmlResponse(''); + $requestHandler = new RequestHandlerStub($response); - $calledOut = false; - $outFunction = function ($request, $response) use (&$calledOut) { - $calledOut = true; - return $response; - }; + $result = $this->middleware->process($request, $requestHandler); - $result = call_user_func($this->middleware, $request, $response, $outFunction); - $this->assertTrue($calledOut, 'Out is not called'); + $this->assertTrue($requestHandler->isCalled(), 'Request handler is not called'); $this->assertSame($response, $result); } /** * @dataProvider getContentTypes */ - public function testHandleStaticFile($extension, $contentType) + public function testHandleStaticFile(string $extension, string $contentType): void { $root = vfsStream::setup('boo'); @@ -140,20 +138,17 @@ public function testHandleStaticFile($extension, $contentType) vfsStream::newFile(sprintf('debugbar.%s', $extension))->withContent('filecontent')->at($root); - $calledOut = false; - $outFunction = function ($request, $response) use (&$calledOut) { - $calledOut = true; - return $response; - }; + $requestHandler = new RequestHandlerStub($response); - $result = call_user_func($this->middleware, $request, $response, $outFunction); - $this->assertFalse($calledOut, 'Out is called'); + $result = $this->middleware->process($request, $requestHandler); + + $this->assertFalse($requestHandler->isCalled(), 'Request handler is called'); $this->assertNotSame($response, $result); $this->assertSame($contentType, $result->getHeaderLine('Content-type')); $this->assertSame('filecontent', (string) $result->getBody()); } - public function getContentTypes() + public function getContentTypes(): array { return [ ['css', 'text/css'], diff --git a/test/RequestHandlerStub.php b/test/RequestHandlerStub.php new file mode 100644 index 0000000..2fb41da --- /dev/null +++ b/test/RequestHandlerStub.php @@ -0,0 +1,32 @@ +response = $response; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $this->called = true; + + return $this->response; + } + + public function isCalled(): bool + { + return $this->called; + } +} \ No newline at end of file diff --git a/test/Slim3Test.php b/test/Slim3Test.php index fc67b7e..26959b0 100644 --- a/test/Slim3Test.php +++ b/test/Slim3Test.php @@ -1,14 +1,16 @@ getContainer()['environment'] = function() use ($server) { diff --git a/test/TestEmitter.php b/test/TestEmitter.php index 6bc5b18..7fe122a 100644 --- a/test/TestEmitter.php +++ b/test/TestEmitter.php @@ -1,23 +1,24 @@ response = $response; - return $response; + return true; } - public function getResponse() + public function getResponse(): ResponseInterface { if ($this->response instanceof ResponseInterface) { return $this->response; diff --git a/test/ZendExpressiveTest.php b/test/ZendExpressiveTest.php index 7456ed9..52bdaea 100644 --- a/test/ZendExpressiveTest.php +++ b/test/ZendExpressiveTest.php @@ -1,26 +1,50 @@ dispatchApplication([ 'REQUEST_URI' => '/hello', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'text/html', ], [ - '/hello' => function (ServerRequestInterface $request, ResponseInterface $response, $next) { + '/hello' => function (ServerRequestInterface $request) { + $response = new Response(); $response->getBody()->write('Hello!'); return $response; }, @@ -31,38 +55,51 @@ final public function testContainsConfigCollectorOutput() $this->assertContains('DebugBar\\\DataCollector\\\ConfigCollector', $responseBody); } - protected function dispatchApplication(array $server, array $pipe = []) + protected function dispatchApplication(array $server, array $pipe = []): ResponseInterface { - $container = $this->createContainer(); + $container = $this->createContainer($server); $appFactory = new ApplicationFactory(); $app = $appFactory($container); + $app->pipe(RouteMiddleware::class); + + $app->pipe(PhpDebugBarMiddleware::class); + + $app->pipe(DispatchMiddleware::class); + foreach ($pipe as $pattern => $middleware) { $app->get($pattern, $middleware); } - $app->pipeRoutingMiddleware(); - $app->pipeDispatchMiddleware(); - - $serverRequest = ServerRequestFactory::fromGlobals($server); - - $app->run($serverRequest); + $app->run(); return $container->get(EmitterInterface::class)->getResponse(); } - /** - * - * @return ContainerInterface - */ - private function createContainer() + private function createContainer(array $server): ContainerInterface { $config = ConfigProvider::getConfig(); + $config['debug'] = true; $serviceManagerConfig = $config['dependencies']; $serviceManagerConfig['services']['config'] = $config; $serviceManagerConfig['services'][EmitterInterface::class] = new TestEmitter(); + $serviceManagerConfig['services'][ServerRequestInterface::class] = function() use ($server) { + return ServerRequestFactory::fromGlobals($server, [], [], [], []); + }; + $serviceManagerConfig['factories'][MiddlewareFactory::class] = MiddlewareFactoryFactory::class; + $serviceManagerConfig['factories'][MiddlewareContainer::class] = MiddlewareContainerFactory::class; + $serviceManagerConfig['factories'][MiddlewarePipe::class] = InvokableFactory::class; + $serviceManagerConfig['factories'][RouteCollector::class] = RouteCollectorFactory::class; + $serviceManagerConfig['factories'][FastRouteRouter::class] = FastRouteRouterFactory::class; + $serviceManagerConfig['factories'][RequestHandlerRunner::class] = RequestHandlerRunnerFactory::class; + $serviceManagerConfig['factories'][ServerRequestErrorResponseGenerator::class] = ServerRequestErrorResponseGeneratorFactory::class; + $serviceManagerConfig['factories'][ResponseInterface::class] = ResponseFactoryFactory::class; + $serviceManagerConfig['factories'][RouteMiddleware::class] = RouteMiddlewareFactory::class; + $serviceManagerConfig['factories'][DispatchMiddleware::class] = DispatchMiddlewareFactory::class; + $serviceManagerConfig['aliases'][RouterInterface::class] = FastRouteRouter::class; + $serviceManagerConfig['aliases'][\Zend\Expressive\ApplicationPipeline::class] = MiddlewarePipe::class; return new ServiceManager($serviceManagerConfig); }