Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 83e6e87

Browse files
committed
Revise path-segregated resource chapter
This patch revises the path-segregated resource chapter to simplify the process: it now combines the steps of routing and pipeline generation into a single factory for the pipeline. This approach is preferable to the previous, as it ensures the module is completely self-contained.
1 parent 3e0ee65 commit 83e6e87

File tree

1 file changed

+81
-91
lines changed

1 file changed

+81
-91
lines changed

docs/book/cookbook/path-segregated-uri-generation.md

Lines changed: 81 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,20 @@ class ConfigProvider
6363
public function getDependencies() : array
6464
{
6565
return [
66-
'delegators' => [
67-
// module class name => delegators
68-
Router::class => [
69-
RoutesDelegatorFactory::class,
70-
],
71-
],
7266
'factories' => [
73-
// module class name => factory
74-
LinkGenerator::class => new LinkGeneratorFactory(UrlGenerator::class),
75-
ResourceGenerator::class => new ResourceGeneratorFactory(LinkGenerator::class),
76-
Router::class => FastRouteRouterFactory::class,
77-
UrlHelper::class => new UrlHelperFactory('/api', Router::class),
78-
UrlHelperMiddleware::class => new UrlHelperMiddlewareFactory(UrlHelper::class),
79-
UrlGenerator::class => new ExpressiveUrlGeneratorFactory(UrlHelper::class),
67+
// module-specific class name => factory
68+
LinkGenerator::class => new LinkGeneratorFactory(UrlGenerator::class),
69+
ResourceGenerator::class => new ResourceGeneratorFactory(LinkGenerator::class),
70+
Router::class => FastRouteRouterFactory::class,
71+
UrlHelper::class => new UrlHelperFactory('/api', Router::class),
72+
UrlHelperMiddleware::class => new UrlHelperMiddlewareFactory(UrlHelper::class),
73+
UrlGenerator::class => new ExpressiveUrlGeneratorFactory(UrlHelper::class),
8074

8175
// Our handler:
82-
CreateBookHandler::class => CreateBookHandlerFactory::class,
76+
CreateBookHandler::class => CreateBookHandlerFactory::class,
77+
78+
// And our pipeline:
79+
Pipeline::class => PipelineFactory::class,
8380
],
8481
];
8582
}
@@ -174,105 +171,98 @@ class CreateBookHandlerFactory
174171
}
175172
```
176173

177-
## Creating path-segregated routes
174+
You can create any number of such handlers for your module; the above
175+
demonstrates how and where injection of the alternate resource generator occurs.
176+
177+
## Creating our pipeline and routes
178+
179+
Now we can create our pipeline and routes.
180+
181+
Generally when piping to an application instance, we can specify a class name of
182+
middleware to pipe, or an array of middleware:
183+
184+
```php
185+
// in config/pipeline.php:
186+
$app->pipe('/api', [
187+
\Zend\ProblemDetails\ProblemDetailsMiddleware::class,
188+
\Api\RouteMiddleware::class, // module-specific routing middleware!
189+
ImplicitHeadMiddleware::class,
190+
ImplicitOptionsMiddleware::class,
191+
MethodNotAllowedMiddleware::class,
192+
\Api\UrlHelperMiddleware::class, // module-specific URL helper middleware!
193+
DispatchMiddleware::class,
194+
\Zend\ProblemDetails\ProblemDetailsNotFoundHandler::class,
195+
]);
196+
```
197+
198+
However, we have both the pipeline _and_ routes, and we likely want to indicate
199+
the exact behavior of this pipeline. Additionally, we may want to re-use this
200+
pipeline in other applications; pushing this into the application configuration
201+
makes that more error-prone.
178202

179-
Finally, we need to create a route to it. We can do that by creating a delegator
180-
factory (which we have already referenced above):
203+
As such, we will create a factory that generates and returns a
204+
`Zend\Stratigility\MiddlewarePipe` instance that is fully configured for our
205+
module. As part of this functionality, we will also add our module-specific
206+
routing.
181207

182208
```php
183-
// In src/Api/RoutesDelegatorFactory.php:
209+
// In src/Api/PipelineFactory.php:
184210
namespace Api;
185211

