Complex classes like RestActionReader 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 RestActionReader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
37 | class RestActionReader |
||
38 | { |
||
39 | const COLLECTION_ROUTE_PREFIX = 'c'; |
||
40 | |||
41 | private $annotationReader; |
||
42 | private $paramReader; |
||
43 | private $inflector; |
||
44 | private $formats; |
||
45 | private $includeFormat; |
||
46 | private $routePrefix; |
||
47 | private $namePrefix; |
||
48 | private $versions; |
||
49 | private $pluralize; |
||
50 | private $parents = []; |
||
51 | private $availableHTTPMethods = [ |
||
52 | 'get', |
||
53 | 'post', |
||
54 | 'put', |
||
55 | 'patch', |
||
56 | 'delete', |
||
57 | 'link', |
||
58 | 'unlink', |
||
59 | 'head', |
||
60 | 'options', |
||
61 | 'mkcol', |
||
62 | 'propfind', |
||
63 | 'proppatch', |
||
64 | 'move', |
||
65 | 'copy', |
||
66 | 'lock', |
||
67 | 'unlock', |
||
68 | ]; |
||
69 | private $availableConventionalActions = ['new', 'edit', 'remove']; |
||
70 | private $hasMethodPrefix; |
||
71 | |||
72 | /** |
||
73 | * ignore several type hinted arguments. |
||
74 | */ |
||
75 | private $ignoredClasses = [ |
||
76 | ConstraintViolationListInterface::class, |
||
77 | MessageInterface::class, |
||
78 | ParamConverter::class, |
||
79 | ParamFetcherInterface::class, |
||
80 | Request::class, |
||
81 | SessionInterface::class, |
||
82 | UserInterface::class, |
||
83 | ]; |
||
84 | |||
85 | 50 | public function __construct(Reader $annotationReader, ParamReaderInterface $paramReader, InflectorInterface $inflector, bool $includeFormat, array $formats = [], bool $hasMethodPrefix = true) |
|
94 | |||
95 | /** |
||
96 | * @param string|null $prefix |
||
97 | */ |
||
98 | 29 | public function setRoutePrefix($prefix = null) |
|
102 | |||
103 | /** |
||
104 | * @return string |
||
105 | */ |
||
106 | 29 | public function getRoutePrefix() |
|
110 | |||
111 | /** |
||
112 | * @param string|null $prefix |
||
113 | */ |
||
114 | 29 | public function setNamePrefix($prefix = null) |
|
118 | |||
119 | /** |
||
120 | * @return string |
||
121 | */ |
||
122 | public function getNamePrefix() |
||
126 | |||
127 | /** |
||
128 | * @param string[]|string|null $versions |
||
129 | */ |
||
130 | 29 | public function setVersions($versions = null) |
|
134 | |||
135 | /** |
||
136 | * @return string[]|null |
||
137 | */ |
||
138 | public function getVersions() |
||
142 | |||
143 | /** |
||
144 | * @param bool|null $pluralize |
||
145 | */ |
||
146 | 29 | public function setPluralize($pluralize) |
|
150 | |||
151 | /** |
||
152 | * @return bool|null |
||
153 | */ |
||
154 | public function getPluralize() |
||
158 | |||
159 | /** |
||
160 | * @param string[] $parents Array of parent resources names |
||
161 | */ |
||
162 | 29 | public function setParents(array $parents) |
|
166 | |||
167 | /** |
||
168 | * @return string[] |
||
169 | */ |
||
170 | public function getParents() |
||
174 | |||
175 | /** |
||
176 | * Set ignored classes. |
||
177 | * |
||
178 | * These classes will be ignored for route construction when |
||
179 | * type hinted as method argument. |
||
180 | * |
||
181 | * @param string[] $ignoredClasses |
||
182 | */ |
||
183 | public function setIgnoredClasses(array $ignoredClasses): void |
||
187 | |||
188 | /** |
||
189 | * Get ignored classes. |
||
190 | * |
||
191 | * @return string[] |
||
192 | */ |
||
193 | 13 | public function getIgnoredClasses(): array |
|
197 | |||
198 | /** |
||
199 | * @param string[] $resource |
||
200 | * |
||
201 | * @throws \InvalidArgumentException |
||
202 | * |
||
203 | * @return Route |
||
204 | */ |
||
205 | 29 | public function read(RestRouteCollection $collection, \ReflectionMethod $method, $resource) |
|
206 | { |
||
207 | // check that every route parent has non-empty singular name |
||
208 | 29 | foreach ($this->parents as $parent) { |
|
209 | 5 | if (empty($parent) || '/' === substr($parent, -1)) { |
|
210 | throw new \InvalidArgumentException('Every parent controller must have `get{SINGULAR}Action(\$id)` method where {SINGULAR} is a singular form of associated object'); |
||
211 | } |
||
212 | } |
||
213 | |||
214 | // if method is not readable - skip |
||
215 | 29 | if (!$this->isMethodReadable($method)) { |
|
216 | 15 | return; |
|
217 | } |
||
218 | |||
219 | // if we can't get http-method and resources from method name - skip |
||
220 | 29 | $httpMethodAndResources = $this->getHttpMethodAndResourcesFromMethod($method, $resource); |
|
221 | 29 | if (!$httpMethodAndResources) { |
|
222 | 27 | return; |
|
223 | } |
||
224 | |||
225 | 29 | [$httpMethod, $resources, $isCollection, $isInflectable] = $httpMethodAndResources; |
|
226 | 29 | $arguments = $this->getMethodArguments($method); |
|
227 | |||
228 | // if we have only 1 resource & 1 argument passed, then it's object call, so |
||
229 | // we can set collection singular name |
||
230 | 29 | if (1 === count($resources) && 1 === count($arguments) - count($this->parents)) { |
|
231 | 22 | $collection->setSingularName($resources[0]); |
|
232 | } |
||
233 | |||
234 | // if we have parents passed - merge them with own resource names |
||
235 | 29 | if (count($this->parents)) { |
|
236 | 5 | $resources = array_merge($this->parents, $resources); |
|
237 | } |
||
238 | |||
239 | 29 | if (empty($resources)) { |
|
240 | 1 | $resources[] = null; |
|
241 | } |
||
242 | |||
243 | 29 | $routeName = $httpMethod.$this->generateRouteName($resources); |
|
244 | 29 | $urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod); |
|
245 | |||
246 | // if passed method is not valid HTTP method then it's either |
||
247 | // a hypertext driver, a custom object (PUT) or collection (GET) |
||
248 | // method |
||
249 | 29 | if (!in_array($httpMethod, $this->availableHTTPMethods)) { |
|
250 | 19 | $urlParts[] = $httpMethod; |
|
251 | 19 | $httpMethod = $this->getCustomHttpMethod($httpMethod, $resources, $arguments); |
|
252 | } |
||
253 | |||
254 | // generated parameters |
||
255 | 29 | $routeName = strtolower($routeName); |
|
256 | 29 | $path = implode('/', $urlParts); |
|
257 | 29 | $defaults = ['_controller' => $method->getName()]; |
|
258 | 29 | $requirements = []; |
|
259 | 29 | $options = []; |
|
260 | 29 | $host = ''; |
|
261 | 29 | $versionCondition = $this->getVersionCondition(); |
|
262 | 29 | $versionRequirement = $this->getVersionRequirement(); |
|
263 | |||
264 | 29 | $annotations = $this->readRouteAnnotation($method); |
|
265 | 29 | if (!empty($annotations)) { |
|
266 | 9 | foreach ($annotations as $annotation) { |
|
267 | 9 | $path = implode('/', $urlParts); |
|
268 | 9 | $defaults = ['_controller' => $method->getName()]; |
|
269 | 9 | $requirements = []; |
|
270 | 9 | $options = []; |
|
271 | 9 | $methods = explode('|', $httpMethod); |
|
272 | |||
273 | 9 | $annoRequirements = $annotation->getRequirements(); |
|
274 | 9 | $annoMethods = $annotation->getMethods(); |
|
275 | |||
276 | 9 | if (!empty($annoMethods)) { |
|
277 | 9 | $methods = $annoMethods; |
|
278 | } |
||
279 | |||
280 | 9 | $path = null !== $annotation->getPath() ? $this->routePrefix.$annotation->getPath() : $path; |
|
281 | 9 | $requirements = array_merge($requirements, $annoRequirements); |
|
282 | 9 | $options = array_merge($options, $annotation->getOptions()); |
|
283 | 9 | $defaults = array_merge($defaults, $annotation->getDefaults()); |
|
284 | 9 | $host = $annotation->getHost(); |
|
285 | 9 | $schemes = $annotation->getSchemes(); |
|
286 | |||
287 | 9 | if ($this->hasVersionPlaceholder($path)) { |
|
288 | 1 | $combinedCondition = $annotation->getCondition(); |
|
289 | 1 | $requirements = array_merge($versionRequirement, $requirements); |
|
290 | } else { |
||
291 | 9 | $combinedCondition = $this->combineConditions($versionCondition, $annotation->getCondition()); |
|
292 | } |
||
293 | |||
294 | 9 | $this->includeFormatIfNeeded($path, $requirements); |
|
295 | |||
296 | // add route to collection |
||
297 | 9 | $route = new Route( |
|
298 | 9 | $path, |
|
299 | $defaults, |
||
300 | $requirements, |
||
301 | $options, |
||
302 | $host, |
||
303 | $schemes, |
||
304 | $methods, |
||
305 | $combinedCondition |
||
306 | ); |
||
307 | 9 | $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable, $annotation); |
|
308 | } |
||
309 | } else { |
||
310 | 29 | if ($this->hasVersionPlaceholder($path)) { |
|
311 | $versionCondition = null; |
||
312 | $requirements = $versionRequirement; |
||
313 | } |
||
314 | |||
315 | 29 | $this->includeFormatIfNeeded($path, $requirements); |
|
316 | |||
317 | 29 | $methods = explode('|', strtoupper($httpMethod)); |
|
318 | |||
319 | // add route to collection |
||
320 | 29 | $route = new Route( |
|
321 | 29 | $path, |
|
322 | $defaults, |
||
323 | $requirements, |
||
324 | $options, |
||
325 | $host, |
||
326 | 29 | [], |
|
327 | $methods, |
||
328 | $versionCondition |
||
329 | ); |
||
330 | 29 | $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable); |
|
331 | } |
||
332 | 29 | } |
|
333 | |||
334 | 29 | private function getVersionCondition(): ?string |
|
342 | |||
343 | 9 | private function combineConditions(?string $conditionOne, ?string $conditionTwo): ?string |
|
355 | |||
356 | 29 | private function getVersionRequirement(): array |
|
364 | |||
365 | 29 | private function hasVersionPlaceholder(string $path): bool |
|
369 | |||
370 | 29 | private function includeFormatIfNeeded(string &$path, array &$requirements) |
|
380 | |||
381 | 29 | private function isMethodReadable(\ReflectionMethod $method): bool |
|
402 | |||
403 | /** |
||
404 | * @param string[] $resource |
||
405 | * |
||
406 | * @return bool|array |
||
407 | */ |
||
408 | 29 | private function getHttpMethodAndResourcesFromMethod(\ReflectionMethod $method, array $resource) |
|
444 | |||
445 | /** |
||
446 | * @return \ReflectionParameter[] |
||
447 | */ |
||
448 | 29 | private function getMethodArguments(\ReflectionMethod $method): array |
|
489 | |||
490 | /** |
||
491 | * @param string|bool $resource |
||
492 | */ |
||
493 | 29 | private function generateResourceName($resource): string |
|
501 | |||
502 | /** |
||
503 | * @param string[] $resources |
||
504 | */ |
||
505 | 29 | private function generateRouteName(array $resources): string |
|
516 | |||
517 | /** |
||
518 | * @param string[] $resources |
||
519 | * @param \ReflectionParameter[] $arguments |
||
520 | */ |
||
521 | 29 | private function generateUrlParts(array $resources, array $arguments, string $httpMethod): array |
|
555 | |||
556 | /** |
||
557 | * @param string[] $resources |
||
558 | * @param \ReflectionParameter[] $arguments |
||
559 | */ |
||
560 | 19 | private function getCustomHttpMethod(string $httpMethod, array $resources, array $arguments): string |
|
576 | |||
577 | /** |
||
578 | * @return RouteAnnotation[] |
||
579 | */ |
||
580 | 29 | private function readRouteAnnotation(\ReflectionMethod $reflectionMethod): array |
|
590 | |||
591 | 29 | private function readClassAnnotation(\ReflectionClass $reflectionClass, string $annotationName): ?RouteAnnotation |
|
601 | |||
602 | 29 | private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, string $annotationName): ?RouteAnnotation |
|
612 | |||
613 | /** |
||
614 | * @return RouteAnnotation[] |
||
615 | */ |
||
616 | 29 | private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, string $annotationName): array |
|
631 | |||
632 | 29 | private function addRoute(RestRouteCollection $collection, string $routeName, Route $route, bool $isCollection, bool $isInflectable, RouteAnnotation $annotation = null) |
|
655 | } |
||
656 |
If you suppress an error, we recommend checking for the error condition explicitly: