Skip to content

Commit

Permalink
Merge pull request #2 from noglitchyo/feature/request_handler_as_midd…
Browse files Browse the repository at this point in the history
…leware

Added MiddlewareInterface to the request handler so it can be used ei…
  • Loading branch information
noglitchyo authored Jul 12, 2019
2 parents 1fd8b68 + 7c5225f commit 8270c12
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 25 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -32,23 +34,31 @@ It comes with a set of middleware collections using different strategy on how to

#### 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.

##### From the constructor

`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`*** : [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php)
- ***`$defaultRequestHandler = null`*** : [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php)

***The default request handler MUST provide a default response if none of the middlewares created one.***
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

Expand Down Expand Up @@ -86,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 */);

Expand Down
31 changes: 25 additions & 6 deletions src/RequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}

Expand All @@ -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
Expand All @@ -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);
}
Expand All @@ -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);
}
}
82 changes: 75 additions & 7 deletions tests/RequestHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

namespace NoGlitchYo\MiddlewareCollectionRequestHandler\Tests;

use LogicException;
use NoGlitchYo\MiddlewareCollectionRequestHandler\MiddlewareCollectionInterface;
use NoGlitchYo\MiddlewareCollectionRequestHandler\RequestHandler;
use Nyholm\Psr7\Response;
Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand Down Expand Up @@ -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;
Expand All @@ -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));
}
}

0 comments on commit 8270c12

Please sign in to comment.