|
1 | | -# How can I autowire routes and pipelines? |
2 | | - |
3 | | -Expressive 2.0 switches to _programmatic_ pipelines and routes, versus |
4 | | -_configuration-driven_ pipelines and routing. One drawback is that with |
5 | | -configuration-driven approaches, users could provide configuration via a module |
6 | | -`ConfigProvider`, and automatically expose new pipeline middleware or routes; |
7 | | -with a programmatic approach, this is no longer possible. |
8 | | - |
9 | | -Or is it? |
10 | | - |
11 | | -## Delegator Factories |
12 | | - |
13 | | -One possibility, available since the Expressive 2.X skeleton application, is to |
14 | | -use _delegator factories_ on the `Zend\Expressive\Application` instance in order |
15 | | -to inject these items. |
16 | | - |
17 | | -A _delegator factory_ is a factory that _delegates_ creation of an instance to a |
18 | | -callback, and then operates on that instance for the purpose of altering the |
19 | | -instance or providing a replacement (e.g., a decorator or proxy). The delegate |
20 | | -callback usually wraps a service factory, or, because delegator factories |
21 | | -_also_ return an instance, additional delegator factories. As such, you assign |
22 | | -delegator _factories_, plural, to instances, allowing multiple delegator |
23 | | -factories to intercept processing of the service initialization. |
24 | | - |
25 | | -For the purposes of this particular example, we will use delegator factories to |
26 | | -both _pipe_ middleware as well as _route_ middleware. |
27 | | - |
28 | | -To demonstrate, we'll take the default pipeline and routing from the skeleton |
29 | | -application, and provide it via a delegator factory instead. |
30 | | - |
31 | | -First, we'll create the class `App\Factory\PipelineAndRoutesDelegator`, with |
32 | | -the following contents: |
33 | | - |
34 | | -```php |
35 | | -<?php |
36 | | - |
37 | | -namespace App\Factory; |
38 | | - |
39 | | -use App\Action; |
40 | | -use Psr\Container\ContainerInterface; |
41 | | -use Zend\Expressive\Application; |
42 | | -use Zend\Expressive\Helper\ServerUrlMiddleware; |
43 | | -use Zend\Expressive\Helper\UrlHelperMiddleware; |
44 | | -use Zend\Expressive\Middleware\ImplicitHeadMiddleware; |
45 | | -use Zend\Expressive\Middleware\ImplicitOptionsMiddleware; |
46 | | -use Zend\Expressive\Middleware\NotFoundHandler; |
47 | | -use Zend\Stratigility\Middleware\ErrorHandler; |
48 | | - |
49 | | -class PipelineAndRoutesDelegator |
50 | | -{ |
51 | | - /** |
52 | | - * @param ContainerInterface $container |
53 | | - * @param string $serviceName Name of the service being created. |
54 | | - * @param callable $callback Creates and returns the service. |
55 | | - * @return Application |
56 | | - */ |
57 | | - public function __invoke(ContainerInterface $container, $serviceName, callable $callback) |
58 | | - { |
59 | | - /** @var $app Application */ |
60 | | - $app = $callback(); |
61 | | - |
62 | | - // Setup pipeline: |
63 | | - $app->pipe(ErrorHandler::class); |
64 | | - $app->pipe(ServerUrlMiddleware::class); |
65 | | - $app->pipeRoutingMiddleware(); |
66 | | - $app->pipe(ImplicitHeadMiddleware::class); |
67 | | - $app->pipe(ImplicitOptionsMiddleware::class); |
68 | | - $app->pipe(UrlHelperMiddleware::class); |
69 | | - $app->pipeDispatchMiddleware(); |
70 | | - $app->pipe(NotFoundHandler::class); |
71 | | - |
72 | | - // Setup routes: |
73 | | - $app->get('/', Action\HomePageAction::class, 'home'); |
74 | | - $app->get('/api/ping', Action\PingAction::class, 'api.ping'); |
75 | | - |
76 | | - return $app; |
77 | | - } |
78 | | -} |
79 | | -``` |
80 | | - |
81 | | -> ### Where to put the factory |
82 | | -> |
83 | | -> You will place the factory class in one of the following locations: |
84 | | -> |
85 | | -> - `src/App/Factory/PipelineAndRoutesDelegator.php` if using the default, flat, |
86 | | -> application structure. |
87 | | -> - `src/App/src/Factory/PipelineAndRoutesDelegator.php` if using the |
88 | | -> recommended, modular, application structure. |
89 | | -
|
90 | | -Once you've created this, edit the class `App\ConfigProvider`; in it, we'll |
91 | | -update the `getDependencies()` method to add the delegator factory: |
92 | | - |
93 | | -```php |
94 | | -public function getDependencies() |
95 | | -{ |
96 | | - return [ |
97 | | - /* . . . */ |
98 | | - 'delegators' => [ |
99 | | - \Zend\Expressive\Application::class => [ |
100 | | - Factory\PipelineAndRoutesDelegator::class, |
101 | | - ], |
102 | | - ], |
103 | | - ]; |
104 | | -} |
105 | | -``` |
106 | | - |
107 | | -> ### Where is the ConfigProvider class? |
108 | | -> |
109 | | -> The `ConfigProvider` class is in one of the following locations: |
110 | | -> |
111 | | -> - `src/App/ConfigProvider.php` if using the default, flat, application |
112 | | -> structure. |
113 | | -> - `src/App/src/ConfigProvider.php` using the recommended, modular, application |
114 | | -> structure. |
115 | | -
|
116 | | -> ### Why is an array assigned? |
117 | | -> |
118 | | -> As noted above in the description of delegator factories, since each delegator |
119 | | -> factory returns an instance, you can nest multiple delegator factories in |
120 | | -> order to shape initialization of a service. As such, they are assigned as an |
121 | | -> _array_ to the service. |
122 | | -
|
123 | | -Once you've done this, you can remove: |
124 | | - |
125 | | -- `config/pipeline.php` |
126 | | -- `config/routes.php` |
127 | | -- The following lines from `public/index.php`: |
128 | | - |
129 | | - ```php |
130 | | - // Import programmatic/declarative middleware pipeline and routing |
131 | | - // configuration statements |
132 | | - require 'config/pipeline.php'; |
133 | | - require 'config/routes.php'; |
134 | | - ``` |
135 | | - |
136 | | -If you reload your application at this point, you should see that everything |
137 | | -continues to work as expected! |
138 | | - |
139 | | -## Caution: pipelines |
140 | | - |
141 | | -Using delegator factories is a nice way to keep your routing and pipeline |
142 | | -configuration close to the modules in which they are defined. However, there is |
143 | | -a caveat: you likely should **not** register pipeline middleware in a delegator |
144 | | -factory _other than within your root application module_. |
145 | | - |
146 | | -The reason for this is simple: pipelines are linear, and specific to your |
147 | | -application. If one module pipes in middleware, there's no guarantee it will be |
148 | | -piped before or after your main pipeline, and no way to pipe the middleware at a |
149 | | -position in the middle of the pipeline! |
150 | | - |
151 | | -As such: |
152 | | - |
153 | | -- Use a `config/pipeline.php` file for your pipeline, **OR** |
154 | | -- Ensure you only define the pipeline in a **single** delegator factory on your |
155 | | - `Application` instance. |
156 | | - |
157 | | -## Caution: third-party, distributed modules |
158 | | - |
159 | | -If you are developing a module to distribute as a package via |
160 | | -[Composer](https://getcomposer.org/), **you should not autowire any delegator |
161 | | -factories that inject pipeline middleware or routes in the `Application`**. |
162 | | - |
163 | | -Why? |
164 | | - |
165 | | -As noted in the above section, pipelines should be created exactly once, at |
166 | | -the application level. Registering pipeline middleware within a distributable |
167 | | -package will very likely not have the intended consequences. |
168 | | - |
169 | | -If you ship with pipeline middleware, we suggest that you: |
170 | | - |
171 | | -- Document the middleware, and where you anticipate it being used in the |
172 | | - middleware pipeline. |
173 | | -- Document how to add the middleware service to dependency configuration, or |
174 | | - provide the dependency configuration via your module's `ConfigProvider`. |
175 | | - |
176 | | -With regards to routes, there are other considerations: |
177 | | - |
178 | | -- Routes defined by the package might conflict with the application, or with |
179 | | - other packages used by the application. |
180 | | - |
181 | | -- Routing definitions are typically highly specific to the router implementation |
182 | | - in use. As an example, each of the currently supported router implementations |
183 | | - has a different syntax for placeholders: |
184 | | - |
185 | | - - `/user/:id` + "constraints" configuration to define constraints (zend-router) |
186 | | - - `/user/{id}` + "tokens" configuration to define constraints (Aura.Router) |
187 | | - - `/user/{id:\d+}` (FastRoute) |
188 | | - |
189 | | -- Your application may have specific routing considerations or design. |
190 | | - |
191 | | -You could, of course, detect what router is in use, and provide routing for each |
192 | | -known, supported router implementation within your delegator factory. We even |
193 | | -recommend doing exactly that. However, we note that such an approach does not |
194 | | -solve the other two points above. |
195 | | - |
196 | | -However, we still recommend _shipping_ a delegator factory that would register |
197 | | -your routes, since routes *are* often a part of module design; just **do not |
198 | | -autowire** that delegator factory. This way, end-users who *can* use the |
199 | | -defaults do not need to cut-and-paste routing definitions from your |
200 | | -documentation into their own applications; they will instead opt-in to your |
201 | | -delegator factory by wiring it into their own configuration. |
202 | | - |
203 | | -## Synopsis |
204 | | - |
205 | | -- We recommend using delegator factories for the purpose of autowiring routes, |
206 | | - and, with caveats, pipeline middleware: |
207 | | - - The pipeline should be created exactly once, so calls to `pipe()` should |
208 | | - occur in exactly _one_ delegator factory. |
209 | | -- Distributable packages should create a delegator factory for _routes only_, |
210 | | - but _should not_ register the delegator factory by default. |
| 1 | +<noscript><meta http-equiv="refresh" content="0; url=v2/cookbook/autowiring-routes-and-pipelines/"></noscript> |
| 2 | +<script> |
| 3 | + document.addEventListener("DOMContentLoaded", function (event) { |
| 4 | + var uri = new URL(window.location.href); |
| 5 | + uri.pathname = 'v2/cookbook/autowiring-routes-and-pipelines/'; |
| 6 | + window.location = uri.href; |
| 7 | + }); |
| 8 | +</script> |
0 commit comments