Completed
Push — standalone ( a7b79f...57bbfc )
by Philip
02:45
created

EntityController   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 480
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 36.13%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 61
c 3
b 0
f 0
lcom 1
cbo 17
dl 0
loc 480
ccs 86
cts 238
cp 0.3613
rs 3.5483

42 Methods

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

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\Tools\Pagination\Paginator;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
8
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
9
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
10
use Dontdrinkandroot\RestBundle\Service\CrudServiceInterface;
11
use Dontdrinkandroot\RestBundle\Service\DoctrineEntityRepositoryCrudService;
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 2
    public function listAction(Request $request)
24
    {
25 2
        $page = $request->query->get('page', 1);
26 2
        $perPage = $request->query->get('perPage', 50);
27
28 2
        $this->assertListGranted();
29
30 2
        $paginator = $this->listEntities($page, $perPage);
31 2
        $total = $paginator->count();
32 2
        $entities = $paginator->getIterator()->getArrayCopy();
33
34 2
        $normalizer = $this->get('ddr_rest.normalizer');
35 2
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
36
37 2
        $response = new JsonResponse($content, Response::HTTP_OK);
38 2
        $this->addPaginationHeaders($response, $page, $perPage, $total);
39
40 2
        return $response;
41
    }
42
43 2
    public function postAction(Request $request)
44
    {
45 2
        $this->assertPostGranted();
46
        $entity = $this->parseRequest($request, null, $this->getEntityClass());
47
        $entity = $this->postProcessPostedEntity($entity);
48
49
        /** @var ValidatorInterface $validator */
50
        $validator = $this->get('validator');
51
        $errors = $validator->validate($entity);
52
        if ($errors->count() > 0) {
53
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
54
        }
55
56
        $entity = $this->createEntity($entity);
57
58
        $normalizer = $this->get('ddr_rest.normalizer');
59
        $content = $normalizer->normalize($entity);
60
61
        return new JsonResponse($content, Response::HTTP_CREATED);
62
    }
63
64 2
    public function getAction(Request $request, $id)
65
    {
66 2
        $entity = $this->fetchEntity($id);
67 2
        $this->assertGetGranted($entity);
68
69 2
        $normalizer = $this->get('ddr_rest.normalizer');
70 2
        $content = $normalizer->normalize($entity, $this->parseIncludes($request));
71
72 2
        return new JsonResponse($content);
73
    }
74
75 2
    public function putAction(Request $request, $id)
76
    {
77 2
        $entity = $this->fetchEntity($id);
78 2
        $this->assertPutGranted($entity);
79
        $entity = $this->parseRequest($request, $entity, $this->getEntityClass());
80
        $entity = $this->postProcessPuttedEntity($entity);
81
82
        /** @var ValidatorInterface $validator */
83
        $validator = $this->get('validator');
84
        $errors = $validator->validate($entity);
85
        if ($errors->count() > 0) {
86
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
87
        }
88
89
        $entity = $this->updateEntity($entity);
90
91
        $normalizer = $this->get('ddr_rest.normalizer');
92
        $content = $normalizer->normalize($entity);
93
94
        return new JsonResponse($content);
95
    }
96
97 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...
98
    {
99 2
        $entity = $this->fetchEntity($id);
100 2
        $this->assertDeleteGranted($entity);
101
        $this->getService()->remove($entity);
102
103
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
104
    }
105
106
    public function listSubresourceAction(Request $request, $id)
107
    {
108
        $page = $request->query->get('page', 1);
109
        $perPage = $request->query->get('perPage', 50);
110
111
        $subresource = $this->getSubresource();
112
        $entity = $this->fetchEntity($id);
113
        $this->assertSubresourceListGranted($entity, $subresource);
114
115
        $paginator = $this->listSubresource(
116
            $entity,
117
            $subresource,
118
            $page,
119
            $perPage
120
        );
121
        $total = $paginator->count();
122
        $entities = $paginator->getIterator()->getArrayCopy();
123
124
        $normalizer = $this->get('ddr_rest.normalizer');
125
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
126
127
        $response = new JsonResponse($content, Response::HTTP_OK);
128
        $this->addPaginationHeaders($response, $page, $perPage, $total);
129
130
        return $response;
131
    }
132
133
    public function postSubresourceAction(Request $request, $id)
134
    {
135
        $subresource = $this->getSubresource();
136
        $parent = $this->fetchEntity($id);
137
        $this->assertSubresourcePostGranted($parent, $subresource);
138
        $entity = $this->parseRequest($request, null, $this->getSubResourceEntityClass($subresource));
139
        $entity = $this->postProcessSubResourcePostedEntity($subresource, $entity, $parent);
140
        $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...
141
        if ($errors->count() > 0) {
142
            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...
143
        }
144
        $entity = $this->saveSubResource($subresource, $entity);
145
146
        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...
147
    }
148
149
    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...
150
    {
151
        $subresource = $this->getSubresource();
152
        $parent = $this->fetchEntity($id);
153
        $this->assertSubresourcePutGranted($parent, $subresource);
154
        $this->getService()->addToCollection($parent, $subresource, $subId);
155
156
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
157
    }
158
159
    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...
160
    {
161
        $subresource = $this->getSubresource();
162
        $parent = $this->fetchEntity($id);
163
        $this->assertSubresourceDeleteGranted($parent, $subresource);
164
        $this->getService()->removeFromCollection($parent, $subresource, $subId);
165
166
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
167
    }
168
169
    /**
170
     * @return CrudServiceInterface
171
     */
172 8
    protected function getService()
173
    {
174 8
        $serviceId = $this->getServiceId();
175 8
        if (null === $serviceId) {
176 8
            $entityClass = $this->getEntityClass();
177 8
            if (null === $entityClass) {
178
                throw new \RuntimeException('No service or entity class given');
179
            }
180 8
            $entityManager = $this->get('doctrine.orm.entity_manager');
181
182 8
            return new DoctrineEntityRepositoryCrudService(
183
                $entityManager,
184
                $entityClass
185
            );
186
        } else {
187
            /** @var CrudServiceInterface $service */
188
            $service = $this->get($serviceId);
189
190
            return $service;
191
        }
192
    }
193
194
    protected function parseRequest(Request $request, $entity = null, $entityClass = null)
195
    {
196
        return $this->get('ddr.rest.parser.request')->parseEntity($request, $entityClass, $entity);
197
    }
198
199
    /**
200
     * @param object $entity
201
     *
202
     * @return object
203
     */
204
    protected function postProcessPostedEntity($entity)
205
    {
206
        return $entity;
207
    }
208
209
    /**
210
     * @param object $entity
211
     *
212
     * @return object
213
     */
214
    protected function postProcessPuttedEntity($entity)
215
    {
216
        return $entity;
217
    }
218
219
    /**
220
     * @param string $subresource
221
     * @param object $parent
222
     * @param object $entity
223
     *
224
     * @return object
225
     */
226
    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...
227
    {
228
        return $entity;
229
    }
230
231 6
    protected function fetchEntity($id)
232
    {
233 6
        $entity = $this->getService()->findById($id);
234 6
        if (null === $entity) {
235
            throw new NotFoundHttpException();
236
        }
237
238 6
        return $entity;
239
    }
240
241 2
    protected function listEntities(int $page = 1, int $perPage = 50): Paginator
242
    {
243 2
        $service = $this->getService();
244
245 2
        return $service->listPaginated($page, $perPage);
246
    }
247
248
    protected function createEntity($entity)
249
    {
250
        return $this->getService()->create($entity);
251
    }
252
253
    protected function updateEntity($entity)
254
    {
255
        return $this->getService()->update($entity);
256
    }
257
258
    protected function listSubresource($entity, $property, $page = 1, $perPage = 50): Paginator
259
    {
260
        $service = $this->getService();
261
262
        return $service->listAssociationPaginated($entity, $property, $page, $perPage);
263
    }
264
265 10
    protected function getEntityClass()
266
    {
267 10
        return $this->getCurrentRequest()->attributes->get('_entityClass');
268
    }
269
270
    protected function getShortName()
271
    {
272
        return Inflector::tableize($this->getClassMetadata()->reflection->getShortName());
273
    }
274
275 8
    protected function getServiceId()
276
    {
277 8
        return $this->getCurrentRequest()->attributes->get('_service');
278
    }
279
280 10
    protected function getCurrentRequest()
281
    {
282 10
        return $this->get('request_stack')->getCurrentRequest();
283
    }
284
285 2
    protected function assertListGranted()
286
    {
287 2
        $classMetadata = $this->getClassMetadata();
288 2
        $right = $classMetadata->getListRight();
289 2
        if (null === $right) {
290 2
            return;
291
        }
292
293
        $this->denyAccessUnlessGranted($right->attributes);
294
    }
295
296 2
    protected function assertPostGranted()
297
    {
298 2
        $classMetadata = $this->getClassMetadata();
299 2
        $right = $classMetadata->getPostRight();
300 2
        if (null === $right) {
301 2
            throw $this->createAccessDeniedException();
302
        }
303
304
        $this->denyAccessUnlessGranted($right->attributes);
305
    }
306
307 2
    protected function assertGetGranted($entity)
308
    {
309 2
        $classMetadata = $this->getClassMetadata();
310 2
        $right = $classMetadata->getGetRight();
311 2
        if (null === $right) {
312 2
            return;
313
        }
314
315
        $this->assertRightGranted($entity, $right);
316
    }
317
318 2
    protected function assertPutGranted($entity)
319
    {
320 2
        $classMetadata = $this->getClassMetadata();
321 2
        $right = $classMetadata->getPutRight();
322 2
        if (null === $right) {
323 2
            throw $this->createAccessDeniedException();
324
        }
325
326
        $this->assertRightGranted($entity, $right);
327
    }
328
329 2
    protected function assertDeleteGranted($entity)
