Total Complexity | 52 |
Total Lines | 390 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like HandlerList often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use HandlerList, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class HandlerList implements \Countable |
||
33 | { |
||
34 | const INIT = 'init'; |
||
35 | const VALIDATE = 'validate'; |
||
36 | const BUILD = 'build'; |
||
37 | const SIGN = 'sign'; |
||
38 | |||
39 | /** @var callable */ |
||
40 | private $handler; |
||
41 | |||
42 | /** @var array */ |
||
43 | private $named = []; |
||
44 | |||
45 | /** @var array */ |
||
46 | private $sorted; |
||
47 | |||
48 | /** @var callable|null */ |
||
49 | private $interposeFn; |
||
50 | |||
51 | /** @var array Steps (in reverse order) */ |
||
52 | private $steps = [ |
||
53 | self::SIGN => [], |
||
54 | self::BUILD => [], |
||
55 | self::VALIDATE => [], |
||
56 | self::INIT => [], |
||
57 | ]; |
||
58 | |||
59 | /** |
||
60 | * @param callable $handler HTTP handler. |
||
61 | */ |
||
62 | public function __construct(callable $handler = null) |
||
63 | { |
||
64 | $this->handler = $handler; |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Dumps a string representation of the list. |
||
69 | * |
||
70 | * @return string |
||
71 | */ |
||
72 | public function __toString() |
||
73 | { |
||
74 | $str = ''; |
||
75 | $i = 0; |
||
76 | |||
77 | foreach (array_reverse($this->steps) as $k => $step) { |
||
78 | foreach (array_reverse($step) as $j => $tuple) { |
||
79 | $str .= "{$i}) Step: {$k}, "; |
||
80 | if ($tuple[1]) { |
||
81 | $str .= "Name: {$tuple[1]}, "; |
||
82 | } |
||
83 | $str .= "Function: " . $this->debugCallable($tuple[0]) . "\n"; |
||
84 | $i++; |
||
85 | } |
||
86 | } |
||
87 | |||
88 | if ($this->handler) { |
||
89 | $str .= "{$i}) Handler: " . $this->debugCallable($this->handler) . "\n"; |
||
90 | } |
||
91 | |||
92 | return $str; |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Set the HTTP handler that actually returns a response. |
||
97 | * |
||
98 | * @param callable $handler Function that accepts a request and array of |
||
99 | * options and returns a Promise. |
||
100 | */ |
||
101 | public function setHandler(callable $handler) |
||
102 | { |
||
103 | $this->handler = $handler; |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Returns true if the builder has a handler. |
||
108 | * |
||
109 | * @return bool |
||
110 | */ |
||
111 | public function hasHandler() |
||
112 | { |
||
113 | return (bool) $this->handler; |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Append a middleware to the init step. |
||
118 | * |
||
119 | * @param callable $middleware Middleware function to add. |
||
120 | * @param string $name Name of the middleware. |
||
121 | */ |
||
122 | public function appendInit(callable $middleware, $name = null) |
||
123 | { |
||
124 | $this->add(self::INIT, $name, $middleware); |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * Prepend a middleware to the init step. |
||
129 | * |
||
130 | * @param callable $middleware Middleware function to add. |
||
131 | * @param string $name Name of the middleware. |
||
132 | */ |
||
133 | public function prependInit(callable $middleware, $name = null) |
||
134 | { |
||
135 | $this->add(self::INIT, $name, $middleware, true); |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Append a middleware to the validate step. |
||
140 | * |
||
141 | * @param callable $middleware Middleware function to add. |
||
142 | * @param string $name Name of the middleware. |
||
143 | */ |
||
144 | public function appendValidate(callable $middleware, $name = null) |
||
145 | { |
||
146 | $this->add(self::VALIDATE, $name, $middleware); |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Prepend a middleware to the validate step. |
||
151 | * |
||
152 | * @param callable $middleware Middleware function to add. |
||
153 | * @param string $name Name of the middleware. |
||
154 | */ |
||
155 | public function prependValidate(callable $middleware, $name = null) |
||
156 | { |
||
157 | $this->add(self::VALIDATE, $name, $middleware, true); |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Append a middleware to the build step. |
||
162 | * |
||
163 | * @param callable $middleware Middleware function to add. |
||
164 | * @param string $name Name of the middleware. |
||
165 | */ |
||
166 | public function appendBuild(callable $middleware, $name = null) |
||
167 | { |
||
168 | $this->add(self::BUILD, $name, $middleware); |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Prepend a middleware to the build step. |
||
173 | * |
||
174 | * @param callable $middleware Middleware function to add. |
||
175 | * @param string $name Name of the middleware. |
||
176 | */ |
||
177 | public function prependBuild(callable $middleware, $name = null) |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Append a middleware to the sign step. |
||
184 | * |
||
185 | * @param callable $middleware Middleware function to add. |
||
186 | * @param string $name Name of the middleware. |
||
187 | */ |
||
188 | public function appendSign(callable $middleware, $name = null) |
||
189 | { |
||
190 | $this->add(self::SIGN, $name, $middleware); |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * Prepend a middleware to the sign step. |
||
195 | * |
||
196 | * @param callable $middleware Middleware function to add. |
||
197 | * @param string $name Name of the middleware. |
||
198 | */ |
||
199 | public function prependSign(callable $middleware, $name = null) |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Add a middleware before the given middleware by name. |
||
206 | * |
||
207 | * @param string|callable $findName Add before this |
||
208 | * @param string $withName Optional name to give the middleware |
||
209 | * @param callable $middleware Middleware to add. |
||
210 | */ |
||
211 | public function before($findName, $withName, callable $middleware) |
||
212 | { |
||
213 | $this->splice($findName, $withName, $middleware, true); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Add a middleware after the given middleware by name. |
||
218 | * |
||
219 | * @param string|callable $findName Add after this |
||
220 | * @param string $withName Optional name to give the middleware |
||
221 | * @param callable $middleware Middleware to add. |
||
222 | */ |
||
223 | public function after($findName, $withName, callable $middleware) |
||
224 | { |
||
225 | $this->splice($findName, $withName, $middleware, false); |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Remove a middleware by name or by instance from the list. |
||
230 | * |
||
231 | * @param string|callable $nameOrInstance Middleware to remove. |
||
232 | */ |
||
233 | public function remove($nameOrInstance) |
||
234 | { |
||
235 | if (is_callable($nameOrInstance)) { |
||
236 | $this->removeByInstance($nameOrInstance); |
||
237 | } elseif (is_string($nameOrInstance)) { |
||
238 | $this->removeByName($nameOrInstance); |
||
239 | } |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Interpose a function between each middleware (e.g., allowing for a trace |
||
244 | * through the middleware layers). |
||
245 | * |
||
246 | * The interpose function is a function that accepts a "step" argument as a |
||
247 | * string and a "name" argument string. This function must then return a |
||
248 | * function that accepts the next handler in the list. This function must |
||
249 | * then return a function that accepts a CommandInterface and optional |
||
250 | * RequestInterface and returns a promise that is fulfilled with an |
||
251 | * Aws\ResultInterface or rejected with an Aws\Exception\AwsException |
||
252 | * object. |
||
253 | * |
||
254 | * @param callable|null $fn Pass null to remove any previously set function |
||
255 | */ |
||
256 | public function interpose(callable $fn = null) |
||
257 | { |
||
258 | $this->sorted = null; |
||
259 | $this->interposeFn = $fn; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Compose the middleware and handler into a single callable function. |
||
264 | * |
||
265 | * @return callable |
||
266 | */ |
||
267 | public function resolve() |
||
268 | { |
||
269 | if (!($prev = $this->handler)) { |
||
270 | throw new \LogicException('No handler has been specified'); |
||
271 | } |
||
272 | |||
273 | if ($this->sorted === null) { |
||
274 | $this->sortMiddleware(); |
||
275 | } |
||
276 | |||
277 | foreach ($this->sorted as $fn) { |
||
278 | $prev = $fn($prev); |
||
279 | } |
||
280 | |||
281 | return $prev; |
||
282 | } |
||
283 | |||
284 | public function count() |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Splices a function into the middleware list at a specific position. |
||
294 | * |
||
295 | * @param $findName |
||
296 | * @param $withName |
||
297 | * @param callable $middleware |
||
298 | * @param $before |
||
299 | */ |
||
300 | private function splice($findName, $withName, callable $middleware, $before) |
||
301 | { |
||
302 | if (!isset($this->named[$findName])) { |
||
303 | throw new \InvalidArgumentException("$findName not found"); |
||
304 | } |
||
305 | |||
306 | $idx = $this->sorted = null; |
||
307 | $step = $this->named[$findName]; |
||
308 | |||
309 | if ($withName) { |
||
310 | $this->named[$withName] = $step; |
||
311 | } |
||
312 | |||
313 | foreach ($this->steps[$step] as $i => $tuple) { |
||
314 | if ($tuple[1] === $findName) { |
||
315 | $idx = $i; |
||
316 | break; |
||
317 | } |
||
318 | } |
||
319 | |||
320 | $replacement = $before |
||
321 | ? [$this->steps[$step][$idx], [$middleware, $withName]] |
||
322 | : [[$middleware, $withName], $this->steps[$step][$idx]]; |
||
323 | array_splice($this->steps[$step], $idx, 1, $replacement); |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Provides a debug string for a given callable. |
||
328 | * |
||
329 | * @param array|callable $fn Function to write as a string. |
||
330 | * |
||
331 | * @return string |
||
332 | */ |
||
333 | private function debugCallable($fn) |
||
334 | { |
||
335 | if (is_string($fn)) { |
||
336 | return "callable({$fn})"; |
||
337 | } elseif (is_array($fn)) { |
||
338 | $ele = is_string($fn[0]) ? $fn[0] : get_class($fn[0]); |
||
339 | return "callable(['{$ele}', '{$fn[1]}'])"; |
||
340 | } else { |
||
341 | return 'callable(' . spl_object_hash($fn) . ')'; |
||
|
|||
342 | } |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Sort the middleware, and interpose if needed in the sorted list. |
||
347 | */ |
||
348 | private function sortMiddleware() |
||
349 | { |
||
350 | $this->sorted = []; |
||
351 | |||
352 | if (!$this->interposeFn) { |
||
353 | foreach ($this->steps as $step) { |
||
354 | foreach ($step as $fn) { |
||
355 | $this->sorted[] = $fn[0]; |
||
356 | } |
||
357 | } |
||
358 | return; |
||
359 | } |
||
360 | |||
361 | $ifn = $this->interposeFn; |
||
362 | // Interpose the interposeFn into the handler stack. |
||
363 | foreach ($this->steps as $stepName => $step) { |
||
364 | foreach ($step as $fn) { |
||
365 | $this->sorted[] = $ifn($stepName, $fn[1]); |
||
366 | $this->sorted[] = $fn[0]; |
||
367 | } |
||
368 | } |
||
369 | } |
||
370 | |||
371 | private function removeByName($name) |
||
372 | { |
||
373 | if (!isset($this->named[$name])) { |
||
374 | return; |
||
375 | } |
||
376 | |||
377 | $this->sorted = null; |
||
378 | $step = $this->named[$name]; |
||
379 | $this->steps[$step] = array_values( |
||
380 | array_filter( |
||
381 | $this->steps[$step], |
||
382 | function ($tuple) use ($name) { |
||
383 | return $tuple[1] !== $name; |
||
384 | } |
||
385 | ) |
||
386 | ); |
||
387 | } |
||
388 | |||
389 | private function removeByInstance(callable $fn) |
||
390 | { |
||
391 | foreach ($this->steps as $k => $step) { |
||
392 | foreach ($step as $j => $tuple) { |
||
393 | if ($tuple[0] === $fn) { |
||
394 | $this->sorted = null; |
||
395 | unset($this->named[$this->steps[$k][$j][1]]); |
||
396 | unset($this->steps[$k][$j]); |
||
397 | } |
||
398 | } |
||
399 | } |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Add a middleware to a step. |
||
404 | * |
||
405 | * @param string $step Middleware step. |
||
406 | * @param string $name Middleware name. |
||
407 | * @param callable $middleware Middleware function to add. |
||
408 | * @param bool $prepend Prepend instead of append. |
||
409 | */ |
||
410 | private function add($step, $name, callable $middleware, $prepend = false) |
||
422 | } |
||
423 | } |
||
424 | } |
||
425 |