Completed
Push — standalone ( 26f344...4b1476 )
by Philip
02:42
created

EntityController   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 485
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 68.6%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 62
c 3
b 0
f 0
lcom 1
cbo 17
dl 0
loc 485
ccs 166
cts 242
cp 0.686
rs 3.5483

42 Methods

Rating   Name   Duplication   Size   Complexity  
A listAction() 0 20 1
A postAction() 0 20 2
A getAction() 0 10 1
A putAction() 0 21 2
A deleteAction() 0 8 1
B listSubresourceAction() 0 26 1
A postSubresourceAction() 0 15 2
A putSubresourceAction() 0 9 1
A deleteSubresourceAction() 0 9 1
B getService() 0 25 4
A parseRequest() 0 4 1
A postProcessPostedEntity() 0 4 1
A postProcessPuttedEntity() 0 4 1
A postProcessSubResourcePostedEntity() 0 4 1
A fetchEntity() 0 9 2
A listEntities() 0 6 1
A createEntity() 0 4 1
A updateEntity() 0 4 1
A listSubresource() 0 6 1
A getEntityClass() 0 4 1
A getShortName() 0 4 1
A getServiceId() 0 4 1
A getCurrentRequest() 0 4 1
A assertListGranted() 0 10 2
A assertPostGranted() 0 10 2
A assertGetGranted() 0 10 2
A assertPutGranted() 0 10 2
A assertDeleteGranted() 0 10 2
A assertSubresourceListGranted() 0 12 2
A assertSubresourcePostGranted() 0 12 2
A assertSubresourcePutGranted() 0 12 2
A assertSubresourceDeleteGranted() 0 12 2
A getClassMetadata() 0 8 1
A getSubResourceEntityClass() 0 7 1
A resolveSubject() 0 9 2
A assertRightGranted() 0 10 2
A getSubresourceSerializationGroups() 0 4 1
A saveSubResource() 0 4 1
A getSubresource() 0 4 1
A parseIncludes() 0 9 2
A parseConstraintViolations() 0 14 2
A addPaginationHeaders() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like EntityController 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 EntityController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Controller;
4
5
use Doctrine\Common\Util\Inflector;
6
use Doctrine\ORM\EntityManagerInterface;
7
use Doctrine\ORM\Tools\Pagination\Paginator;
8
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
9
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
10
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
11
use Dontdrinkandroot\Service\CrudServiceInterface;
12
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
17
use Symfony\Component\Validator\ConstraintViolationInterface;
18
use Symfony\Component\Validator\ConstraintViolationListInterface;
19
use Symfony\Component\Validator\Validator\ValidatorInterface;
20
21
class EntityController extends Controller
22
{
23 8
    public function listAction(Request $request)
24
    {
25 8
        $page = $request->query->get('page', 1);
26 8
        $perPage = $request->query->get('perPage', 50);
27
28 8
        $this->assertListGranted();
29
30 6
        $paginator = $this->listEntities($page, $perPage);
31
32 6
        $total = $paginator->count();
33 6
        $entities = $paginator->getIterator()->getArrayCopy();
34
35 6
        $normalizer = $this->get('ddr_rest.normalizer');
36 6
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
37
38 6
        $response = new JsonResponse($content, Response::HTTP_OK);
39 6
        $this->addPaginationHeaders($response, $page, $perPage, $total);
40
41 6
        return $response;
42
    }
43
44 2
    public function postAction(Request $request)
45
    {
46 2
        $this->assertPostGranted();
47
        $entity = $this->parseRequest($request, null, $this->getEntityClass());
48
        $entity = $this->postProcessPostedEntity($entity);
49
50
        /** @var ValidatorInterface $validator */
51
        $validator = $this->get('validator');
52
        $errors = $validator->validate($entity);
53
        if ($errors->count() > 0) {
54
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
55
        }
56
57
        $entity = $this->createEntity($entity);
58
59
        $normalizer = $this->get('ddr_rest.normalizer');
60
        $content = $normalizer->normalize($entity);
61
62
        return new JsonResponse($content, Response::HTTP_CREATED);
63
    }
64
65 8
    public function getAction(Request $request, $id)
66
    {
67 8
        $entity = $this->fetchEntity($id);
68 8
        $this->assertGetGranted($entity);
69
70 6
        $normalizer = $this->get('ddr_rest.normalizer');
71 6
        $content = $normalizer->normalize($entity, $this->parseIncludes($request));
72
73 6
        return new JsonResponse($content);
74
    }
75
76 6
    public function putAction(Request $request, $id)
77
    {
78 6
        $entity = $this->fetchEntity($id);
79 6
        $this->assertPutGranted($entity);
80 2
        $entity = $this->parseRequest($request, $entity, $this->getEntityClass());
81 2
        $entity = $this->postProcessPuttedEntity($entity);
82
83
        /** @var ValidatorInterface $validator */
84 2
        $validator = $this->get('validator');
85 2
        $errors = $validator->validate($entity);
86 2
        if ($errors->count() > 0) {
87
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
88
        }
89
90 2
        $entity = $this->updateEntity($entity);
91
92 2
        $normalizer = $this->get('ddr_rest.normalizer');
93 2
        $content = $normalizer->normalize($entity);
94
95 2
        return new JsonResponse($content);
96
    }
97
98 2
    public function deleteAction(Request $request, $id)
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
99
    {
100 2
        $entity = $this->fetchEntity($id);
101 2
        $this->assertDeleteGranted($entity);
102
        $this->getService()->remove($entity);
103
104
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
105
    }
106
107 6
    public function listSubresourceAction(Request $request, $id)
108
    {
109 6
        $page = $request->query->get('page', 1);
110 6
        $perPage = $request->query->get('perPage', 50);
111
112 6
        $subresource = $this->getSubresource();
113 6
        $entity = $this->fetchEntity($id);
114 6
        $this->assertSubresourceListGranted($entity, $subresource);
115
116 6
        $paginator = $this->listSubresource(
117
            $entity,
118
            $subresource,
119
            $page,
120
            $perPage
121
        );
122 6
        $total = $paginator->count();
123 6
        $entities = $paginator->getIterator()->getArrayCopy();
124
125 6
        $normalizer = $this->get('ddr_rest.normalizer');
126 6
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
127
128 6
        $response = new JsonResponse($content, Response::HTTP_OK);
129 6
        $this->addPaginationHeaders($response, $page, $perPage, $total);
130
131 6
        return $response;
132
    }
133
134
    public function postSubresourceAction(Request $request, $id)
135
    {
136
        $subresource = $this->getSubresource();
137
        $parent = $this->fetchEntity($id);
138
        $this->assertSubresourcePostGranted($parent, $subresource);
139
        $entity = $this->parseRequest($request, null, $this->getSubResourceEntityClass($subresource));
140
        $entity = $this->postProcessSubResourcePostedEntity($subresource, $entity, $parent);
141
        $errors = $this->validate($entity);
0 ignored issues
show
Bug introduced by
The method validate() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
142
        if ($errors->count() > 0) {
143
            return $this->handleView($this->view($errors, Response::HTTP_BAD_REQUEST));
0 ignored issues
show
Bug introduced by
The method view() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method handleView() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
144
        }
145
        $entity = $this->saveSubResource($subresource, $entity);
146
147
        return $this->handleView($this->view($entity, Response::HTTP_CREATED));
0 ignored issues
show
Bug introduced by
The method view() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method handleView() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
148
    }
149
150 2
    public function putSubresourceAction(Request $request, $id, $subId)
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
151
    {
152 2
        $subresource = $this->getSubresource();
153 2
        $parent = $this->fetchEntity($id);
154 2
        $this->assertSubresourcePutGranted($parent, $subresource);
155 2
        $this->getService()->addToCollection($parent, $subresource, $subId);
156
157 2
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
158
    }
159
160 2
    public function deleteSubresourceAction(Request $request, $id, $subId)
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
161
    {
162 2
        $subresource = $this->getSubresource();
163 2
        $parent = $this->fetchEntity($id);
164 2
        $this->assertSubresourceDeleteGranted($parent, $subresource);
165 2
        $this->getService()->removeFromCollection($parent, $subresource, $subId);
166
167 2
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
168
    }
169
170
    /**
171
     * @return CrudServiceInterface
172
     */
173 28
    protected function getService(): CrudServiceInterface
174
    {
175 28
        $serviceId = $this->getServiceId();
176 28
        if (null === $serviceId) {
177 28
            $entityClass = $this->getEntityClass();
178 28
            if (null === $entityClass) {
179
                throw new \RuntimeException('No service or entity class given');
180
            }
181
            /** @var EntityManagerInterface $entityManager */
182 28
            $entityManager = $this->get('doctrine.orm.entity_manager');
183 28
            $repository = $entityManager->getRepository($entityClass);
184 28
            if (!$repository instanceof CrudServiceInterface) {
185
                throw new \RuntimeException(
186
                    'Your Entity Repository needs to be an instance of ' . CrudServiceInterface::class . '.'
187
                );
188
            }
189
190 28
            return $repository;
191
        } else {
192
            /** @var CrudServiceInterface $service */
193
            $service = $this->get($serviceId);
194
195
            return $service;
196
        }
197
    }
198
199 2
    protected function parseRequest(Request $request, $entity = null, $entityClass = null)
200
    {
201 2
        return $this->get('ddr.rest.parser.request')->parseEntity($request, $entityClass, $entity);
202
    }
203
204
    /**
205
     * @param object $entity
206
     *
207
     * @return object
208
     */
209
    protected function postProcessPostedEntity($entity)
210
    {
211
        return $entity;
212
    }
213
214
    /**
215
     * @param object $entity
216
     *
217
     * @return object
218
     */
219 2
    protected function postProcessPuttedEntity($entity)
220
    {
221 2
        return $entity;
222
    }
223
224
    /**
225
     * @param string $subresource
226
     * @param object $parent
227
     * @param object $entity
228
     *
229
     * @return object
230
     */
231
    protected function postProcessSubResourcePostedEntity($subresource, $entity, $parent)
2 ignored issues
show
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...
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...
232
    {
233
        return $entity;
234
    }
235
236 22
    protected function fetchEntity($id)
237
    {
238 22
        $entity = $this->getService()->find($id);
239 22
        if (null === $entity) {
240
            throw new NotFoundHttpException();
241
        }
242
243 22
        return $entity;
244
    }
245
246 6
    protected function listEntities(int $page = 1, int $perPage = 50): Paginator
247
    {
248 6
        $service = $this->getService();
249
250 6
        return $service->findAllPaginated($page, $perPage);
0 ignored issues
show
Bug introduced by
The method findAllPaginated() does not seem to exist on object<Dontdrinkandroot\...e\CrudServiceInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
251
    }
252
253
    protected function createEntity($entity)
254
    {
255
        return $this->getService()->create($entity);
256
    }
257
258 2
    protected function updateEntity($entity)
259
    {
260 2
        return $this->getService()->update($entity);
261
    }
262
263 6
    protected function listSubresource($entity, $property, $page = 1, $perPage = 50): Paginator
264
    {
265 6
        $service = $this->getService();
266
267 6
        return $service->findAssociationPaginated($entity, $property, $page, $perPage);
0 ignored issues
show
Bug introduced by
The method findAssociationPaginated() does not seem to exist on object<Dontdrinkandroot\...e\CrudServiceInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
268
    }
269
270 32
    protected function getEntityClass()
271
    {
272 32
        return $this->getCurrentRequest()->attributes->get('_entityClass');
273
    }
274
275
    protected function getShortName()
276
    {
277
        return Inflector::tableize($this->getClassMetadata()->reflection->getShortName());
278
    }
279
280 28
    protected function getServiceId()
281
    {
282 28
        return $this->getCurrentRequest()->attributes->get('_service');
283
    }
284
285 32
    protected function getCurrentRequest()
286
    {
287 32
        return $this->get('request_stack')->getCurrentRequest();
288
    }
289
290 8
    protected function assertListGranted()
291
    {
292 8
        $classMetadata = $this->getClassMetadata();
293 8
        $right = $classMetadata->getListRight();
294 8
        if (null === $right) {
295 4
            return;
296
        }
297
298 4
        $this->denyAccessUnlessGranted($right->attributes);
299 2
    }
300
301 2
    protected function assertPostGranted()
302
    {
303 2
        $classMetadata = $this->getClassMetadata();
304 2
        $right = $classMetadata->getPostRight();
305 2
        if (null === $right) {
306 2
            throw $this->createAccessDeniedException();
307
        }
308
309
        $this->denyAccessUnlessGranted($right->attributes);
310
    }
311
312 8
    protected function assertGetGranted($entity)
313
    {
314 8
        $classMetadata = $this->getClassMetadata();
315 8
        $right = $classMetadata->getGetRight();
316 8
        if (null === $right) {
317 2
            return;
318
        }
319
320 6
        $this->assertRightGranted($entity, $right);
321 4
    }
322
323 6
    protected function assertPutGranted($entity)
324
    {
325 6
        $classMetadata = $this->getClassMetadata();
326 6
        $right = $classMetadata->getPutRight();
327 6
        if (null === $right) {
328 2
            throw $this->createAccessDeniedException();
329
        }
330
331 4
        $this->assertRightGranted($entity, $right);
332 2
    }
333
334 2
    protected function assertDeleteGranted($entity)
335
    {
336 2
        $classMetadata = $this->getClassMetadata();
337 2
        $right = $classMetadata->getDeleteRight();
338 2
        if (null === $right) {
339 2
            throw $this->createAccessDeniedException();
340
        }
341
342
        $this->assertRightGranted($entity, $right);
343
    }
344
345 6
    protected function assertSubresourceListGranted($entity, $subresource)
346
    {
347 6
        $classMetadata = $this->getClassMetadata();
348
        /** @var PropertyMetadata $propertyMetadata */
349 6
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
350 6
        $right = $propertyMetadata->getSubResourceListRight();
351 6
        if (null === $right) {
352 6
            return;
353
        }
354
355
        $this->assertRightGranted($entity, $right);
356
    }
357
358
    protected function assertSubresourcePostGranted($entity, $subresource)
359
    {
360
        $classMetadata = $this->getClassMetadata();
361
        /** @var PropertyMetadata $propertyMetadata */
362
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
363
        $right = $propertyMetadata->getSubResourcePostRight();
364
        if (null === $right) {
365
            throw $this->createAccessDeniedException();
366
        }
367
368
        $this->assertRightGranted($entity, $right);
369
    }
370
371 2
    protected function assertSubresourcePutGranted($entity, $subresource)
372
    {
373 2
        $classMetadata = $this->getClassMetadata();
374
        /** @var PropertyMetadata $propertyMetadata */
375 2
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
376 2
        $right = $propertyMetadata->getSubResourcePutRight();
377 2
        if (null === $right) {
378
            throw $this->createAccessDeniedException();
379
        }
380
381 2
        $this->assertRightGranted($entity, $right);
382 2
    }
383
384 2
    protected function assertSubresourceDeleteGranted($entity, $subresource)
385
    {
386 2
        $classMetadata = $this->getClassMetadata();
387
        /** @var PropertyMetadata $propertyMetadata */
388 2
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
389 2
        $right = $propertyMetadata->getSubResourceDeleteRight();
390 2
        if (null === $right) {
391
            throw $this->createAccessDeniedException();
392
        }
393
394 2
        $this->assertRightGranted($entity, $right);
395 2
    }
396
397
    /**
398
     * @return ClassMetadata
399
     */
400 32
    protected function getClassMetadata()
401
    {
402 32
        $metaDataFactory = $this->get('ddr_rest.metadata.factory');
403
        /** @var ClassMetadata $classMetaData */
404 32
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
405
406 32
        return $classMetaData;
407
    }
408
409
    protected function getSubResourceEntityClass($subresource)
410
    {
411
        /** @var PropertyMetadata $propertyMetadata */
412
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
413
414
        return $propertyMetadata->getTargetClass();
415
    }
416
417
    protected function resolveSubject($entity, $propertyPath)
418
    {
419
        if ('this' === $propertyPath) {
420
            return $entity;
421
        }
422
        $propertyAccessor = $this->get('property_accessor');
423
424
        return $propertyAccessor->getValue($entity, $propertyPath);
425
    }
426
427
    /**
428
     * @param object $entity
429
     * @param Right  $right
430
     */
431 14
    protected function assertRightGranted($entity, Right $right)
432
    {
433 14
        $propertyPath = $right->propertyPath;
434 14
        if (null === $propertyPath) {
435 14
            $this->denyAccessUnlessGranted($right->attributes);
436
        } else {
437
            $subject = $this->resolveSubject($entity, $propertyPath);
438
            $this->denyAccessUnlessGranted($right->attributes, $subject);
439
        }
440 10
    }
441
442
    /**
443
     * @return string[]
444
     */
445
    protected function getSubresourceSerializationGroups($subresource)
446
    {
447
        return ['Default', 'ddr.rest.subresource', 'ddr.rest.' . $this->getShortName() . '.' . $subresource];
448
    }
449
450
    /**
451
     * @param string $subresource
452
     * @param object $entity
453
     *
454
     * @return
455
     */
456
    protected function saveSubResource($subresource, $entity)
0 ignored issues
show
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...
457
    {
458
        return $this->getService()->save($entity);
0 ignored issues
show
Bug introduced by
The method save() does not seem to exist on object<Dontdrinkandroot\...e\CrudServiceInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
459
    }
460
461
    /**
462
     * @return string|null
463
     */
464 6
    protected function getSubresource()
465
    {
466 6
        return $this->getCurrentRequest()->attributes->get('_subresource');
467
    }
468
469 18
    protected function parseIncludes(Request $request)
470
    {
471 18
        $includeString = $request->query->get('include');
472 18
        if (empty($includeString)) {
473 16
            return [];
474
        }
475
476 2
        return explode(',', $includeString);
477
    }
478
479
    private function parseConstraintViolations(ConstraintViolationListInterface $errors)
480
    {
481
        $data = [];
482
        /** @var ConstraintViolationInterface $error */
483
        foreach ($errors as $error) {
484
            $data[] = [
485
                'propertyPath' => $error->getPropertyPath(),
486
                'message'      => $error->getMessage(),
487
                'value'        => $error->getInvalidValue()
488
            ];
489
        }
490
491
        return $data;
492
    }
493
494 12
    private function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
495
    {
496 12
        $response->headers->add(
497
            [
498 12
                'x-pagination-current-page' => $page,
499 12
                'x-pagination-per-page'     => $perPage,
500 12
                'x-pagination-total'        => $total,
501 12
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
502
            ]
503
        );
504 12
    }
505
}
506