330
    {
331 2
        $classMetadata = $this->getClassMetadata();
332 2
        $right = $classMetadata->getDeleteRight();
333 2
        if (null === $right) {
334 2
            throw $this->createAccessDeniedException();
335
        }
336
337
        $this->assertRightGranted($entity, $right);
338
    }
339
340
    protected function assertSubresourceListGranted($entity, $subresource)
341
    {
342
        $classMetadata = $this->getClassMetadata();
343
        /** @var PropertyMetadata $propertyMetadata */
344
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
345
        $right = $propertyMetadata->getSubResourceListRight();
346
        if (null === $right) {
347
            return;
348
        }
349
350
        $this->assertRightGranted($entity, $right);
351
    }
352
353
    protected function assertSubresourcePostGranted($entity, $subresource)
354
    {
355
        $classMetadata = $this->getClassMetadata();
356
        /** @var PropertyMetadata $propertyMetadata */
357
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
358
        $right = $propertyMetadata->getSubResourcePostRight();
359
        if (null === $right) {
360
            throw $this->createAccessDeniedException();
361
        }
362
363
        $this->assertRightGranted($entity, $right);
364
    }
365
366
    protected function assertSubresourcePutGranted($entity, $subresource)
367
    {
368
        $classMetadata = $this->getClassMetadata();
369
        /** @var PropertyMetadata $propertyMetadata */
370
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
371
        $right = $propertyMetadata->getSubResourcePutRight();
372
        if (null === $right) {
373
            throw $this->createAccessDeniedException();
374
        }
375
376
        $this->assertRightGranted($entity, $right);
377
    }
378
379
    protected function assertSubresourceDeleteGranted($entity, $subresource)
380
    {
381
        $classMetadata = $this->getClassMetadata();
382
        /** @var PropertyMetadata $propertyMetadata */
383
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
384
        $right = $propertyMetadata->getSubResourceDeleteRight();
385
        if (null === $right) {
386
            throw $this->createAccessDeniedException();
387
        }
388
389
        $this->assertRightGranted($entity, $right);
390
    }
391
392
    /**
393
     * @return ClassMetadata
394
     */
395 10
    protected function getClassMetadata()
396
    {
397 10
        $metaDataFactory = $this->get('ddr_rest.metadata.factory');
398
        /** @var ClassMetadata $classMetaData */
399 10
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
400
401 10
        return $classMetaData;
402
    }
403
404
    protected function getSubResourceEntityClass($subresource)
405
    {
406
        /** @var PropertyMetadata $propertyMetadata */
407
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
408
409
        return $propertyMetadata->getTargetClass();
410
    }
411
412
    protected function resolveSubject($entity, $propertyPath)
413
    {
414
        if ('this' === $propertyPath) {
415
            return $entity;
416
        }
417
        $propertyAccessor = $this->get('property_accessor');
418
419
        return $propertyAccessor->getValue($entity, $propertyPath);
420
    }
421
422
    /**
423
     * @param object $entity
424
     * @param Right  $right
425
     */
426
    protected function assertRightGranted($entity, Right $right)
427
    {
428
        $propertyPath = $right->propertyPath;
429
        if (null === $propertyPath) {
430
            $this->denyAccessUnlessGranted($right->attributes);
431
        } else {
432
            $subject = $this->resolveSubject($entity, $propertyPath);
433
            $this->denyAccessUnlessGranted($right->attributes, $subject);
434
        }
435
    }
436
437
    /**
438
     * @return string[]
439
     */
440
    protected function getSubresourceSerializationGroups($subresource)
441
    {
442
        return ['Default', 'ddr.rest.subresource', 'ddr.rest.' . $this->getShortName() . '.' . $subresource];
443
    }
444
445
    /**
446
     * @param string $subresource
447
     * @param object $entity
448
     *
449
     * @return
450
     */
451
    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...
452
    {
453
        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...
454
    }
455
456
    /**
457
     * @return string|null
458
     */
459
    protected function getSubresource()
460
    {
461
        return $this->getCurrentRequest()->attributes->get('_subresource');
462
    }
463
464 4
    protected function parseIncludes(Request $request)
465
    {
466 4
        $includeString = $request->query->get('include');
467 4
        if (empty($includeString)) {
468 4
            return [];
469
        }
470
471
        return explode(',', $includeString);
472
    }
473
474
    private function parseConstraintViolations(ConstraintViolationListInterface $errors)
475
    {
476
        $data = [];
477
        /** @var ConstraintViolationInterface $error */
478
        foreach ($errors as $error) {
479
            $data[] = [
480
                'propertyPath' => $error->getPropertyPath(),
481
                'message'      => $error->getMessage(),
482
                'value'        => $error->getInvalidValue()
483
            ];
484
        }
485
486
        return $data;
487
    }
488
489 2
    private function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
490
    {
491 2
        $response->headers->add(
492
            [
493 2
                'x-pagination-current-page' => $page,
494 2
                'x-pagination-per-page'     => $perPage,
495 2
                'x-pagination-total'        => $total,
496 2
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
497
            ]
498
        );
499 2
    }
500
}
501