Completed
Push — standalone ( 46923f...8c7d4e )
by Philip
03:55
created

assertSubresourceDeleteGranted()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0054

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 9
cp 0.8889
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 2
crap 2.0054
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\Method;
9
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
10
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
11
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
12
use Dontdrinkandroot\RestBundle\Service\Normalizer;
13
use Dontdrinkandroot\RestBundle\Service\RestRequestParser;
14
use Dontdrinkandroot\Service\CrudServiceInterface;
15
use Metadata\MetadataFactoryInterface;
16
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
17
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
18
use Symfony\Component\HttpFoundation\JsonResponse;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\HttpFoundation\RequestStack;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
23
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
24
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
25
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
26
use Symfony\Component\Validator\ConstraintViolationInterface;
27
use Symfony\Component\Validator\ConstraintViolationListInterface;
28
use Symfony\Component\Validator\Validator\ValidatorInterface;
29
30
class RestResourceController implements ContainerAwareInterface, RestResourceControllerInterface
31
{
32
    use ContainerAwareTrait;
33
34
    /**
35
     * {@inheritdoc}
36
     */
37 10
    public function listAction(Request $request)
38
    {
39 10
        $page = $request->query->get('page', 1);
40 10
        $perPage = $request->query->get('perPage', 50);
41
42 10
        $this->assertListGranted();
43
44 6
        $listResult = $this->listEntities($page, $perPage);
45
46 6
        $response = new JsonResponse();
47
48 6
        if ($listResult instanceof Paginator) {
49 6
            $entities = iterator_to_array($listResult->getIterator());
50 6
            $total = $listResult->count();
51 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
52
        } else {
53
            $entities = $listResult;
54
        }
55
56 6
        $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request));
57
58 6
        $response->setData($content);
59
60 6
        return $response;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 4
    public function postAction(Request $request)
67
    {
68 4
        $this->assertPostGranted();
69 2
        $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass());
70 2
        $entity = $this->postProcessPostedEntity($entity);
71
72 2
        $errors = $this->getValidator()->validate($entity);
73 2
        if ($errors->count() > 0) {
74 2
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
75
        }
76
77
        $entity = $this->createEntity($entity);
78
79
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
80
81
        return new JsonResponse($content, Response::HTTP_CREATED);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 16
    public function getAction(Request $request, $id)
