Skip to content

Commit 8270c12

Browse files
authored
Merge pull request #2 from noglitchyo/feature/request_handler_as_middleware
Added MiddlewareInterface to the request handler so it can be used ei…
2 parents 1fd8b68 + 7c5225f commit 8270c12

File tree

4 files changed

+144
-25
lines changed

4 files changed

+144
-25
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [2.0.0] - 2019-07-12
8+
### Added
9+
- RequestHandler supports MiddlewareInterface to be use as a middleware as well
10+
11+
### Changed
12+
- Constructor arguments order for RequestHandler
13+
- Default handler is now optional
14+
15+
## [1.1.0] - 2019-06-26
16+
### Added
17+
- Handful factory methods
18+
- Updated documentation
19+
20+
## [1.0.0] - 2019-06-23
21+
- Initial release

README.md

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ Lightweight & dead simple PSR-15 Server Request Handler implementation to proces
1010

1111
### Description
1212

13-
PSR-7 Request Handler implementing the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php)
14-
and able to manage a collection of Middlewares implementing the [MiddlewareInterface](https://github.com/php-fig/http-server-middleware/blob/master/src/MiddlewareInterface.php).
13+
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)
14+
able to manage a collection of middlewares implementing the [MiddlewareInterface](https://github.com/php-fig/http-server-middleware/blob/master/src/MiddlewareInterface.php).
1515

16-
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.
16+
It can be used either as a RequestHandler or as a Middleware to fit into any implementation.
17+
18+
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.
1719

1820
### Goals
1921

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

3335
#### Run
3436

35-
Instantiate the RequestHandler class. It requires ony 2 arguments:
37+
Create a new instance of the request handler class which can be use as a request handler or as a middleware.
38+
39+
##### From the constructor
40+
41+
`RequestHandler::__construct(MiddlewareCollectionInterface $middlewareCollection, RequestHandlerInterface $defaultRequestHandler = null)`
42+
43+
- ***`$middlewareCollection`*** : [MiddlewareCollectionInterface](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/MiddlewareCollectionInterface.php)
44+
45+
Contains the middlewares and defines the strategy used to store the middlewares and to retrieve the next middleware.
46+
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).
3647

37-
- ***`$defaultRequestHandler`*** : [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php)
48+
- ***`$defaultRequestHandler = null`*** : [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php)
3849

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

4152
Some examples of what could be a "default request handler":
4253
- 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.*
4354
- 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.
4455

45-
It is possible to directly provide a `callable` and use the factory method `RequestHandler::fromCallable(callable $callable)`.
46-
It will create an anonymous instance of RequestHandlerInterface wrapping the given `callable` inside.
47-
48-
- ***`$middlewareCollection`*** : [MiddlewareCollectionInterface](https://github.com/noglitchyo/middleware-collection-request-handler/blob/master/src/MiddlewareCollectionInterface.php)
56+
##### From the factory method
4957

50-
Contains the middlewares and defines the strategy used to store the middlewares and to retrieve the next middleware.
51-
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).
58+
`RequestHandler::fromCallable(callable $callable, MiddlewareCollectionInterface $middlewareCollection)`
59+
60+
It creates a RequestHandler instance by wrapping the given `callable` inside an anonymous instance of RequestHandlerInterface.
61+
The callable is the $defaultRequestHandler. **It MUST returns a response implementing [ResponseInterface](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php).**
5262

5363
##### Example
5464

@@ -86,6 +96,7 @@ $requestHandler = RequestHandler::fromCallable(
8696
$middlewareCollection
8797
);
8898

99+
// As a RequestHandler:
89100
// Pass the request to the request handler which will dispatch the request to the middlewares.
90101
$response = $requestHandler->handle(/* ServerRequestInterface */);
91102

src/RequestHandler.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626

2727
namespace NoGlitchYo\MiddlewareCollectionRequestHandler;
2828

29+
use LogicException;
2930
use Psr\Http\Message\ResponseInterface;
3031
use Psr\Http\Message\ServerRequestInterface;
32+
use Psr\Http\Server\MiddlewareInterface;
3133
use Psr\Http\Server\RequestHandlerInterface;
3234

33-
class RequestHandler implements RequestHandlerInterface
35+
class RequestHandler implements RequestHandlerInterface, MiddlewareInterface
3436
{
3537
use RequestHandlerTrait;
3638

@@ -40,15 +42,15 @@ class RequestHandler implements RequestHandlerInterface
4042
private $middlewareCollection;
4143

4244
/**
43-
* @var RequestHandlerInterface
45+
* @var RequestHandlerInterface|null
4446
*/
4547
private $defaultRequestHandler;
4648

4749
public function __construct(
48-
RequestHandlerInterface $defaultRequestHandler,
49-
MiddlewareCollectionInterface $middlewareCollection
50+
MiddlewareCollectionInterface $middlewareCollection,
51+
RequestHandlerInterface $defaultRequestHandler = null
5052
) {
51-
$this->middlewareCollection = $middlewareCollection;
53+
$this->middlewareCollection = $middlewareCollection;
5254
$this->defaultRequestHandler = $defaultRequestHandler;
5355
}
5456

@@ -57,7 +59,7 @@ public static function fromCallable(
5759
MiddlewareCollectionInterface $middlewareCollection
5860
): self {
5961
$defaultRequestHandler = static::createRequestHandlerFromCallable($callable);
60-
return new static($defaultRequestHandler, $middlewareCollection);
62+
return new static($middlewareCollection, $defaultRequestHandler);
6163
}
6264

6365
public function __invoke(ServerRequestInterface $serverRequest): ResponseInterface
@@ -67,6 +69,12 @@ public function __invoke(ServerRequestInterface $serverRequest): ResponseInterfa
6769

6870
public function handle(ServerRequestInterface $request): ResponseInterface
6971
{
72+
if ($this->defaultRequestHandler === null) {
73+
throw new LogicException(
74+
'A default request handler must be defined if RequestHandler is used as a RequestHandler.'
75+
);
76+
}
77+
7078
if ($this->middlewareCollection->isEmpty()) {
7179
return $this->defaultRequestHandler->handle($request);
7280
}
@@ -75,4 +83,15 @@ public function handle(ServerRequestInterface $request): ResponseInterface
7583

7684
return $nextMiddleware->process($request, $this);
7785
}
86+
87+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
88+
{
89+
if ($this->middlewareCollection->isEmpty()) {
90+
return $handler->handle($request);
91+
}
92+
93+
$nextMiddleware = $this->middlewareCollection->next();
94+
95+
return $nextMiddleware->process($request, $this);
96+
}
7897
}