186212
use Psr\Container\ContainerInterface;
187213
use Zend\Expressive\MiddlewareFactory;
188-
use Zend\Expressive\Router\RouteCollector;
189-
use Zend\Expressive\Router\RouterInterface;
190-
191-
/**
192-
* Add routes to the router.
193-
*
194-
* This delegator decorates creation of the router, and is used to
195-
* inject routes into it via a `RouteCollector` instance, using a combination of
196-
* the HTTP method name as the instance method, a path, a middleware/handler, and
197-
* optionally a name.
198-
*
199-
* You will need to use the MiddlewareFactory to prepare your middleware,
200-
* as the `RouteCollector` expects valid middleware instances.
201-
*/
202-
class RoutesDelegatorFactory
214+
use Zend\Expressive\Router\Middleware as RouterMiddleware;
215+
use Zend\ProblemDetails\ProblemDetailsMiddleware;
216+
use Zend\ProblemDetails\ProblemDetailsNotFoundHandler;
217+
use Zend\Stratigility\MiddlewarePipe;
218+
219+
class PipelineFactory
203220
{
204-
public function __invoke(ContainerInterface $container, string $serviceName, callable $routerFactory) : RouterInterface
221+
public function __invoke(ContainerInterface $container) : MiddlewarePipe
205222
{
206-
$router = $routerFactory();
207-
$routes = new RouteCollector($router);
208223
$factory = $container->get(MiddlewareFactory::class);
209224

210-
// Add routing here:
225+
// First, create our middleware pipeline
226+
$pipeline = new MiddlewarePipe();
227+
$pipeline->pipe($factory->lazy(ProblemDetailsMiddleware::class));
228+
$pipeline->pipe($factory->lazy(RouteMiddleware::class)); // module-specific!
229+
$pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitHeadMiddleware::class));
230+
$pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitOptionsMiddleware::class));
231+
$pipeline->pipe($factory->lazy(RouterMiddleware\MethodNotAllowedMiddleware::class));
232+
$pipeline->pipe($factory->lazy(UrlHelperMiddlweare::class)); // module-specific!
233+
$pipeline->pipe($factory->lazy(RouterMiddleware\DispatchMiddleware::class));
234+
$pipeline->pipe($factory->lazy(ProblemDetailsNotFoundHandler::class));
235+
236+
// Second, we'll create our routes
237+
$router = $container->get(Router::class); // Retrieve our module-specific router
238+
$routes = new RouteCollector($router); // Create a route collector to simplify routing
239+
240+
// Start routing:
211241
$routes->post('/books', $factory->lazy(CreateBookHandler::class));
212-
213-
// Return the router at the end!
214-
return $router;
242+
243+
// Return the pipeline now that we're done!
244+
return $pipeline;
215245
}
216246
}
217247
```
218248

219-
Note that the routing does **not** include the string `/api`; this is because
220-
that string will be stripped when we path-segregate our API middleware pipeline.
221-
All routing will be _relative_ to that path.
249+
Note that the routing definitions do **not** include the prefix `/api`; this is
250+
because that prefix will be stripped when we path-segregate our API middleware
251+
pipeline. All routing will be _relative_ to that path.
222252

223253
## Creating a path-segregated pipeline
224254

225-
Finally, we will create our path-segregated middleware pipeline:
255+
Finally, we will attach our pipeline to the application, using path segregation:
226256

227257
```php
228258
// in config/pipeline.php:
229-
$app->pipe('/api', [
230-
\Zend\ProblemDetails\ProblemDetailsMiddleware::class,
231-
\Api\RouteMiddleware::class, // module-specific routing middleware!
232-
ImplicitHeadMiddleware::class,
233-
ImplicitOptionsMiddleware::class,
234-
MethodNotAllowedMiddleware::class,
235-
\Api\UrlHelperMiddleware::class, // module-specific URL helper middleware!
236-
DispatchMiddleware::class,
237-
\Zend\ProblemDetails\ProblemDetailsNotFoundHandler::class,
238-
]);
259+
$app->pipe('/api', \Api\Pipeline::class);
239260
```
240261

241-
> You might want to create the above as a middleware pipeline _service_ via a
242-
> factory:
243-
>
244-
> ```php
245-
> namespace Api;
246-
>
247-
> use Psr\Container\ContainerInterface;
248-
> use Zend\Expressive\MiddlewareFactory;
249-
> use Zend\Expressive\Router\Middleware as RouterMiddleware;
250-
> use Zend\ProblemDetails\ProblemDetailsMiddleware;
251-
> use Zend\ProblemDetails\ProblemDetailsNotFoundHandler;
252-
> use Zend\Stratigility\MiddlewarePipe;
253-
>
254-
> class PipelineFactory
255-
> {
256-
> public function __invoke(ContainerInterface $container) : MiddlewarePipe
257-
> {
258-
> $factory = $container->get(MiddlewareFactory::class);
259-
> $pipeline = new MiddlewarePipe();
260-
> $pipeline->pipe($factory->lazy(ProblemDetailsMiddleware::class));
261-
> $pipeline->pipe($factory->lazy(RouteMiddleware::class)); // module-specific!
262-
> $pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitHeadMiddleware::class));
263-
> $pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitOptionsMiddleware::class));
264-
> $pipeline->pipe($factory->lazy(RouterMiddleware\MethodNotAllowedMiddleware::class));
265-
> $pipeline->pipe($factory->lazy(UrlHelperMiddlweare::class)); // module-specific!
266-
> $pipeline->pipe($factory->lazy(RouterMiddleware\DispatchMiddleware::class));
267-
> $pipeline->pipe($factory->lazy(ProblemDetailsNotFoundHandler::class));
268-
> return $pipeline;
269-
> }
270-
> }
271-
> ```
272-
>
273-
> Such an approach keeps the pipeline definition in the module, which allows you
274-
> to better re-use it later.
262+
This statement tells the application to pipe the pipeline returned by our
263+
`PipelineFactory` under the path `/api`; that path will be stripped from
264+
requests when passed to the underlying middleware.
275265

276-
The above approach will allow you to create a custom pipeline that can be
277-
dropped into an existing application, and allows defining per-module routing and
278-
dispatch relative to a given path.
266+
At this point, we now have a re-usable module, complete with its own routing,
267+
with URI generation that will include the base path under which we have
268+
segregated the pipeline!

0 commit comments

Comments
 (0)