Completed
Push — master ( faed70...56c59d )
by Philip
13:07
created

AbstractRestResourceController   C

Complexity

Total Complexity 40

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 92.21%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 19
dl 0
loc 458
ccs 142
cts 154
cp 0.9221
rs 5.9206
c 0
b 0
f 0

40 Methods

Rating   Name   Duplication   Size   Complexity  
B listAction() 0 25 2
A postAction() 0 18 2
A getAction() 0 9 1
A putAction() 0 18 2
A deleteAction() 0 8 1
B listSubresourceAction() 0 26 2
A postSubresourceAction() 0 22 2
A putSubresourceAction() 0 8 1
A deleteSubresourceAction() 0 8 1
A postProcessPostedEntity() 0 4 1
A postProcessPuttedEntity() 0 4 1
A postProcessSubResourcePostedEntity() 0 4 1
A getEntityClass() 0 4 1
A getSubResourceEntityClass() 0 7 1
A getCurrentRequest() 0 4 1
A assertMethodGranted() 0 7 3
A assertSubResourceMethodGranted() 0 10 2
A getClassMetadata() 0 8 1
A resolveSubject() 0 9 2
A assertRightGranted() 0 10 3
A parseIncludes() 0 16 3
A denyAccessUnlessGranted() 0 11 3
A parseConstraintViolations() 0 14 2
A addPaginationHeaders() 0 11 1
listEntities() 0 1 ?
fetchEntity() 0 1 ?
createEntity() 0 1 ?
updateEntity() 0 1 ?
removeEntity() 0 1 ?
listSubresource() 0 1 ?
createAssociation() 0 1 ?
addAssociation() 0 1 ?
removeAssociation() 0 1 ?
getNormalizer() 0 1 ?
getValidator() 0 1 ?
getRequestParser() 0 1 ?
getRequestStack() 0 1 ?
getMetadataFactory() 0 1 ?
getPropertyAccessor() 0 1 ?
getAuthorizationChecker() 0 1 ?

How to fix   Complexity   

Complex Class

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
2
3
namespace Dontdrinkandroot\RestBundle\Controller;
4
5
use Doctrine\ORM\Tools\Pagination\Paginator;
6
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
8
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
9
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
10
use Dontdrinkandroot\RestBundle\Service\Normalizer;
11
use Dontdrinkandroot\RestBundle\Service\RestRequestParser;
12
use Metadata\MetadataFactoryInterface;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
19
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
20
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
21
use Symfony\Component\Validator\ConstraintViolationInterface;
22
use Symfony\Component\Validator\ConstraintViolationListInterface;
23
use Symfony\Component\Validator\Validator\ValidatorInterface;
24
25
/**
26
 * @author Philip Washington Sorst <[email protected]>
27
 */
28
abstract class AbstractRestResourceController implements RestResourceControllerInterface
29
{
30
    /**
31
     * {@inheritdoc}
32
     */
33 10
    public function listAction(Request $request)
34
    {
35 10
        $page = $request->query->get('page', 1);
36 10
        $perPage = $request->query->get('perPage', 50);
37
38 10
        $this->assertMethodGranted(Method::LIST);
39
40 6
        $listResult = $this->listEntities($page, $perPage);
41
42 6
        $response = new JsonResponse();
43
44 6
        if ($listResult instanceof Paginator) {
45 6
            $entities = iterator_to_array($listResult->getIterator());
46 6
            $total = $listResult->count();
47 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
48
        } else {
49
            $entities = $listResult;
50
        }
51
52 6
        $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request));
53
54 6
        $response->setData($content);
55
56 6
        return $response;
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 14
    public function postAction(Request $request)
63
    {
64 14
        $this->assertMethodGranted(Method::POST);
65
66 12
        $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass());
67 12
        $entity = $this->postProcessPostedEntity($entity);
68
69 12
        $errors = $this->getValidator()->validate($entity);
70 12
        if ($errors->count() > 0) {
71 2
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
72
        }
73
74 10
        $entity = $this->createEntity($entity);
75
76 10
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
77
78 10
        return new JsonResponse($content, Response::HTTP_CREATED);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 32
    public function getAction(Request $request, $id)
