From c60e68da04a149fe0f66d7ce7331a94ef72b3239 Mon Sep 17 00:00:00 2001 From: Maxime Elomari <764791+noglitchyo@users.noreply.github.com> Date: Fri, 12 Jul 2019 11:03:41 +0200 Subject: [PATCH 1/2] Added MiddlewareInterface to the request handler so it can be used either as RequestHandler or as middleware. Introduce BC break --- README.md | 8 ++-- src/RequestHandler.php | 31 +++++++++++--- tests/RequestHandlerTest.php | 82 +++++++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 713ef81..99e0f59 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ Lightweight & dead simple PSR-15 Server Request Handler implementation to proces ### Description -PSR-7 Request Handler implementing the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) -and able to manage a collection of Middlewares implementing the [MiddlewareInterface](https://github.com/php-fig/http-server-middleware/blob/master/src/MiddlewareInterface.php). +PSR-15 request handler implementing both [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) and [MiddlewareInterface](https://github.com/php-fig/http-server-middleware/blob/master/src/MiddlewareInterface.php) +able to manage a collection of middlewares implementing the [MiddlewareInterface](https://github.com/php-fig/http-server-middleware/blob/master/src/MiddlewareInterface.php). -It comes with a set of middleware collections using different strategy on how to provide the middlewares to the RequestHandler, and also provide a dead simple collection interface to implement in a glimpse your own strategy. +It can be used either as a RequestHandler or as a Middleware to fit into any implementation. + +Comes with a set of middleware collections using different strategy (LIFO, FIFO...) on how the middlewares from the collection are provided to the RequestHandler, and also provides a simple MiddlewareCollectionInterface to implement in a glimpse your own strategy. ### Goals diff --git a/src/RequestHandler.php b/src/RequestHandler.php index b5e0839..c1d0122 100644 --- a/src/RequestHandler.php +++ b/src/RequestHandler.php @@ -26,11 +26,13 @@ namespace NoGlitchYo\MiddlewareCollectionRequestHandler; +use LogicException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -class RequestHandler implements RequestHandlerInterface +class RequestHandler implements RequestHandlerInterface, MiddlewareInterface { use RequestHandlerTrait; @@ -40,15 +42,15 @@ class RequestHandler implements RequestHandlerInterface private $middlewareCollection; /** - * @var RequestHandlerInterface + * @var RequestHandlerInterface|null */ private $defaultRequestHandler; public function __construct( - RequestHandlerInterface $defaultRequestHandler, - MiddlewareCollectionInterface $middlewareCollection + MiddlewareCollectionInterface $middlewareCollection, + RequestHandlerInterface $defaultRequestHandler = null ) { - $this->middlewareCollection = $middlewareCollection; + $this->middlewareCollection = $middlewareCollection; $this->defaultRequestHandler = $defaultRequestHandler; } @@ -57,7 +59,7 @@ public static function fromCallable( MiddlewareCollectionInterface $middlewareCollection ): self { $defaultRequestHandler = static::createRequestHandlerFromCallable($callable); - return new static($defaultRequestHandler, $middlewareCollection); + return new static($middlewareCollection, $defaultRequestHandler); } public function __invoke(ServerRequestInterface $serverRequest): ResponseInterface @@ -67,6 +69,12 @@ public function __invoke(ServerRequestInterface $serverRequest): ResponseInterfa public function handle(ServerRequestInterface $request): ResponseInterface { + if ($this->defaultRequestHandler === null) { + throw new LogicException( + 'A default request handler must be defined if RequestHandler is used as a RequestHandler.' + ); + } + if ($this->middlewareCollection->isEmpty()) { return $this->defaultRequestHandler->handle($request); } @@ -75,4 +83,15 @@ public function handle(ServerRequestInterface $request): ResponseInterface return $nextMiddleware->process($request, $this); } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($this->middlewareCollection->isEmpty()) { + return $handler->handle($request); + } + + $nextMiddleware = $this->middlewareCollection->next(); + + return $nextMiddleware->process($request, $this); + } } diff --git a/tests/RequestHandlerTest.php b/tests/RequestHandlerTest.php index a41d46c..acb8a52 100644 --- a/tests/RequestHandlerTest.php +++ b/tests/RequestHandlerTest.php @@ -25,6 +25,7 @@ namespace NoGlitchYo\MiddlewareCollectionRequestHandler\Tests; +use LogicException; use NoGlitchYo\MiddlewareCollectionRequestHandler\MiddlewareCollectionInterface; use NoGlitchYo\MiddlewareCollectionRequestHandler\RequestHandler; use Nyholm\Psr7\Response; @@ -59,15 +60,15 @@ class RequestHandlerTest extends TestCase protected function setUp(): void { - $this->requestHandlerMock = $this->createMock(RequestHandlerInterface::class); + $this->requestHandlerMock = $this->createMock(RequestHandlerInterface::class); $this->middlewareCollectionMock = $this->createMock(MiddlewareCollectionInterface::class); - $this->sut = new RequestHandler($this->requestHandlerMock, $this->middlewareCollectionMock); + $this->sut = new RequestHandler($this->middlewareCollectionMock, $this->requestHandlerMock); } public function testHandleDelegateToDefaultHandlerIfNoMiddleware() { - $request = new ServerRequest('GET', '/test'); + $request = new ServerRequest('GET', '/test'); $response = new Response(404); $this->middlewareCollectionMock->method('isEmpty') @@ -87,7 +88,7 @@ function (ServerRequestInterface $request) use ($response) { public function testIsCallable() { - $request = new ServerRequest('GET', '/test'); + $request = new ServerRequest('GET', '/test'); $response = new Response(404); $this->middlewareCollectionMock->method('isEmpty') @@ -136,15 +137,29 @@ public function testHandleCallNextMiddlewareWithInstanceOfThisAsHandler() ->method('next') ->willReturnOnConsecutiveCalls( self::getMiddleware(false), - self::getMiddleware(true), - ); + self::getMiddleware(true) + ); $this->assertInstanceOf(ResponseInterface::class, $this->sut->handle($request)); } - public function testFromCallableCreateDefaultRequestHandlerFromCallable() + public function testHandleThrowExceptionIfDefaultRequestHandlerIsNull() { $request = new ServerRequest('GET', '/test'); + + $this->middlewareCollectionMock = $this->createMock(MiddlewareCollectionInterface::class); + + $sut = new RequestHandler($this->middlewareCollectionMock); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('A default request handler must be defined if RequestHandler is used as a RequestHandler.'); + + $sut->handle($request); + } + + public function testFromCallableCreateDefaultRequestHandlerFromCallable() + { + $request = new ServerRequest('GET', '/test'); $response = new Response(405); $callable = function () use ($response) { return $response; @@ -158,5 +173,58 @@ public function testFromCallableCreateDefaultRequestHandlerFromCallable() $this->assertSame($response, $sut->handle($request)); } + public function testProcessCallNextMiddleware() + { + $request = new ServerRequest('GET', '/test'); + + $this->middlewareCollectionMock + ->expects($this->once()) + ->method('isEmpty') + ->willReturn(false); + $this->middlewareCollectionMock + ->expects($this->once()) + ->method('next') + ->willReturn(self::getMiddleware(true)); + + $handler = new class implements RequestHandlerInterface + { + public function handle(ServerRequestInterface $request): ResponseInterface + { + return new Response(); + } + }; + + $this->assertInstanceOf(ResponseInterface::class, $this->sut->process($request, $handler)); + } + + + public function testProcessReturnAndDelegateToHandlerIfNoMiddleware() + { + $request = new ServerRequest('GET', '/test'); + $response = new Response(404); + + $this->middlewareCollectionMock->method('isEmpty') + ->willReturn(true); + + $handler = new class($response) implements RequestHandlerInterface + { + /** + * @var ResponseInterface + */ + private $response; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $this->assertSame($response, $this->sut->process($request, $handler)); + } } From 7c5225f577f8bde862f52c983bd012652804b823 Mon Sep 17 00:00:00 2001 From: Maxime Elomari <764791+noglitchyo@users.noreply.github.com> Date: Fri, 12 Jul 2019 21:09:54 +0200 Subject: [PATCH 2/2] Added MiddlewareInterface to the request handler so it can be used either as RequestHandler or as middleware. Introduce BC break --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 27 ++++++++++++++++++--------- 2 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8b83cc7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - 2019-07-12 +### Added +- RequestHandler supports MiddlewareInterface to be use as a middleware as well + +### Changed +- Constructor arguments order for RequestHandler +- Default handler is now optional + +## [1.1.0] - 2019-06-26 +### Added +- Handful factory methods +- Updated documentation + +## [1.0.0] - 2019-06-23 +- Initial release diff --git a/README.md b/README.md index 99e0f59..b36ca2e 100644 --- a/README.md +++ b/README.md @@ -34,23 +34,31 @@ Comes with a set of middleware collections using different strategy (LIFO, FIFO. #### Run -Instantiate the RequestHandler class. It requires ony 2 arguments: +Create a new instance of the request handler class which can be use as a request handler or as a middleware. -- ***`$defaultRequestHandler`*** : [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) +##### From the constructor - ***The default request handler MUST provide a default response if none of the middlewares created one.*** +`RequestHandler::__construct(MiddlewareCollectionInterface $middlewareCollection, RequestHandlerInterface $defaultRequestHandler = null)` + +- ***`$middlewareCollection`*** : [MiddlewareCollectionInterface](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/MiddlewareCollectionInterface.php) + + Contains the middlewares and defines the strategy used to store the middlewares and to retrieve the next middleware. + Some implementations with common strategies are provided: [stack (LIFO)](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/Collection/SplStackMiddlewareCollection.php), [queue (FIFO)](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/Collection/SplQueueMiddlewareCollection.php). + +- ***`$defaultRequestHandler = null`*** : [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php) + + Provides a default response implementing [ResponseInterface](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php) if none of the middlewares in the collection was able to create one. Some examples of what could be a "default request handler": - with the [ADR pattern](https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder), the default request handler might be your action class.* - with the [MVC pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller), the default request handler might be the action method of your controller. - It is possible to directly provide a `callable` and use the factory method `RequestHandler::fromCallable(callable $callable)`. - It will create an anonymous instance of RequestHandlerInterface wrapping the given `callable` inside. - -- ***`$middlewareCollection`*** : [MiddlewareCollectionInterface](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/MiddlewareCollectionInterface.php) +##### From the factory method - Contains the middlewares and defines the strategy used to store the middlewares and to retrieve the next middleware. - Some implementations with common strategies are provided: [stack (LIFO)](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/Collection/SplStackMiddlewareCollection.php), [queue (FIFO)](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/Collection/SplQueueMiddlewareCollection.php). +`RequestHandler::fromCallable(callable $callable, MiddlewareCollectionInterface $middlewareCollection)` + +It creates a RequestHandler instance by wrapping the given `callable` inside an anonymous instance of RequestHandlerInterface. +The callable is the $defaultRequestHandler. **It MUST returns a response implementing [ResponseInterface](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php).** ##### Example @@ -88,6 +96,7 @@ $requestHandler = RequestHandler::fromCallable( $middlewareCollection ); +// As a RequestHandler: // Pass the request to the request handler which will dispatch the request to the middlewares. $response = $requestHandler->handle(/* ServerRequestInterface */);