From c80cbdb04320c626b2f9382317b8749642968927 Mon Sep 17 00:00:00 2001 From: pmall Date: Tue, 13 Mar 2018 15:02:34 +0100 Subject: [PATCH] move composable dispatcher to dispatcher-composable package --- README.md | 240 ++---------------- spec/Dispatcher/ComposableResolver.spec.php | 82 ------ .../ResolverWithMiddleware.spec.php | 150 ----------- src/Dispatcher/ComposableResolver.php | 50 ---- src/Dispatcher/ResolverWithMiddleware.php | 81 ------ 5 files changed, 22 insertions(+), 581 deletions(-) delete mode 100644 spec/Dispatcher/ComposableResolver.spec.php delete mode 100644 spec/Dispatcher/ResolverWithMiddleware.spec.php delete mode 100644 src/Dispatcher/ComposableResolver.php delete mode 100644 src/Dispatcher/ResolverWithMiddleware.php diff --git a/README.md b/README.md index 4d977e3..f486017 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,21 @@ This package provides a [Psr-15](https://www.php-fig.org/psr/psr-15/) dispatcher **Run tests** `./vendor/bin/kahlan` -- [Getting started](https://github.com/ellipsephp/dispatcher#getting-started) -- [Middleware and request handler resolving](https://github.com/ellipsephp/dispatcher#middleware-and-request-handler-resolving) -- [Composing a dispatcher](https://github.com/ellipsephp/dispatcher#composing-a-dispatcher) +- [Using a dispatcher](#using-a-dispatcher) +- [Middleware and request handler resolving](#middleware-and-request-handler-resolving) -## Getting started +## Using a dispatcher 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. It is basically a request handler decorator wrapping a middleware queue around a request handler. Its constructor takes two parameters: -- a request handler instance implementing `Psr\Http\Server\RequestHandlerInterface` -- an array containing middleware instances implementing `Psr\Http\Server\MiddlewareInterface` +- a request handler object implementing `Psr\Http\Server\RequestHandlerInterface` +- an array containing middleware objects implementing `Psr\Http\Server\MiddlewareInterface` 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. -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. +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. ```php handle($request); ## Middleware and request handler resolving -Another common practice is to allow callables and class names registered in a container to be used as regular middleware/request handler. +A common practice is to allow callables and class names registered in a container to be used as regular middleware/request handler. -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. +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`. ```php handle($reque Here is some ellipse packages providing resolvers for common resolving scenario: - [ellipse/dispatcher-callable](https://github.com/ellipsephp/dispatcher-callable) allowing to use callables as Psr-15 implementations -- [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 -- [ellipse/dispatcher-controller](https://github.com/ellipsephp/dispatcher-controller) allowing to use controller definitions as Psr-15 request handler - -Then it is up to you to build the dispatcher factory you need. +- [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 +- [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 Here is an example of a class implementing `DispatcherFactoryInterface` in case you need to create a custom one: @@ -182,22 +165,22 @@ class MyResolver implements DispatcherFactoryInterface public function __invoke($handler, array $middleware = []): Dispatcher { - // replace the handler with a ResolvedRequestHandler when the request handler should be resolved. - if ($this->shouldResolveHandler($handler)) { + // Replace the handler with a ResolvedRequestHandler when the request handler should be resolved. + $handler = $this->shouldResolveHandler($handler) + : new ResolvedRequestHandler($handler) + ? $handler; - $handler = new ResolvedRequestHandler($handler); + // Replace middleware with a ResolvedMiddleware when a middleware should be resolved. + $middleware = array_map(function ($middleware) { - } - - // Delegate the dispatcher creation to the decorated factory. - return ($this->delegate)($handler, array_map(function ($middleware) { - - // replace the middleware with a ResolvedMiddleware when the middleware should be resolved. return $this->shouldResolveMiddleware($middleware) ? new ResolvedMiddleware($middleware) : $middleware; - }, $middleware)); + }, $middleware); + + // Delegate the dispatcher creation to the decorated factory. + return ($this->delegate)($handler, $middleware); } private shouldResolveHandler($handler): bool @@ -211,182 +194,3 @@ class MyResolver implements DispatcherFactoryInterface } } ``` - -The `MyResolver` can now decorate any implementation of `DispatcherFactoryInterface`: - -```php -get('/', $factory(new RequestHandler1)); - -// Let's have a first route group. -$r->group('/group1', function ($r) use ($factory) { - - // SomeMiddleware3 is specific to this route group. - $factory = new ResolverWithMiddleware($factory, [new SomeMiddleware3]); - - // The dispatcher matching the GET /group1/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler2. - $r->get('/route1', $factory(new RequestHandler2)); - - // The dispatcher matching the GET /group1/route2 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler3. - $r->get('/route2', $factory(new RequestHandler3)); - -}); - -// And a second route group. -$r->group('/group2', function ($r) use ($factory) { - - // SomeMiddleware4 is specific to this route group. - $factory = new ResolverWithMiddleware($factory, [new SomeMiddleware4]); - - // The dispatcher matching the GET /group2/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware4 and RequestHandler4. - $r->get('/route1', $factory(new RequestHandler4)); - - // Also middleware can be added on a per route basis. - $r->get('/route2', $factory(new RequestHandler5, [ - new SomeMiddleware5, - ])); - -}); -``` - -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: - -```php -with([ - new SomeMiddleware1, - new SomeMiddleware2, -]); - -// The dispatcher matching the GET / route will use SomeMiddleware1, SomeMiddleware2 and RequestHandler1. -$r->get('/', $factory(new RequestHandler1)); - -// Let's have a first route group. -$r->group('/group1', function ($r) use ($factory) { - - // SomeMiddleware3 is specific to this route group. - $factory = $factory->with([new SomeMiddleware3]); - - // The dispatcher matching the GET /group1/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler2. - $r->get('/route1', $factory(new RequestHandler2)); - - // The dispatcher matching the GET /group1/route2 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware3 and RequestHandler3. - $r->get('/route2', $factory(new RequestHandler3)); - -}); - -// And a second route group. -$r->group('/group2', function ($r) use ($factory) { - - // SomeMiddleware4 is specific to this route group. - $factory = $factory->with([new SomeMiddleware4]); - - // The dispatcher matching the GET /group2/route1 route will use SomeMiddleware1, SomeMiddleware2, SomeMiddleware4 and RequestHandler4. - $r->get('/route1', $factory(new RequestHandler4)); - - // Also middleware can be added on a per route basis. - $r->get('/route2', $factory(new RequestHandler5, [ - new SomeMiddleware5, - ])); - -}); -``` - -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: - -```php -with([ - function ($request, $handler) { - - // This callable behave like a Psr-15 middleware. - - }, -]); - -// Callables can be used as request handlers too. -$r->get('/', $factory(function ($request) { - - // This callable behave like a Psr-15 request handler. - -})) -``` diff --git a/spec/Dispatcher/ComposableResolver.spec.php b/spec/Dispatcher/ComposableResolver.spec.php deleted file mode 100644 index bf3848f..0000000 --- a/spec/Dispatcher/ComposableResolver.spec.php +++ /dev/null @@ -1,82 +0,0 @@ -delegate = mock(DispatcherFactoryInterface::class); - - $this->resolver = new ComposableResolver($this->delegate->get()); - - }); - - it('should implement DispatcherFactoryInterface', function () { - - expect($this->resolver)->toBeAnInstanceOf(DispatcherFactoryInterface::class); - - }); - - describe('->with()', function () { - - it ('should return a new ResolverWithMiddleware using the delegate and the given middleware queue', function () { - - $middleware = ['middleware1', 'middleware2']; - - $test = $this->resolver->with($middleware); - - $resolver = new ResolverWithMiddleware($this->delegate->get(), $middleware); - - expect($test)->toEqual($resolver); - - }); - - }); - - describe('->__invoke()', function () { - - beforeEach(function () { - - $this->dispatcher = mock(Dispatcher::class)->get(); - - }); - - context('when no middleware queue is given', function () { - - it('should proxy the delegate with the given request handler and an empty array of middleware', function () { - - $this->delegate->__invoke->with('handler', [])->returns($this->dispatcher); - - $test = ($this->resolver)('handler'); - - expect($test)->toBe($this->dispatcher); - - }); - - }); - - context('when an middleware queue is given', function () { - - it('should proxy the delegate with the given request handler and middleware queue', function () { - - $middleware = ['middleware1', 'middleware2']; - - $this->delegate->__invoke->with('handler', $middleware)->returns($this->dispatcher); - - $test = ($this->resolver)('handler', $middleware); - - expect($test)->toBe($this->dispatcher); - - }); - - }); - - }); - -}); diff --git a/spec/Dispatcher/ResolverWithMiddleware.spec.php b/spec/Dispatcher/ResolverWithMiddleware.spec.php deleted file mode 100644 index 267a770..0000000 --- a/spec/Dispatcher/ResolverWithMiddleware.spec.php +++ /dev/null @@ -1,150 +0,0 @@ -delegate = mock(DispatcherFactoryInterface::class); - - $this->middleware = ['middleware1', 'middleware2']; - - $this->resolver = new ResolverWithMiddleware($this->delegate->get(), $this->middleware); - - }); - - it('should implement DispatcherFactoryInterface', function () { - - expect($this->resolver)->toBeAnInstanceOf(DispatcherFactoryInterface::class); - - }); - - describe('->with()', function () { - - it ('should return a new ResolverWithMiddleware using the resolver and the given middleware queue', function () { - - $middleware = ['middleware3', 'middleware4']; - - $test = $this->resolver->with($middleware); - - $resolver = new ResolverWithMiddleware($this->resolver, $middleware); - - expect($test)->toEqual($resolver); - - }); - - }); - - describe('->dispatcher()', function () { - - beforeEach(function () { - - $this->dispatcher = mock(Dispatcher::class)->get(); - - }); - - context("when the delegate is a ResolverWithMiddleware", function () { - - it('should proxy the delegate ->dispatcher() method', function () { - - $middleware = ['middleware1', 'middleware2']; - - $delegate = mock(ResolverWithMiddleware::class); - - $resolver = new ResolverWithMiddleware($delegate->get(), []); - - $delegate->dispatcher->with('handler', $middleware)->returns($this->dispatcher); - - $test = $resolver->dispatcher('handler', $middleware); - - expect($test)->toBe($this->dispatcher); - - }); - - }); - - context("when the delegate is not a ResolverWithMiddleware", function () { - - it('should proxy the delegate', function () { - - $middleware = ['middleware1', 'middleware2']; - - $delegate = mock(DispatcherFactoryInterface::class); - - $resolver = new ResolverWithMiddleware($delegate->get(), []); - - $delegate->__invoke->with('handler', $middleware)->returns($this->dispatcher); - - $test = $resolver->dispatcher('handler', $middleware); - - expect($test)->toBe($this->dispatcher); - - }); - - }); - - }); - - describe('->__invoke()', function () { - - beforeEach(function () { - - $this->delegate = mock(DispatcherFactoryInterface::class); - $this->middleware = ['middleware3', 'middleware4']; - - $this->resolver = partialMock(ResolverWithMiddleware::class, [ - $this->delegate->get(), - $this->middleware, - ]); - - $this->handler = mock(RequestHandlerInterface::class)->get(); - $this->dispatcher1 = mock(Dispatcher::class)->get(); - $this->dispatcher2 = mock(Dispatcher::class)->get(); - - }); - - context('when no middleware queue is given', function () { - - it('should resolve the decorated dispatcher with an empty array of middleware', function () { - - $this->resolver->dispatcher->with($this->handler, [])->returns($this->dispatcher1); - - $this->delegate->__invoke->with($this->dispatcher1, $this->middleware)->returns($this->dispatcher2); - - $test = ($this->resolver->get())($this->handler); - - expect($test)->toBe($this->dispatcher2); - - }); - - }); - - context('when an middleware queue is given', function () { - - it('should resolve the decorated dispatcher with the given middleware queue', function () { - - $middleware = ['middleware1', 'middleware2']; - - $this->resolver->dispatcher->with($this->handler, $middleware)->returns($this->dispatcher1); - - $this->delegate->__invoke->with($this->dispatcher1, $this->middleware)->returns($this->dispatcher2); - - $test = ($this->resolver->get())($this->handler, $middleware); - - expect($test)->toBe($this->dispatcher2); - - }); - - }); - - }); - -}); diff --git a/src/Dispatcher/ComposableResolver.php b/src/Dispatcher/ComposableResolver.php deleted file mode 100644 index 6b47b24..0000000 --- a/src/Dispatcher/ComposableResolver.php +++ /dev/null @@ -1,50 +0,0 @@ -delegate = $delegate; - } - - /** - * Returns a new DispatcherWithMiddleware using the delegate and the given - * middleware queue. - * - * @param array $middleware - * @return \Ellipse\Dispatcher\ResolverWithMiddleware - */ - public function with(array $middleware): ResolverWithMiddleware - { - return new ResolverWithMiddleware($this->delegate, $middleware); - } - - /** - * Proxy the delegate. - * - * @param mixed $handler - * @param array $middleware - * @return \Ellipse\Dispatcher - */ - public function __invoke($handler, array $middleware = []): Dispatcher - { - return ($this->delegate)($handler, $middleware); - } -} diff --git a/src/Dispatcher/ResolverWithMiddleware.php b/src/Dispatcher/ResolverWithMiddleware.php deleted file mode 100644 index a453e8f..0000000 --- a/src/Dispatcher/ResolverWithMiddleware.php +++ /dev/null @@ -1,81 +0,0 @@ -delegate = $delegate; - $this->middleware = $middleware; - } - - /** - * Returns a new DispatcherWithMiddleware using this resolver as delegate - * and the given middleware queue. - * - * @param array $middleware - * @return \Ellipse\Dispatcher\ResolverWithMiddleware - */ - public function with(array $middleware): ResolverWithMiddleware - { - return new ResolverWithMiddleware($this, $middleware); - } - - /** - * Proxy the delegate with the resolved value of the dispatcher to decorate - * and the given middleware queue. - * - * @param mixed $handler - * @param array $middleware - * @return \Ellipse\Dispatcher - */ - public function __invoke($handler, array $middleware = []): Dispatcher - { - $handler = $this->dispatcher($handler, $middleware); - - return ($this->delegate)($handler, $this->middleware); - } - - /** - * Proxy the first delegate which is not a ResolverWithMiddleware. - * - * @param mixed $handler - * @param array $middleware - * @return \Ellipse\Dispatcher - */ - public function dispatcher($handler, array $middleware): Dispatcher - { - if ($this->delegate instanceof ResolverWithMiddleware) { - - return $this->delegate->dispatcher($handler, $middleware); - - } - - return ($this->delegate)($handler, $middleware); - } -}