85
    {
86 32
        $entity = $this->fetchEntity($id);
87 30
        $this->assertMethodGranted(Method::GET, $entity);
88
89 28
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
90
91 28
        return new JsonResponse($content);
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 12
    public function putAction(Request $request, $id)
98
    {
99 12
        $entity = $this->fetchEntity($id);
100 12
        $this->assertMethodGranted(Method::PUT, $entity);
101 10
        $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass(), $entity);
102 10
        $entity = $this->postProcessPuttedEntity($entity);
103
104 10
        $errors = $this->getValidator()->validate($entity);
105 10
        if ($errors->count() > 0) {
106
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
107
        }
108
109 10
        $entity = $this->updateEntity($entity);
110
111 10
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
112
113 10
        return new JsonResponse($content);
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 4
    public function deleteAction(Request $request, $id)
120
    {
121 4
        $entity = $this->fetchEntity($id);
122 4
        $this->assertMethodGranted(Method::DELETE, $entity);
123 2
        $this->removeEntity($entity);
124
125 2
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 6
    public function listSubresourceAction(Request $request, $id, string $subresource)
132
    {
133 6
        $page = $request->query->get('page', 1);
134 6
        $perPage = $request->query->get('perPage', 50);
135
136 6
        $entity = $this->fetchEntity($id);
137 6
        $this->assertSubResourceMethodGranted(Method::LIST, $entity, $subresource);
138
139 6
        $listResult = $this->listSubresource($entity, $subresource, $page, $perPage);
140
141 6
        $response = new JsonResponse();
142
143 6
        if ($listResult instanceof Paginator) {
144 6
            $entities = iterator_to_array($listResult->getIterator());
145 6
            $total = $listResult->count();
146 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
147
        } else {
148
            $entities = $listResult;
149
        }
150
151 6
        $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request));
152
153 6
        $response->setData($content);
154
155 6
        return $response;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 4
    public function postSubresourceAction(Request $request, $id, string $subresource)
162
    {
163 4
        $parent = $this->fetchEntity($id);
164 4
        $this->assertSubResourceMethodGranted(Method::POST, $parent, $subresource);
165
166 2
        $restRequestParser = $this->getRequestParser();
167 2
        $entity = $restRequestParser->parseEntity($request, $this->getSubResourceEntityClass($subresource));
168
169 2
        $entity = $this->postProcessSubResourcePostedEntity($parent, $subresource, $entity);
170
171 2
        $errors = $this->getValidator()->validate($entity);
172
173 2
        if ($errors->count() > 0) {
174
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
175
        }
176
177 2
        $entity = $this->createAssociation($parent, $subresource, $entity);
178
179 2
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
180
181 2
        return new JsonResponse($content, Response::HTTP_CREATED);
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 12
    public function putSubresourceAction(Request $request, $id, string $subresource, $subId)
188
    {
189 12
        $parent = $this->fetchEntity($id);
190 12
        $this->assertSubResourceMethodGranted(Method::PUT, $parent, $subresource);
191 12
        $this->addAssociation($parent, $subresource, $subId);
192
193 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 12
    public function deleteSubresourceAction(Request $request, $id, string $subresource, $subId = null)
200
    {
201 12
        $parent = $this->fetchEntity($id);
202 12
        $this->assertSubResourceMethodGranted(Method::DELETE, $parent, $subresource);
203 12
        $this->removeAssociation($parent, $subresource, $subId);
204
205 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
206
    }
207
208
    /**
209
     * @param object $entity
210
     *
211
     * @return object
212
     */
213 12
    protected function postProcessPostedEntity($entity)
214
    {
215 12
        return $entity;
216
    }
217
218
    /**
219
     * @param object $entity
220
     *
221
     * @return object
222
     */
223 10
    protected function postProcessPuttedEntity($entity)
224
    {
225 10
        return $entity;
226
    }
227
228
    /**
229
     * @param object $parent
230
     * @param string $subresource
231
     * @param object $entity
232
     *
233
     * @return object
234
     */
235 2
    protected function postProcessSubResourcePostedEntity($parent, $subresource, $entity)
0 ignored issues
show
Unused Code introduced by
The parameter $parent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $subresource is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
236
    {
237 2
        return $entity;
238
    }
239
240 80
    protected function getEntityClass()
241
    {
242 80
        return $this->getCurrentRequest()->attributes->get('_entityClass');
243
    }
244
245 2
    protected function getSubResourceEntityClass($subresource)
246
    {
247
        /** @var PropertyMetadata $propertyMetadata */
248 2
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
249
250 2
        return $propertyMetadata->getType();
251
    }
252
253 80
    protected function getCurrentRequest()
254
    {
255 80
        return $this->getRequestStack()->getCurrentRequest();
256
    }
257
258 70
    protected function assertMethodGranted(string $methodName, $entity = null)
259
    {
260 70
        $method = $this->getClassMetadata()->getMethod($methodName);
261 70
        if ($method !== null && null !== $right = $method->right) {
262 36
            $this->assertRightGranted($right, $entity);
263
        }
264 58
    }
265
266
    /**
267
     * @param string $methodName
268
     * @param object $entity
269
     * @param string $subresource
270
     */
271 30
    protected function assertSubResourceMethodGranted($methodName, $entity, string $subresource): void
272
    {
273 30
        $classMetadata = $this->getClassMetadata();
274
        /** @var PropertyMetadata $propertyMetadata */
275 30
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
276 30
        $method = $propertyMetadata->getMethod($methodName);
277 30
        if (null !== $right = $method->right) {
278 14
            $this->assertRightGranted($right, $entity);
279
        }
280 28
    }
281
282
    /**
283
     * @return ClassMetadata
284
     */
285 80
    protected function getClassMetadata()
286
    {
287 80
        $metaDataFactory = $this->getMetadataFactory();
288
        /** @var ClassMetadata $classMetaData */
289 80
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
290
291 80
        return $classMetaData;
292
    }
293
294
    protected function resolveSubject($entity, $propertyPath)
295
    {
296
        if ('this' === $propertyPath) {
297
            return $entity;
298
        }
299
        $propertyAccessor = $this->getPropertyAccessor();
300
301
        return $propertyAccessor->getValue($entity, $propertyPath);
302
    }
303
304
    /**
305
     * @param Right  $right
306
     * @param object $entity
307
     */
308 46
    protected function assertRightGranted(Right $right, $entity = null)
309
    {
310 46
        $propertyPath = $right->propertyPath;
311 46
        if (null === $propertyPath || null == $entity) {
312 46
            $this->denyAccessUnlessGranted($right->attributes);
313
        } else {
314
            $subject = $this->resolveSubject($entity, $propertyPath);
315
            $this->denyAccessUnlessGranted($right->attributes, $subject);
316
        }
317 32
    }
318
319 62
    protected function parseIncludes(Request $request)
320
    {
321 62
        $defaultIncludes = $request->attributes->get('_defaultincludes');
322 62
        if (null == $defaultIncludes) {
323 30
            $defaultIncludes = [];
324
        }
325
326 62
        $includeString = $request->query->get('include');
327 62
        if (empty($includeString)) {
328 48
            $includes = [];
329
        } else {
330 16
            $includes = explode(',', $includeString);
331
        }
332
333 62
        return array_merge($defaultIncludes, $includes);
334
    }
335
336 46
    protected function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.')
337
    {
338 46
        $authorizationChecker = $this->getAuthorizationChecker();
339 46
        if (null === $authorizationChecker) {
340
            throw new AccessDeniedException('No authorization checker configured');
341
        }
342
343 46
        if (!$authorizationChecker->isGranted($attributes, $object)) {
344 14
            throw new AccessDeniedException($message);
345
        }
346 32
    }
347
348 2
    protected function parseConstraintViolations(ConstraintViolationListInterface $errors)
349
    {
350 2
        $data = [];
351
        /** @var ConstraintViolationInterface $error */
352 2
        foreach ($errors as $error) {
353 2
            $data[] = [
354 2
                'propertyPath' => $error->getPropertyPath(),
355 2
                'message'      => $error->getMessage(),
356 2
                'value'        => $error->getInvalidValue()
357
            ];
358
        }
359
360 2
        return $data;
361
    }
362
363 12
    protected function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
364
    {
365 12
        $response->headers->add(
366
            [
367 12
                'x-pagination-current-page' => $page,
368 12
                'x-pagination-per-page'     => $perPage,
369 12
                'x-pagination-total'        => $total,
370 12
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
371
            ]
372
        );
373 12
    }
374
375
    /**
376
     * @param int $page
377
     * @param int $perPage
378
     *
379
     * @return Paginator|array
380
     */
381
    abstract protected function listEntities(int $page = 1, int $perPage = 50);
382
383
    /**
384
     * @param int|string $id
385
     *
386
     * @return object
387
     *
388
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
389
     */
390
    abstract protected function fetchEntity($id);
391
392
    /**
393
     * @param object $entity
394
     *
395
     * @return object
396
     */
397
    abstract protected function createEntity($entity);
398
399
    /**
400
     * @param object $entity
401
     *
402
     * @return object
403
     *
404
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
405
     */
406
    abstract protected function updateEntity($entity);
407
408
    /**
409
     * @param $entity
410
     *
411
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
412
     */
413
    abstract protected function removeEntity($entity);
414
415
    /**
416
     * @param object $entity
417
     * @param string $subresource
418
     * @param int    $page
419
     * @param int    $perPage
420
     *
421
     * @return Paginator|array
422
     */
423
    abstract protected function listSubresource($entity, string $subresource, int $page = 1, int $perPage = 50);
424
425
    /**
426
     * @param object $parent
427
     * @param string $subresource
428
     *
429
     * @return object
430
     */
431
    abstract protected function createAssociation($parent, string $subresource, $entity);
432
433
    /**
434
     * @param object     $parent
435
     * @param string     $subresource
436
     * @param int|string $subId
437
     *
438
     * @return object
439
     */
440
    abstract protected function addAssociation($parent, string $subresource, $subId);
441
442
    /**
443
     * @param object          $parent
444
     * @param string          $subresource
445
     * @param int|string|null $subId
446
     *
447
     * @return mixed
448
     */
449
    abstract protected function removeAssociation($parent, string $subresource, $subId = null);
450
451
    /**
452
     * @return Normalizer
453
     */
454
    abstract protected function getNormalizer();
455
456
    /**
457
     * @return ValidatorInterface
458
     */
459
    abstract protected function getValidator();
460
461
    /**
462
     * @return RestRequestParser
463
     */
464
    abstract protected function getRequestParser();
465
466
    /**
467
     * @return RequestStack
468
     */
469
    abstract protected function getRequestStack();
470
471
    /**
472
     * @return MetadataFactoryInterface
473
     */
474
    abstract protected function getMetadataFactory();
475
476
    /**
477
     * @return PropertyAccessorInterface
478
     */
479
    abstract protected function getPropertyAccessor();
480
481
    /**
482
     * @return AuthorizationCheckerInterface
483
     */
484
    abstract protected function getAuthorizationChecker();
485
}
486