Skip to content

Commit c80cbdb

Browse files
committed
move composable dispatcher to dispatcher-composable package
1 parent 7b8e567 commit c80cbdb

5 files changed

Lines changed: 22 additions & 581 deletions

File tree

README.md

Lines changed: 22 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,21 @@ This package provides a [Psr-15](https://www.php-fig.org/psr/psr-15/) dispatcher
88

99
**Run tests** `./vendor/bin/kahlan`
1010

11-
- [Getting started](https://github.com/ellipsephp/dispatcher#getting-started)
12-
- [Middleware and request handler resolving](https://github.com/ellipsephp/dispatcher#middleware-and-request-handler-resolving)
13-
- [Composing a dispatcher](https://github.com/ellipsephp/dispatcher#composing-a-dispatcher)
11+
- [Using a dispatcher](#using-a-dispatcher)
12+
- [Middleware and request handler resolving](#middleware-and-request-handler-resolving)
1413

15-
## Getting started
14+
## Using a dispatcher
1615

1716
This package provides an `Ellipse\Dispatcher` class allowing to process a Psr-7 request through a Psr-15 middleware queue (First in first out order) before handling it with a Psr-15 request handler in order to create a Psr-7 response.
1817

1918
It is basically a request handler decorator wrapping a middleware queue around a request handler. Its constructor takes two parameters:
2019

21-
- a request handler instance implementing `Psr\Http\Server\RequestHandlerInterface`
22-
- an array containing middleware instances implementing `Psr\Http\Server\MiddlewareInterface`
20+
- a request handler object implementing `Psr\Http\Server\RequestHandlerInterface`
21+
- an array containing middleware objects implementing `Psr\Http\Server\MiddlewareInterface`
2322

2423
The `Dispatcher` itself implements `RequestHandlerInterface` so a response is produced by using its `->handle()` method with a request. It also means it can be used as the request handler of another `Dispatcher`. Also, The same `Dispatcher` can be used multiple times to handle as many requests as needed.
2524

26-
Finally when a value of the given middleware queue is not an implementation of `MiddlewareInterface` an `Ellipse\Dispatcher\Exceptions\MiddlewareTypeException` is thrown. [Factory decorators](https://github.com/ellipsephp/dispatcher#middleware-and-request-handler-resolving) can be used to resolve some type of values as middleware.
25+
Finally when a value of the given middleware queue is not an implementation of `MiddlewareInterface` an `Ellipse\Dispatcher\Exceptions\MiddlewareTypeException` is thrown. [Factory decorators](#middleware-and-request-handler-resolving) can be used to resolve some type of values as middleware.
2726

2827
```php
2928
<?php
@@ -32,9 +31,6 @@ namespace App;
3231

3332
use Ellipse\Dispatcher;
3433

35-
// Get some incoming Psr-7 request.
36-
$request = some_psr7_request_factory();
37-
3834
// Create a dispatcher using two middleware and a request handler.
3935
$dispatcher = new Dispatcher(new SomeRequestHandler, [
4036
new SomeMiddleware1,
@@ -61,9 +57,6 @@ namespace App;
6157

6258
use Ellipse\Dispatcher;
6359

64-
// Get some incoming Psr-7 request.
65-
$request = some_psr7_request_factory();
66-
6760
// Create a dispatcher with two middleware.
6861
$dispatcher = new Dispatcher(new SomeRequestHandler, [
6962
new SomeMiddleware1,
@@ -89,9 +82,9 @@ $response = $dispatcher->handle($request);
8982

9083
## Middleware and request handler resolving
9184

92-
Another common practice is to allow callables and class names registered in a container to be used as regular middleware/request handler.
85+
A common practice is to allow callables and class names registered in a container to be used as regular middleware/request handler.
9386

94-
For this purpose this package also provides an `Ellipse\DispatcherFactory` class implementing `Ellipse\DispatcherFactoryInterface`, allowing to produce `Dispatcher` instances. Its `__invoke()` method takes any value as request handler and an optional middleware queue. When the given request handler is not an implementation of `RequestHandlerInterface`, an `Ellipse\Dispatcher\Exceptions\RequestHandlerTypeException` is thrown.
87+
For this purpose this package also provides an `Ellipse\DispatcherFactory` class implementing `Ellipse\DispatcherFactoryInterface`, allowing to produce `Dispatcher` instances. Its `__invoke()` method takes any value as request handler and an optional middleware queue. An `Ellipse\Dispatcher\Exceptions\RequestHandlerTypeException` is thrown when the given request handler is not an implementation of `RequestHandlerInterface`.
9588

9689
```php
9790
<?php
@@ -100,9 +93,6 @@ namespace App;
10093

10194
use Ellipse\DispatcherFactory;
10295

103-
// Get some incoming Psr-7 request.
104-
$request = some_psr7_request_factory();
105-
10696
// Get a dispatcher factory.
10797
$factory = new DispatcherFactory;
10898

@@ -112,13 +102,11 @@ $dispatcher = $factory(new SomeRequestHandler, [new SomeMiddleware]);
112102
// Here a RequestHandlerTypeException is thrown because 'something' is not a Psr-15 request handler.
113103
$dispatcher = $factory('something', [new SomeMiddleware]);
114104

115-
// Here a MiddlewareTypeException is thrown because 'something' is not a Psr-15 middleware.
105+
// Here a MiddlewareTypeException is thrown by the Dispatcher class because 'something' is not a Psr-15 middleware.
116106
$dispatcher = $factory(new SomeRequestHandler, [new SomeMiddleware, 'something']);
117107
```
118108

119-
So what's the point of all this you may ask.
120-
121-
The point of `DispatcherFactory` is to be decorated by other factories resolving the given values as Psr-15 implementations before delegating it the dispatcher creation. It is a starting point for such factory decorators (also called resolvers) which ensure the dispatcher creation fails nicely when any value is not resolved as a Psr-15 implementation by any decorator.
109+
This class is not very useful by itself. The point of `DispatcherFactory` is to be decorated by other factories resolving the given values as Psr-15 implementations before delegating it the dispatcher creation. It is a starting point for such factory decorators (also called resolvers) which ensure the dispatcher creation fails nicely when any value is not resolved as a Psr-15 implementation by any decorator.
122110

123111
Here is an example of callable resolving using the `Ellipse\Dispatcher\CallableResolver` class from the [ellipse/dispatcher-callable](https://github.com/ellipsephp/dispatcher-callable) package:
124112

@@ -130,9 +118,6 @@ namespace App;
130118
use Ellipse\DispatcherFactory;
131119
use Ellipse\Dispatcher\CallableResolver;
132120

133-
// Get some incoming Psr-7 request.
134-
$request = some_psr7_request_factory();
135-
136121
// Get a decorated dispatcher factory.
137122
$factory = new CallableResolver(new DispatcherFactory);
138123

@@ -156,10 +141,8 @@ $response = $factory($handler, [$middleware, new SomeMiddleware])->handle($reque
156141
Here is some ellipse packages providing resolvers for common resolving scenario:
157142

158143
- [ellipse/dispatcher-callable](https://github.com/ellipsephp/dispatcher-callable) allowing to use callables as Psr-15 implementations
159-
- [ellipse/dispatcher-container](https://github.com/ellipsephp/dispatcher-container) allowing to use Psr-15 implementations retrieved from a [Psr-11 container](http://www.php-fig.org/psr/psr-11/meta/) using their class names
160-
- [ellipse/dispatcher-controller](https://github.com/ellipsephp/dispatcher-controller) allowing to use controller definitions as Psr-15 request handler
161-
162-
Then it is up to you to build the dispatcher factory you need.
144+
- [ellipse/dispatcher-container](https://github.com/ellipsephp/dispatcher-container) allowing to use Psr-15 implementation class names using a [Psr-11](https://www.php-fig.org/psr/psr-11/) container
145+
- [ellipse/dispatcher-controller](https://github.com/ellipsephp/dispatcher-controller) allowing to use controller actions as Psr-15 request handlers using a [Psr-11](https://www.php-fig.org/psr/psr-11/) container
163146

164147
Here is an example of a class implementing `DispatcherFactoryInterface` in case you need to create a custom one:
165148

@@ -182,22 +165,22 @@ class MyResolver implements DispatcherFactoryInterface
182165

183166
public function __invoke($handler, array $middleware = []): Dispatcher
184167
{
185-
// replace the handler with a ResolvedRequestHandler when the request handler should be resolved.
186-
if ($this->shouldResolveHandler($handler)) {
168+
// Replace the handler with a ResolvedRequestHandler when the request handler should be resolved.
169+
$handler = $this->shouldResolveHandler($handler)
170+
: new ResolvedRequestHandler($handler)
171+
? $handler;
187172

188-
$handler = new ResolvedRequestHandler($handler);
173+
// Replace middleware with a ResolvedMiddleware when a middleware should be resolved.
174+
$middleware = array_map(function ($middleware) {
189175

190-
}
191-
192-
// Delegate the dispatcher creation to the decorated factory.
193-
return ($this->delegate)($handler, array_map(function ($middleware) {
194-
195-
// replace the middleware with a ResolvedMiddleware when the middleware should be resolved.
196176
return $this->shouldResolveMiddleware($middleware)
197177
? new ResolvedMiddleware($middleware)
198178
: $middleware;
199179

200-
}, $middleware));
180+
}, $middleware);
181+
182+
// Delegate the dispatcher creation to the decorated factory.
183+
return ($this->delegate)($handler, $middleware);
201184
}
202185

203186
private shouldResolveHandler($handler): bool
@@ -211,182 +194,3 @@ class MyResolver implements DispatcherFactoryInterface
211194
}
212195
}
213196
```
214-
215-
The `MyResolver` can now decorate any implementation of `DispatcherFactoryInterface`:
216-
217-
```php
218-
<?php
219-
220-
namespace App;
221-
222-
use Ellipse\DispatcherFactory;
223-
use Ellipse\Dispatcher\CallableResolver;
224-
225-
$factory = new MyResolver(
226-
new CallableResolver(
227-
new DispatcherFactory
228-
)
229-
);
230-
```
231-
232-
## Composing a dispatcher
233-
234-
Sometimes a dispatcher needs to be composed at runtime according to certain conditions. When routing for example: a solution is needed to build a dispatcher using different middleware queues and request handlers according to the matched route.
235-
236-
For this purpose this package provides an `Ellipse\Dispatcher\ResolverWithMiddleware` class. It can decorate any object implementing `DispatcherFactoryInterface`, wrapping a middleware queue around the dispatcher produced by the decorated factory.
237-
238-
Let's have an example using [FastRoute](https://github.com/nikic/FastRoute):
239-
240-
```php
241-
<?php
242-
243-
namespace App;
244-
245-
use FastRoute\RouteCollector;
246-
247-
use Ellipse\DispatcherFactory;
248-
use Ellipse\Dispatcher\ResolverWithMiddleware;
249-
250-
// Create a new DispatcherFactory.
251-
$factory = new DispatcherFactory;
252-
253-
// Create a new FastRoute route collector.
254-
$r = new RouteCollector(...);
255-
256-
// Those middleware will be wrapped around all the dispatchers.
257-
$factory = new ResolverWithMiddleware($factory, [
258-
new SomeMiddleware1,
259-
new SomeMiddleware2,
260-
]);
261-
262-
// The dispatcher matching the GET / route will use SomeMiddleware1, SomeMiddleware2 and RequestHandler1.
263-
$r->get('/', $factory(new RequestHandler1));
264-
265-
// Let's have a first route group.
266-
$r->group('/group1', function ($r) use ($factory) {
267-
268-
// SomeMiddleware3 is specific to this route group.
269-
$factory = new ResolverWithMiddleware($factory, [new SomeMiddleware3]);
270-
271-
// The dispatcher matching the GET /group1/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler2.
272-
$r->get('/route1', $factory(new RequestHandler2));
273-
274-
// The dispatcher matching the GET /group1/route2 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler3.
275-
$r->get('/route2', $factory(new RequestHandler3));
276-
277-
});
278-
279-
// And a second route group.
280-
$r->group('/group2', function ($r) use ($factory) {
281-
282-
// SomeMiddleware4 is specific to this route group.
283-
$factory = new ResolverWithMiddleware($factory, [new SomeMiddleware4]);
284-
285-
// The dispatcher matching the GET /group2/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware4 and RequestHandler4.
286-
$r->get('/route1', $factory(new RequestHandler4));
287-
288-
// Also middleware can be added on a per route basis.
289-
$r->get('/route2', $factory(new RequestHandler5, [
290-
new SomeMiddleware5,
291-
]));
292-
293-
});
294-
```
295-
296-
Create many new classes is cumbersome so `ResolverWithMiddleware` has a `->with()` method taking a middleware queue as parameter and returning a new `ResolverWithMiddleware` using it. The first factory can be decorated with the `Ellipse\Dispatcher\ComposableResolver` class implementing the same `->with()` method. The previous example can be written like this using the `->with()` method:
297-
298-
```php
299-
<?php
300-
301-
namespace App;
302-
303-
use FastRoute\RouteCollector;
304-
305-
use Ellipse\DispatcherFactory;
306-
use Ellipse\Dispatcher\ComposableResolver;
307-
308-
// Create a new ComposableResolver.
309-
$factory = new ComposableResolver(new DispatcherFactory);
310-
311-
// Create a new FastRoute route collector.
312-
$r = new RouteCollector(...);
313-
314-
// Those middleware will be wrapped around all the dispatchers.
315-
$factory = $factory->with([
316-
new SomeMiddleware1,
317-
new SomeMiddleware2,
318-
]);
319-
320-
// The dispatcher matching the GET / route will use SomeMiddleware1, SomeMiddleware2 and RequestHandler1.
321-
$r->get('/', $factory(new RequestHandler1));
322-
323-
// Let's have a first route group.
324-
$r->group('/group1', function ($r) use ($factory) {
325-
326-
// SomeMiddleware3 is specific to this route group.
327-
$factory = $factory->with([new SomeMiddleware3]);
328-
329-
// The dispatcher matching the GET /group1/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler2.
330-
$r->get('/route1', $factory(new RequestHandler2));
331-
332-
// The dispatcher matching the GET /group1/route2 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler3.
333-
$r->get('/route2', $factory(new RequestHandler3));
334-
335-
});
336-
337-
// And a second route group.
338-
$r->group('/group2', function ($r) use ($factory) {
339-
340-
// SomeMiddleware4 is specific to this route group.
341-
$factory = $factory->with([new SomeMiddleware4]);
342-
343-
// The dispatcher matching the GET /group2/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware4 and RequestHandler4.
344-
$r->get('/route1', $factory(new RequestHandler4));
345-
346-
// Also middleware can be added on a per route basis.
347-
$r->get('/route2', $factory(new RequestHandler5, [
348-
new SomeMiddleware5,
349-
]));
350-
351-
});
352-
```
353-
354-
Of course, `ComposableResolver` and `ResolverWithMiddleware` can decorate any instance implementing `DispatcherFactoryInterface`. For example the `CallableResolver` class from the [ellipse/dispatcher-callable](https://github.com/ellipsephp/dispatcher-callable) package:
355-
356-
```php
357-
<?php
358-
359-
namespace App;
360-
361-
use FastRoute\RouteCollector;
362-
363-
use Ellipse\DispatcherFactory;
364-
use Ellipse\Dispatcher\ComposableResolver;
365-
use Ellipse\Dispatcher\CallableResolver;
366-
367-
// Create a new ComposableResolver resolving callables.
368-
$factory = new ComposableResolver(
369-
new CallableResolver(
370-
new DispatcherFactory
371-
)
372-
);
373-
374-
// Create a new FastRoute route collector.
375-
$r = new RouteCollector(...);
376-
377-
// Callables can be used as Psr-15 middleware.
378-
$factory = $factory->with([
379-
function ($request, $handler) {
380-
381-
// This callable behave like a Psr-15 middleware.
382-
383-
},
384-
]);
385-
386-
// Callables can be used as request handlers too.
387-
$r->get('/', $factory(function ($request) {
388-
389-
// This callable behave like a Psr-15 request handler.
390-
391-
}))
392-
```

0 commit comments

Comments
 (0)