88
    {
89 16
        $entity = $this->fetchEntity($id);
90 14
        $this->assertGetGranted($entity);
91
92 12
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
93
94 12
        return new JsonResponse($content);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 4
    public function putAction(Request $request, $id)
101
    {
102 4
        $entity = $this->fetchEntity($id);
103 4
        $this->assertPutGranted($entity);
104 2
        $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass(), $entity);
105 2
        $entity = $this->postProcessPuttedEntity($entity);
106
107 2
        $errors = $this->getValidator()->validate($entity);
108 2
        if ($errors->count() > 0) {
109
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
110
        }
111
112 2
        $entity = $this->updateEntity($entity);
113
114 2
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
115
116 2
        return new JsonResponse($content);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 4
    public function deleteAction(Request $request, $id)
123
    {
124 4
        $entity = $this->fetchEntity($id);
125 4
        $this->assertDeleteGranted($entity);
126 2
        $this->getService()->remove($entity);
127
128 2
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 6
    public function listSubresourceAction(Request $request, $id)
135
    {
136 6
        $page = $request->query->get('page', 1);
137 6
        $perPage = $request->query->get('perPage', 50);
138
139 6
        $subresource = $this->getSubresource();
140 6
        $entity = $this->fetchEntity($id);
141 6
        $this->assertSubresourceListGranted($entity, $subresource);
142
143 6
        $listResult = $this->listSubresource($entity, $subresource, $page, $perPage);
144
145 6
        $response = new JsonResponse();
146
147 6
        if ($listResult instanceof Paginator) {
148 6
            $entities = iterator_to_array($listResult->getIterator());
149 6
            $total = $listResult->count();
150 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
151
        } else {
152
            $entities = $listResult;
153
        }
154
155 6
        $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request));
156
157 6
        $response->setData($content);
158
159 6
        return $response;
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165 4
    public function postSubresourceAction(Request $request, $id)
166
    {
167 4
        $subresource = $this->getSubresource();
168 4
        $parent = $this->fetchEntity($id);
169 4
        $this->assertSubresourcePostGranted($parent, $subresource);
170 2
        $entity = $this->getRequestParser()->parseEntity(
171
            $request,
172 2
            $this->getSubResourceEntityClass($subresource)
173
        );
174 2
        $entity = $this->postProcessSubResourcePostedEntity($subresource, $entity, $parent);
175
176 2
        $errors = $this->getValidator()->validate($entity);
177
178 2
        if ($errors->count() > 0) {
179
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
180
        }
181
182 2
        $entity = $this->createSubResource($parent, $subresource, $entity);
183
184 2
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
185
186 2
        return new JsonResponse($content, Response::HTTP_CREATED);
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 4
    public function putSubresourceAction(Request $request, $id, $subId)
193
    {
194 4
        $subresource = $this->getSubresource();
195 4
        $parent = $this->fetchEntity($id);
196 4
        $this->assertSubresourcePutGranted($parent, $subresource);
197 4
        $this->getService()->addAssociation($parent, $subresource, $subId);
0 ignored issues
show
Bug introduced by
The method addAssociation() 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...
198
199 4
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205 4
    public function deleteSubresourceAction(Request $request, $id, $subId = null)
206
    {
207 4
        $subresource = $this->getSubresource();
208 4
        $parent = $this->fetchEntity($id);
209 4
        $this->assertSubresourceDeleteGranted($parent, $subresource);
210 4
        $this->getService()->removeAssociation($parent, $subresource, $subId);
0 ignored issues
show
Bug introduced by
The method removeAssociation() does not exist on Dontdrinkandroot\Service\CrudServiceInterface. Did you maybe mean remove()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
211
212 4
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
213
    }
214
215
    /**
216
     * @return CrudServiceInterface
217
     */
218 38
    protected function getService(): CrudServiceInterface
219
    {
220 38
        $serviceId = $this->getServiceId();
221 38
        if (null === $serviceId) {
222 36
            $entityClass = $this->getEntityClass();
223 36
            if (null === $entityClass) {
224
                throw new \RuntimeException('No service or entity class given');
225
            }
226
            /** @var EntityManagerInterface $entityManager */
227 36
            $entityManager = $this->container->get('doctrine.orm.entity_manager');
228 36
            $repository = $entityManager->getRepository($entityClass);
229 36
            if (!$repository instanceof CrudServiceInterface) {
230
                throw new \RuntimeException(
231
                    'Your Entity Repository needs to be an instance of ' . CrudServiceInterface::class . '.'
232
                );
233
            }
234
235 36
            return $repository;
236
        } else {
237
            /** @var CrudServiceInterface $service */
238 2
            $service = $this->container->get($serviceId);
239
240 2
            return $service;
241
        }
242
    }
243
244
    /**
245
     * @param object $entity
246
     *
247
     * @return object
248
     */
249 2
    protected function postProcessPostedEntity($entity)
250
    {
251 2
        return $entity;
252
    }
253
254
    /**
255
     * @param object $entity
256
     *
257
     * @return object
258
     */
259 2
    protected function postProcessPuttedEntity($entity)
260
    {
261 2
        return $entity;
262
    }
263
264
    /**
265
     * @param string $subresource
266
     * @param object $parent
267
     * @param object $entity
268
     *
269
     * @return object
270
     */
271 2
    protected function postProcessSubResourcePostedEntity($subresource, $entity, $parent)
272
    {
273 2
        return $entity;
274
    }
275
276 32
    protected function fetchEntity($id)
277
    {
278 32
        $entity = $this->getService()->find($id);
279 32
        if (null === $entity) {
280 2
            throw new NotFoundHttpException();
281
        }
282
283 32
        return $entity;
284
    }
285
286
    /**
287
     * @param int $page
288
     * @param int $perPage
289
     *
290
     * @return Paginator|array
291
     */
292 6
    protected function listEntities(int $page = 1, int $perPage = 50)
293
    {
294 6
        return $this->getService()->findAllPaginated($page, $perPage);
295
    }
296
297
    protected function createEntity($entity)
298
    {
299
        return $this->getService()->create($entity);
300
    }
301
302 2
    protected function updateEntity($entity)
303
    {
304 2
        return $this->getService()->update($entity);
305
    }
306
307
    /**
308
     * @param object $entity
309
     * @param string $property
310
     * @param int    $page
311
     * @param int    $perPage
312
     *
313
     * @return Paginator|array
314
     */
315 6
    protected function listSubresource($entity, string $property, int $page = 1, int $perPage = 50)
316
    {
317 6
        return $this->getService()->findAssociationPaginated($entity, $property, $page, $perPage);
318
    }
319
320 46
    protected function getEntityClass()
321
    {
322 46
        return $this->getCurrentRequest()->attributes->get('_entityClass');
323
    }
324
325
    protected function getShortName()
326
    {
327
        return Inflector::tableize($this->getClassMetadata()->reflection->getShortName());
328
    }
329
330 38
    protected function getServiceId()
331
    {
332 38
        return $this->getCurrentRequest()->attributes->get('_service');
333
    }
334
335 46
    protected function getCurrentRequest()
336
    {
337 46
        return $this->getRequestStack()->getCurrentRequest();
338
    }
339
340 10
    protected function assertListGranted()
341
    {
342 10
        $method = $this->getClassMetadata()->getMethod(Method::LIST);
343 10
        if ($method !== null && null !== $right = $method->right) {
344 8
            $this->denyAccessUnlessGranted($right->attributes);
345
        }
346 6
    }
347
348 4
    protected function assertPostGranted()
349
    {
350 4
        $method = $this->getClassMetadata()->getMethod(Method::POST);
351 4
        $right = $method->right;
352 4
        if (null === $right) {
353
            throw new AccessDeniedException();
354
        }
355
356 4
        $this->denyAccessUnlessGranted($right->attributes);
357 2
    }
358
359 14
    protected function assertGetGranted($entity)
360
    {
361 14
        $method = $this->getClassMetadata()->getMethod(Method::GET);
362 14
        if ($method !== null && null !== $right = $method->right) {
363 10
            $this->assertRightGranted($entity, $right);
364
        }
365 12
    }
366
367 4
    protected function assertPutGranted($entity)
368
    {
369 4
        $method = $this->getClassMetadata()->getMethod(Method::PUT);
370 4
        $right = $method->right;
371 4
        if (null === $right) {
372
            throw new AccessDeniedException();
373
        }
374
375 4
        $this->assertRightGranted($entity, $right);
376 2
    }
377
378 4
    protected function assertDeleteGranted($entity)
379
    {
380 4
        $method = $this->getClassMetadata()->getMethod(Method::POST);
381 4
        $right = $method->right;
382 4
        if (null === $right) {
383
            throw new AccessDeniedException();
384
        }
385
386 4
        $this->assertRightGranted($entity, $right);
387 2
    }
388
389 6
    protected function assertSubresourceListGranted($entity, $subresource)
390
    {
391 6
        $classMetadata = $this->getClassMetadata();
392
        /** @var PropertyMetadata $propertyMetadata */
393 6
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
394 6
        $method = $propertyMetadata->getMethod(Method::LIST);
395 6
        $right = $method->right;
396 6
        if (null === $right) {
397
            return;
398
        }
399
400 6
        $this->assertRightGranted($entity, $right);
401 6
    }
402
403 4
    protected function assertSubresourcePostGranted($entity, $subresource)
404
    {
405 4
        $classMetadata = $this->getClassMetadata();
406
        /** @var PropertyMetadata $propertyMetadata */
407 4
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
408 4
        $method = $propertyMetadata->getMethod(Method::POST);
409 4
        $right = $method->right;
410 4
        if (null === $right) {
411
            throw new AccessDeniedException();
412
        }
413
414 4
        $this->assertRightGranted($entity, $right);
415 2
    }
416
417 4
    protected function assertSubresourcePutGranted($entity, $subresource)
418
    {
419 4
        $classMetadata = $this->getClassMetadata();
420
        /** @var PropertyMetadata $propertyMetadata */
421 4
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
422 4
        $method = $propertyMetadata->getMethod(Method::PUT);
423 4
        $right = $method->right;
424 4
        if (null === $right) {
425
            throw new AccessDeniedException();
426
        }
427
428 4
        $this->assertRightGranted($entity, $right);
429 4
    }
430
431 4
    protected function assertSubresourceDeleteGranted($entity, $subresource)
432
    {
433 4
        $classMetadata = $this->getClassMetadata();
434
        /** @var PropertyMetadata $propertyMetadata */
435 4
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
436 4
        $method = $propertyMetadata->getMethod(Method::DELETE);
437 4
        $right = $method->right;
438 4
        if (null === $right) {
439
            throw new AccessDeniedException();
440
        }
441
442 4
        $this->assertRightGranted($entity, $right);
443 4
    }
444
445
    /**
446
     * @return ClassMetadata
447
     */
448 46
    protected function getClassMetadata()
449
    {
450 46
        $metaDataFactory = $this->getMetadataFactory();
451
        /** @var ClassMetadata $classMetaData */
452 46
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
453
454 46
        return $classMetaData;
455
    }
456
457 2
    protected function getSubResourceEntityClass($subresource)
458
    {
459
        /** @var PropertyMetadata $propertyMetadata */
460 2
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
461
462 2
        return $propertyMetadata->getType();
463
    }
464
465
    protected function resolveSubject($entity, $propertyPath)
466
    {
467
        if ('this' === $propertyPath) {
468
            return $entity;
469
        }
470
        $propertyAccessor = $this->getPropertyAccessor();
471
472
        return $propertyAccessor->getValue($entity, $propertyPath);
473
    }
474
475
    /**
476
     * @param object $entity
477
     * @param Right  $right
478
     */
479 28
    protected function assertRightGranted($entity, Right $right)
480
    {
481 28
        $propertyPath = $right->propertyPath;
482 28
        if (null === $propertyPath) {
483 28
            $this->denyAccessUnlessGranted($right->attributes);
484
        } else {
485
            $subject = $this->resolveSubject($entity, $propertyPath);
486
            $this->denyAccessUnlessGranted($right->attributes, $subject);
487
        }
488 20
    }
489
490
    /**
491
     * @param object $parent
492
     * @param string $subresource
493
     * @param object $entity
494
     *
495
     * @return
496
     */
497 2
    protected function createSubResource($parent, $subresource, $entity)
498
    {
499 2
        return $this->getService()->createAssociation($parent, $subresource, $entity);
500
    }
501
502
    /**
503
     * @return string|null
504
     */
505 14
    protected function getSubresource()
506
    {
507 14
        return $this->getCurrentRequest()->attributes->get('_subresource');
508
    }
509
510 28
    protected function parseIncludes(Request $request)
511
    {
512 28
        $defaultIncludes = $request->attributes->get('_defaultincludes');
513 28
        if (null == $defaultIncludes) {
514 14
            $defaultIncludes = [];
515
        }
516
517 28
        $includeString = $request->query->get('include');
518 28
        if (empty($includeString)) {
519 26
            $includes = [];
520
        } else {
521 2
            $includes = explode(',', $includeString);
522
        }
523
524 28
        return array_merge($defaultIncludes, $includes);
525
    }
526
527 2
    private function parseConstraintViolations(ConstraintViolationListInterface $errors)
528
    {
529 2
        $data = [];
530
        /** @var ConstraintViolationInterface $error */
531 2
        foreach ($errors as $error) {
532 2
            $data[] = [
533 2
                'propertyPath' => $error->getPropertyPath(),
534 2
                'message'      => $error->getMessage(),
535 2
                'value'        => $error->getInvalidValue()
536
            ];
537
        }
538
539 2
        return $data;
540
    }
541
542 12
    private function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
543
    {
544 12
        $response->headers->add(
545
            [
546 12
                'x-pagination-current-page' => $page,
547 12
                'x-pagination-per-page'     => $perPage,
548 12
                'x-pagination-total'        => $total,
549 12
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
550
            ]
551
        );
552 12
    }
553
554 40
    protected function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.')
555
    {
556 40
        if (!$this->getAuthorizationChecker()->isGranted($attributes, $object)) {
557 14
            throw new AccessDeniedException($message);
558
        }
559 26
    }
560
561
    /**
562
     * @return Normalizer
563
     */
564 28
    protected function getNormalizer()
565
    {
566 28
        return $this->container->get('ddr_rest.normalizer');
567
    }
568
569
    /**
570
     * @return ValidatorInterface
571
     */
572 6
    protected function getValidator()
573
    {
574 6
        return $this->container->get('validator');
575
    }
576
577
    /**
578
     * @return RestRequestParser
579
     */
580 6
    protected function getRequestParser()
581
    {
582 6
        return $this->container->get('ddr.rest.parser.request');
583
    }
584
585
    /**
586
     * @return RequestStack
587
     */
588 46
    protected function getRequestStack()
589
    {
590 46
        return $this->container->get('request_stack');
591
    }
592
593
    /**
594
     * @return MetadataFactoryInterface
595
     */
596 46
    protected function getMetadataFactory()
597
    {
598 46
        return $this->container->get('ddr_rest.metadata.factory');
599
    }
600
601
    /**
602
     * @return PropertyAccessorInterface
603
     */
604
    protected function getPropertyAccessor()
605
    {
606
        return $this->container->get('property_accessor');
607
    }
608
609
    /**
610
     * @return AuthorizationCheckerInterface
611
     */
612 40
    protected function getAuthorizationChecker()
613
    {
614 40
        return $this->container->get('security.authorization_checker');
615
    }
616
}
617