Complex classes like AbstractRestResourceController 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 AbstractRestResourceController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
29 | abstract class AbstractRestResourceController implements RestResourceControllerInterface |
||
30 | { |
||
31 | /** |
||
32 | * @var Normalizer |
||
33 | */ |
||
34 | private $normalizer; |
||
35 | |||
36 | /** |
||
37 | * @var ValidatorInterface |
||
38 | */ |
||
39 | private $validator; |
||
40 | |||
41 | /** |
||
42 | * @var RestRequestParserInterface |
||
43 | */ |
||
44 | private $requestParser; |
||
45 | |||
46 | /** |
||
47 | * @var RequestStack |
||
48 | */ |
||
49 | private $requestStack; |
||
50 | |||
51 | /** |
||
52 | * @var RestMetadataFactory |
||
53 | */ |
||
54 | private $metadataFactory; |
||
55 | |||
56 | /** |
||
57 | * @var PropertyAccessorInterface |
||
58 | */ |
||
59 | private $propertyAccessor; |
||
60 | |||
61 | /** |
||
62 | * @var AuthorizationCheckerInterface |
||
63 | */ |
||
64 | private $authorizationChecker; |
||
65 | |||
66 | 80 | public function __construct( |
|
67 | RestRequestParserInterface $requestParser, |
||
68 | Normalizer $normalizer, |
||
69 | ValidatorInterface $validator, |
||
70 | RequestStack $requestStack, |
||
71 | RestMetadataFactory $metadataFactory, |
||
72 | PropertyAccessorInterface $propertyAccessor |
||
73 | ) { |
||
74 | 80 | $this->requestParser = $requestParser; |
|
75 | 80 | $this->normalizer = $normalizer; |
|
76 | 80 | $this->validator = $validator; |
|
77 | 80 | $this->requestStack = $requestStack; |
|
78 | 80 | $this->metadataFactory = $metadataFactory; |
|
79 | 80 | $this->propertyAccessor = $propertyAccessor; |
|
80 | 80 | } |
|
81 | |||
82 | /** |
||
83 | * {@inheritdoc} |
||
84 | */ |
||
85 | 10 | public function listAction(Request $request) |
|
86 | { |
||
87 | 10 | $page = $request->query->get('page', 1); |
|
88 | 10 | $perPage = $request->query->get('perPage', 50); |
|
89 | |||
90 | 10 | $this->assertMethodGranted(Method::LIST); |
|
91 | |||
92 | 6 | $listResult = $this->listEntities($page, $perPage); |
|
93 | |||
94 | 6 | $response = new JsonResponse(); |
|
95 | |||
96 | 6 | if ($listResult instanceof Paginator) { |
|
97 | 6 | $entities = iterator_to_array($listResult->getIterator()); |
|
98 | 6 | $total = $listResult->count(); |
|
99 | 6 | $this->addPaginationHeaders($response, $page, $perPage, $total); |
|
100 | } else { |
||
101 | $entities = $listResult; |
||
102 | } |
||
103 | |||
104 | 6 | $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request)); |
|
105 | |||
106 | 6 | $response->setData($content); |
|
107 | |||
108 | 6 | return $response; |
|
109 | } |
||
110 | |||
111 | /** |
||
112 | * {@inheritdoc} |
||
113 | */ |
||
114 | 14 | public function postAction(Request $request) |
|
115 | { |
||
116 | 14 | $this->assertMethodGranted(Method::POST); |
|
117 | |||
118 | 12 | $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass()); |
|
119 | 12 | $entity = $this->postProcessPostedEntity($entity); |
|
120 | |||
121 | 12 | $errors = $this->getValidator()->validate($entity); |
|
122 | 12 | if ($errors->count() > 0) { |
|
123 | 2 | return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST); |
|
124 | } |
||
125 | |||
126 | 10 | $entity = $this->createEntity($entity); |
|
127 | |||
128 | 10 | $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request)); |
|
129 | |||
130 | 10 | return new JsonResponse($content, Response::HTTP_CREATED); |
|
131 | } |
||
132 | |||
133 | /** |
||
134 | * {@inheritdoc} |
||
135 | */ |
||
136 | 32 | public function getAction(Request $request, $id) |
|
137 | { |
||
138 | 32 | $entity = $this->fetchEntity($id); |
|
139 | 30 | $this->assertMethodGranted(Method::GET, $entity); |
|
140 | |||
141 | 28 | $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request)); |
|
142 | |||
143 | 28 | return new JsonResponse($content); |
|
144 | } |
||
145 | |||
146 | /** |
||
147 | * {@inheritdoc} |
||
148 | */ |
||
149 | 12 | public function putAction(Request $request, $id) |
|
150 | { |
||
151 | 12 | $entity = $this->fetchEntity($id); |
|
152 | 12 | $this->assertMethodGranted(Method::PUT, $entity); |
|
153 | 10 | $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass(), $entity); |
|
154 | 10 | $entity = $this->postProcessPuttedEntity($entity); |
|
155 | |||
156 | 10 | $errors = $this->getValidator()->validate($entity); |
|
157 | 10 | if ($errors->count() > 0) { |
|
158 | return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST); |
||
159 | } |
||
160 | |||
161 | 10 | $entity = $this->updateEntity($entity); |
|
162 | |||
163 | 10 | $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request)); |
|
164 | |||
165 | 10 | return new JsonResponse($content); |
|
166 | } |
||
167 | |||
168 | /** |
||
169 | * {@inheritdoc} |
||
170 | */ |
||
171 | 4 | public function deleteAction(Request $request, $id) |
|
172 | { |
||
173 | 4 | $entity = $this->fetchEntity($id); |
|
174 | 4 | $this->assertMethodGranted(Method::DELETE, $entity); |
|
175 | 2 | $this->removeEntity($entity); |
|
176 | |||
177 | 2 | return new JsonResponse(null, Response::HTTP_NO_CONTENT); |
|
178 | } |
||
179 | |||
180 | /** |
||
181 | * {@inheritdoc} |
||
182 | */ |
||
183 | 6 | public function listSubresourceAction(Request $request, $id, string $subresource) |
|
184 | { |
||
185 | 6 | $page = $request->query->get('page', 1); |
|
186 | 6 | $perPage = $request->query->get('perPage', 50); |
|
187 | |||
188 | 6 | $entity = $this->fetchEntity($id); |
|
189 | 6 | $this->assertSubResourceMethodGranted(Method::LIST, $entity, $subresource); |
|
190 | |||
191 | 6 | $listResult = $this->listSubresource($entity, $subresource, $page, $perPage); |
|
192 | |||
193 | 6 | $response = new JsonResponse(); |
|
194 | |||
195 | 6 | if ($listResult instanceof Paginator) { |
|
196 | 6 | $entities = iterator_to_array($listResult->getIterator()); |
|
197 | 6 | $total = $listResult->count(); |
|
198 | 6 | $this->addPaginationHeaders($response, $page, $perPage, $total); |
|
199 | } else { |
||
200 | $entities = $listResult; |
||
201 | } |
||
202 | |||
203 | 6 | $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request)); |
|
204 | |||
205 | 6 | $response->setData($content); |
|
206 | |||
207 | 6 | return $response; |
|
208 | } |
||
209 | |||
210 | /** |
||
211 | * {@inheritdoc} |
||
212 | */ |
||
213 | 4 | public function postSubresourceAction(Request $request, $id, string $subresource) |
|
214 | { |
||
215 | 4 | $parent = $this->fetchEntity($id); |
|
216 | 4 | $this->assertSubResourceMethodGranted(Method::POST, $parent, $subresource); |
|
217 | |||
218 | 2 | $restRequestParser = $this->getRequestParser(); |
|
219 | 2 | $entity = $restRequestParser->parseEntity($request, $this->getSubResourceEntityClass($subresource)); |
|
220 | 2 | $entity = $this->buildAssociation($parent, $subresource, $entity); |
|
221 | 2 | $entity = $this->postProcessSubResourcePostedEntity($parent, $subresource, $entity); |
|
222 | |||
223 | 2 | $errors = $this->getValidator()->validate($entity); |
|
224 | |||
225 | 2 | if ($errors->count() > 0) { |
|
226 | return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST); |
||
227 | } |
||
228 | |||
229 | 2 | $entity = $this->createAssociation($entity); |
|
230 | |||
231 | 2 | $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request)); |
|
232 | |||
233 | 2 | return new JsonResponse($content, Response::HTTP_CREATED); |
|
234 | } |
||
235 | |||
236 | /** |
||
237 | * {@inheritdoc} |
||
238 | */ |
||
239 | 12 | public function putSubresourceAction(Request $request, $id, string $subresource, $subId) |
|
240 | { |
||
241 | 12 | $parent = $this->fetchEntity($id); |
|
242 | 12 | $this->assertSubResourceMethodGranted(Method::PUT, $parent, $subresource); |
|
243 | 12 | $this->addAssociation($parent, $subresource, $subId); |
|
244 | |||
245 | 12 | return new JsonResponse(null, Response::HTTP_NO_CONTENT); |
|
246 | } |
||
247 | |||
248 | /** |
||
249 | * {@inheritdoc} |
||
250 | */ |
||
251 | 12 | public function deleteSubresourceAction(Request $request, $id, string $subresource, $subId = null) |
|
252 | { |
||
253 | 12 | $parent = $this->fetchEntity($id); |
|
254 | 12 | $this->assertSubResourceMethodGranted(Method::DELETE, $parent, $subresource); |
|
255 | 12 | $this->removeAssociation($parent, $subresource, $subId); |
|
256 | |||
257 | 12 | return new JsonResponse(null, Response::HTTP_NO_CONTENT); |
|
258 | } |
||
259 | |||
260 | /** |
||
261 | * @param object $entity |
||
262 | * |
||
263 | * @return object |
||
264 | */ |
||
265 | 12 | protected function postProcessPostedEntity($entity) |
|
266 | { |
||
267 | 12 | return $entity; |
|
268 | } |
||
269 | |||
270 | /** |
||
271 | * @param object $entity |
||
272 | * |
||
273 | * @return object |
||
274 | */ |
||
275 | 10 | protected function postProcessPuttedEntity($entity) |
|
276 | { |
||
277 | 10 | return $entity; |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * @param object $parent |
||
282 | * @param string $subresource |
||
283 | * @param object $entity |
||
284 | * |
||
285 | * @return object |
||
286 | */ |
||
287 | 2 | protected function postProcessSubResourcePostedEntity($parent, $subresource, $entity) |
|
288 | { |
||
289 | 2 | return $entity; |
|
290 | } |
||
291 | |||
292 | 80 | protected function getEntityClass() |
|
293 | { |
||
294 | 80 | return $this->getCurrentRequest()->attributes->get('_entityClass'); |
|
295 | } |
||
296 | |||
297 | 2 | protected function getSubResourceEntityClass($subresource) |
|
298 | { |
||
299 | /** @var PropertyMetadata $propertyMetadata */ |
||
300 | 2 | $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource]; |
|
301 | |||
302 | 2 | return $propertyMetadata->getType(); |
|
303 | } |
||
304 | |||
305 | 80 | protected function getCurrentRequest() |
|
306 | { |
||
307 | 80 | return $this->getRequestStack()->getCurrentRequest(); |
|
308 | } |
||
309 | |||
310 | 70 | protected function assertMethodGranted(string $methodName, $entity = null) |
|
311 | { |
||
312 | 70 | $method = $this->getClassMetadata()->getMethod($methodName); |
|
313 | 70 | if ($method !== null && null !== $right = $method->right) { |
|
314 | 36 | $this->assertRightGranted($right, $entity); |
|
315 | } |
||
316 | 58 | } |
|
317 | |||
318 | /** |
||
319 | * @param string $methodName |
||
320 | * @param object $entity |
||
321 | * @param string $subresource |
||
322 | */ |
||
323 | 30 | protected function assertSubResourceMethodGranted($methodName, $entity, string $subresource): void |
|
324 | { |
||
325 | 30 | $classMetadata = $this->getClassMetadata(); |
|
326 | /** @var PropertyMetadata $propertyMetadata */ |
||
327 | 30 | $propertyMetadata = $classMetadata->propertyMetadata[$subresource]; |
|
328 | 30 | $method = $propertyMetadata->getMethod($methodName); |
|
329 | 30 | if (null !== $right = $method->right) { |
|
330 | 14 | $this->assertRightGranted($right, $entity); |
|
331 | } |
||
332 | 28 | } |
|
333 | |||
334 | /** |
||
335 | * @return ClassMetadata |
||
336 | */ |
||
337 | 80 | protected function getClassMetadata() |
|
338 | { |
||
339 | 80 | $metaDataFactory = $this->getMetadataFactory(); |
|
340 | /** @var ClassMetadata $classMetaData */ |
||
341 | 80 | $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass()); |
|
342 | |||
343 | 80 | return $classMetaData; |
|
344 | } |
||
345 | |||
346 | protected function resolveSubject($entity, $propertyPath) |
||
347 | { |
||
348 | if ('this' === $propertyPath) { |
||
349 | return $entity; |
||
350 | } |
||
351 | $propertyAccessor = $this->getPropertyAccessor(); |
||
352 | |||
353 | return $propertyAccessor->getValue($entity, $propertyPath); |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * @param Right $right |
||
358 | * @param object $entity |
||
359 | */ |
||
360 | 46 | protected function assertRightGranted(Right $right, $entity = null) |
|
361 | { |
||
362 | 46 | $propertyPath = $right->propertyPath; |
|
363 | 46 | if (null === $propertyPath || null == $entity) { |
|
364 | 46 | $this->denyAccessUnlessGranted($right->attributes); |
|
365 | } else { |
||
366 | $subject = $this->resolveSubject($entity, $propertyPath); |
||
367 | $this->denyAccessUnlessGranted($right->attributes, $subject); |
||
368 | } |
||
369 | 32 | } |
|
370 | |||
371 | 62 | protected function parseIncludes(Request $request) |
|
372 | { |
||
373 | 62 | $defaultIncludes = $request->attributes->get('_defaultincludes'); |
|
374 | 62 | if (null == $defaultIncludes) { |
|
375 | 30 | $defaultIncludes = []; |
|
376 | } |
||
377 | |||
378 | 62 | $includeString = $request->query->get('include'); |
|
379 | 62 | if (empty($includeString)) { |
|
380 | 48 | $includes = []; |
|
381 | } else { |
||
382 | 16 | $includes = explode(',', $includeString); |
|
383 | } |
||
384 | |||
385 | 62 | return array_merge($defaultIncludes, $includes); |
|
386 | } |
||
387 | |||
388 | 46 | protected function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.') |
|
389 | { |
||
390 | 46 | $authorizationChecker = $this->getAuthorizationChecker(); |
|
391 | 46 | if (null === $authorizationChecker) { |
|
392 | throw new AccessDeniedException('No authorization checker configured'); |
||
393 | } |
||
394 | |||
395 | 46 | if (!$authorizationChecker->isGranted($attributes, $object)) { |
|
396 | 14 | throw new AccessDeniedException($message); |
|
397 | } |
||
398 | 32 | } |
|
399 | |||
400 | 2 | protected function parseConstraintViolations(ConstraintViolationListInterface $errors) |
|
401 | { |
||
402 | 2 | $data = []; |
|
403 | /** @var ConstraintViolationInterface $error */ |
||
404 | 2 | foreach ($errors as $error) { |
|
405 | 2 | $data[] = [ |
|
406 | 2 | 'propertyPath' => $error->getPropertyPath(), |
|
407 | 2 | 'message' => $error->getMessage(), |
|
408 | 2 | 'value' => $error->getInvalidValue() |
|
409 | ]; |
||
410 | } |
||
411 | |||
412 | 2 | return $data; |
|
413 | } |
||
414 | |||
415 | 12 | protected function addPaginationHeaders(Response $response, int $page, int $perPage, int $total) |
|
416 | { |
||
417 | 12 | $response->headers->add( |
|
418 | [ |
||
419 | 12 | 'x-pagination-current-page' => $page, |
|
420 | 12 | 'x-pagination-per-page' => $perPage, |
|
421 | 12 | 'x-pagination-total' => $total, |
|
422 | 12 | 'x-pagination-total-pages' => (int)(($total - 1) / $perPage + 1) |
|
423 | ] |
||
424 | ); |
||
425 | 12 | } |
|
426 | |||
427 | /** |
||
428 | * {@inheritdoc} |
||
429 | */ |
||
430 | 62 | protected function getNormalizer() |
|
431 | { |
||
432 | 62 | return $this->normalizer; |
|
433 | } |
||
434 | |||
435 | /** |
||
436 | * {@inheritdoc} |
||
437 | */ |
||
438 | 24 | protected function getValidator() |
|
439 | { |
||
440 | 24 | return $this->validator; |
|
441 | } |
||
442 | |||
443 | /** |
||
444 | * {@inheritdoc} |
||
445 | */ |
||
446 | 24 | protected function getRequestParser() |
|
447 | { |
||
448 | 24 | return $this->requestParser; |
|
449 | } |
||
450 | |||
451 | /** |
||
452 | * {@inheritdoc} |
||
453 | */ |
||
454 | 80 | protected function getRequestStack() |
|
455 | { |
||
456 | 80 | return $this->requestStack; |
|
457 | } |
||
458 | |||
459 | /** |
||
460 | * {@inheritdoc} |
||
461 | */ |
||
462 | 80 | protected function getMetadataFactory() |
|
463 | { |
||
464 | 80 | return $this->metadataFactory; |
|
465 | } |
||
466 | |||
467 | /** |
||
468 | * {@inheritdoc} |
||
469 | */ |
||
470 | protected function getPropertyAccessor() |
||
471 | { |
||
472 | return $this->propertyAccessor; |
||
473 | } |
||
474 | |||
475 | /** |
||
476 | * {@inheritdoc} |
||
477 | */ |
||
478 | 46 | protected function getAuthorizationChecker(): ?AuthorizationCheckerInterface |
|
479 | { |
||
480 | 46 | return $this->authorizationChecker; |
|
481 | } |
||
482 | |||
483 | /** |
||
484 | * @param AuthorizationCheckerInterface $authorizationChecker |
||
485 | */ |
||
486 | 76 | public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker): void |
|
487 | { |
||
488 | 76 | $this->authorizationChecker = $authorizationChecker; |
|
489 | 76 | } |
|
490 | |||
491 | /** |
||
492 | * @param int $page |
||
493 | * @param int $perPage |
||
494 | * |
||
495 | * @return Paginator|array |
||
496 | */ |
||
497 | abstract protected function listEntities(int $page = 1, int $perPage = 50); |
||
498 | |||
499 | /** |
||
500 | * @param int|string $id |
||
501 | * |
||
502 | * @return object |
||
503 | * |
||
504 | * @throws NotFoundHttpException Thrown if entity with the given id could not be found. |
||
505 | */ |
||
506 | abstract protected function fetchEntity($id); |
||
507 | |||
508 | /** |
||
509 | * @param object $entity |
||
510 | * |
||
511 | * @return object |
||
512 | */ |
||
513 | abstract protected function createEntity($entity); |
||
514 | |||
515 | /** |
||
516 | * @param object $entity |
||
517 | * |
||
518 | * @return object |
||
519 | * |
||
520 | * @throws NotFoundHttpException Thrown if entity with the given id could not be found. |
||
521 | */ |
||
522 | abstract protected function updateEntity($entity); |
||
523 | |||
524 | /** |
||
525 | * @param $entity |
||
526 | * |
||
527 | * @throws NotFoundHttpException Thrown if entity with the given id could not be found. |
||
528 | */ |
||
529 | abstract protected function removeEntity($entity); |
||
530 | |||
531 | /** |
||
532 | * @param object $entity |
||
533 | * @param string $subresource |
||
534 | * @param int $page |
||
535 | * @param int $perPage |
||
536 | * |
||
537 | * @return Paginator|array |
||
538 | */ |
||
539 | abstract protected function listSubresource($entity, string $subresource, int $page = 1, int $perPage = 50); |
||
540 | |||
541 | /** |
||
542 | * @param object $parent |
||
543 | * @param string $subresource |
||
544 | * |
||
545 | * @return object |
||
546 | */ |
||
547 | abstract protected function buildAssociation($parent, string $subresource, $entity); |
||
548 | |||
549 | /** |
||
550 | * @param object $associatedEntity |
||
551 | * |
||
552 | * @return object |
||
553 | */ |
||
554 | abstract protected function createAssociation($associatedEntity); |
||
555 | |||
556 | /** |
||
557 | * @param object $parent |
||
558 | * @param string $subresource |
||
559 | * @param int|string $subId |
||
560 | * |
||
561 | * @return object |
||
562 | */ |
||
563 | abstract protected function addAssociation($parent, string $subresource, $subId); |
||
564 | |||
565 | /** |
||
566 | * @param object $parent |
||
567 | * @param string $subresource |
||
568 | * @param int|string|null $subId |
||
569 | * |
||
570 | * @return mixed |
||
571 | */ |
||
572 | abstract protected function removeAssociation($parent, string $subresource, $subId = null); |
||
573 | } |
||
574 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.