tests/RequestHandlerTest.php

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
namespace NoGlitchYo\MiddlewareCollectionRequestHandler\Tests;
2727

28+
use LogicException;
2829
use NoGlitchYo\MiddlewareCollectionRequestHandler\MiddlewareCollectionInterface;
2930
use NoGlitchYo\MiddlewareCollectionRequestHandler\RequestHandler;
3031
use Nyholm\Psr7\Response;
@@ -59,15 +60,15 @@ class RequestHandlerTest extends TestCase
5960

6061
protected function setUp(): void
6162
{
62-
$this->requestHandlerMock = $this->createMock(RequestHandlerInterface::class);
63+
$this->requestHandlerMock = $this->createMock(RequestHandlerInterface::class);
6364
$this->middlewareCollectionMock = $this->createMock(MiddlewareCollectionInterface::class);
6465

65-
$this->sut = new RequestHandler($this->requestHandlerMock, $this->middlewareCollectionMock);
66+
$this->sut = new RequestHandler($this->middlewareCollectionMock, $this->requestHandlerMock);
6667
}
6768

6869
public function testHandleDelegateToDefaultHandlerIfNoMiddleware()
6970
{
70-
$request = new ServerRequest('GET', '/test');
71+
$request = new ServerRequest('GET', '/test');
7172
$response = new Response(404);
7273

7374
$this->middlewareCollectionMock->method('isEmpty')
@@ -87,7 +88,7 @@ function (ServerRequestInterface $request) use ($response) {
8788

8889
public function testIsCallable()
8990
{
90-
$request = new ServerRequest('GET', '/test');
91+
$request = new ServerRequest('GET', '/test');
9192
$response = new Response(404);
9293

9394
$this->middlewareCollectionMock->method('isEmpty')
@@ -136,15 +137,29 @@ public function testHandleCallNextMiddlewareWithInstanceOfThisAsHandler()
136137
->method('next')
137138
->willReturnOnConsecutiveCalls(
138139
self::getMiddleware(false),
139-
self::getMiddleware(true),
140-
);
140+
self::getMiddleware(true)
141+
);
141142

142143
$this->assertInstanceOf(ResponseInterface::class, $this->sut->handle($request));
143144
}
144145

145-
public function testFromCallableCreateDefaultRequestHandlerFromCallable()
146+
public function testHandleThrowExceptionIfDefaultRequestHandlerIsNull()
146147
{
147148
$request = new ServerRequest('GET', '/test');
149+
150+
$this->middlewareCollectionMock = $this->createMock(MiddlewareCollectionInterface::class);
151+
152+
$sut = new RequestHandler($this->middlewareCollectionMock);
153+
154+
$this->expectException(LogicException::class);
155+
$this->expectExceptionMessage('A default request handler must be defined if RequestHandler is used as a RequestHandler.');
156+
157+
$sut->handle($request);
158+
}
159+
160+
public function testFromCallableCreateDefaultRequestHandlerFromCallable()
161+
{
162+
$request = new ServerRequest('GET', '/test');
148163
$response = new Response(405);
149164
$callable = function () use ($response) {
150165
return $response;
@@ -158,5 +173,58 @@ public function testFromCallableCreateDefaultRequestHandlerFromCallable()
158173
$this->assertSame($response, $sut->handle($request));
159174
}
160175

176+
public function testProcessCallNextMiddleware()
177+
{
178+
$request = new ServerRequest('GET', '/test');
179+
180+
$this->middlewareCollectionMock
181+
->expects($this->once())
182+
->method('isEmpty')
183+
->willReturn(false);
161184

185+
$this->middlewareCollectionMock
186+
->expects($this->once())
187+
->method('next')
188+
->willReturn(self::getMiddleware(true));
189+
190+
$handler = new class implements RequestHandlerInterface
191+
{
192+
public function handle(ServerRequestInterface $request): ResponseInterface
193+
{
194+
return new Response();
195+
}
196+
};
197+
198+
$this->assertInstanceOf(ResponseInterface::class, $this->sut->process($request, $handler));
199+
}
200+
201+
202+
public function testProcessReturnAndDelegateToHandlerIfNoMiddleware()
203+
{
204+
$request = new ServerRequest('GET', '/test');
205+
$response = new Response(404);
206+
207+
$this->middlewareCollectionMock->method('isEmpty')
208+
->willReturn(true);
209+
210+
$handler = new class($response) implements RequestHandlerInterface
211+
{
212+
/**
213+
* @var ResponseInterface
214+
*/
215+
private $response;
216+
217+
public function __construct(ResponseInterface $response)
218+
{
219+
$this->response = $response;
220+
}
221+
222+
public function handle(ServerRequestInterface $request): ResponseInterface
223+
{
224+
return $this->response;
225+
}
226+
};
227+
228+
$this->assertSame($response, $this->sut->process($request, $handler));
229+
}
162230
}

0 commit comments

Comments
 (0)