Completed
Push — standalone ( 8c7d4e...e42fd2 )
by Philip
04:11
created

postProcessSubResourcePostedEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
crap 1
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 32
    public function getAction(Request $request, $id)
88
    {
89 32
        $entity = $this->fetchEntity($id);
90 30
        $this->assertGetGranted($entity);
91
92 28
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
93
94 28
        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 12
    public function putSubresourceAction(Request $request, $id, $subId)
193
    {
194 12
        $subresource = $this->getSubresource();
195 12
        $parent = $this->fetchEntity($id);
196 12
        $this->assertSubresourcePutGranted($parent, $subresource);
197 12
        $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 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205 12
    public function deleteSubresourceAction(Request $request, $id, $subId = null)
206
    {
207 12
        $subresource = $this->getSubresource();
208 12
        $parent = $this->fetchEntity($id);
209 12
        $this->assertSubresourceDeleteGranted($parent, $subresource);
210 12
        $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 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
213
    }
214
215
    /**
216
     * @return CrudServiceInterface
217
     */
218 54
    protected function getService(): CrudServiceInterface
219
    {
220 54
        $serviceId = $this->getServiceId();
221 54
        if (null === $serviceId) {
222 52
            $entityClass = $this->getEntityClass();
223 52
            if (null === $entityClass) {
224
                throw new \RuntimeException('No service or entity class given');
225
            }
226
            /** @var EntityManagerInterface $entityManager */
227 52
            $entityManager = $this->container->get('doctrine.orm.entity_manager');
228 52
            $repository = $entityManager->getRepository($entityClass);
229 52
            if (!$repository instanceof CrudServiceInterface) {
230
                throw new \RuntimeException(
231
                    'Your Entity Repository needs to be an instance of ' . CrudServiceInterface::class . '.'
232
                );
233
            }
234
235 52
            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 48
    protected function fetchEntity($id)
277
    {
278 48
        $entity = $this->getService()->find($id);
279 48
        if (null === $entity) {
280 2
            throw new NotFoundHttpException();
281
        }
282
283 48
        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 62
    protected function getEntityClass()
321
    {
322 62
        return $this->getCurrentRequest()->attributes->get('_entityClass');
323
    }
324
325
    protected function getShortName()
326
    {
327
        return Inflector::tableize($this->getClassMetadata()->reflection->getShortName());
328
    }
329
330 54
    protected function getServiceId()
331
    {
332 54
        return $this->getCurrentRequest()->attributes->get('_service');
333
    }
334
335 62
    protected function getCurrentRequest()
336
    {
337 62
        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 30
    protected function assertGetGranted($entity)
360
    {
361 30
        $method = $this->getClassMetadata()->getMethod(Method::GET);
362 30
        if ($method !== null && null !== $right = $method->right) {
363 10
            $this->assertRightGranted($entity, $right);
364
        }
365 28
    }
366
367 4
    protected function assertPutGranted($entity)
368
    {
369 4
        $method = $this->getClassMetadata()->getMethod(Method::PUT);
370 4
        if ($method !== null && null !== $right = $method->right) {
371 4
            $this->assertRightGranted($entity, $right);
372
        }
373 2
    }
374
375 4
    protected function assertDeleteGranted($entity)
376
    {
377 4
        $method = $this->getClassMetadata()->getMethod(Method::POST);
378 4
        if ($method !== null && null !== $right = $method->right) {
379 4
            $this->assertRightGranted($entity, $right);
380
        }
381 2
    }
382
383 6
    protected function assertSubresourceListGranted($entity, $subresource)
384
    {
385 6
        $classMetadata = $this->getClassMetadata();
386
        /** @var PropertyMetadata $propertyMetadata */
387 6
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
388 6
        $method = $propertyMetadata->getMethod(Method::LIST);
389 6
        $right = $method->right;
390 6
        if (null === $right) {
391
            return;
392
        }
393
394 6
        $this->assertRightGranted($entity, $right);
395 6
    }
396
397 4
    protected function assertSubresourcePostGranted($entity, $subresource)
398
    {
399 4
        $classMetadata = $this->getClassMetadata();
400
        /** @var PropertyMetadata $propertyMetadata */
401 4
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
402 4
        $method = $propertyMetadata->getMethod(Method::POST);
403 4
        $right = $method->right;
404 4
        if (null === $right) {
405
            throw new AccessDeniedException();
406
        }
407
408 4
        $this->assertRightGranted($entity, $right);
409 2
    }
410
411 12
    protected function assertSubresourcePutGranted($entity, $subresource)
412
    {
413 12
        $classMetadata = $this->getClassMetadata();
414
        /** @var PropertyMetadata $propertyMetadata */
415 12
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
416 12
        $method = $propertyMetadata->getMethod(Method::PUT);
417 12
        if (null !== $right = $method->right) {
418 4
            $this->assertRightGranted($entity, $right);
419
        }
420 12
    }
421
422 12
    protected function assertSubresourceDeleteGranted($entity, $subresource)
423
    {
424 12
        $classMetadata = $this->getClassMetadata();
425
        /** @var PropertyMetadata $propertyMetadata */
426 12
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
427 12
        $method = $propertyMetadata->getMethod(Method::PUT);
428 12
        if (null !== $right = $method->right) {
429 4
            $this->assertRightGranted($entity, $right);
430
        }
431 12
    }
432
433
    /**
434
     * @return ClassMetadata
435
     */
436 62
    protected function getClassMetadata()
437
    {
438 62
        $metaDataFactory = $this->getMetadataFactory();
439
        /** @var ClassMetadata $classMetaData */
440 62
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
441
442 62
        return $classMetaData;
443
    }
444
445 2
    protected function getSubResourceEntityClass($subresource)
446
    {
447
        /** @var PropertyMetadata $propertyMetadata */
448 2
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
449
450 2
        return $propertyMetadata->getType();
451
    }
452
453
    protected function resolveSubject($entity, $propertyPath)
454
    {
455
        if ('this' === $propertyPath) {
456
            return $entity;
457
        }
458
        $propertyAccessor = $this->getPropertyAccessor();
459
460
        return $propertyAccessor->getValue($entity, $propertyPath);
461
    }
462
463
    /**
464
     * @param object $entity
465
     * @param Right  $right
466
     */
467 28
    protected function assertRightGranted($entity, Right $right)
468
    {
469 28
        $propertyPath = $right->propertyPath;
470 28
        if (null === $propertyPath) {
471 28
            $this->denyAccessUnlessGranted($right->attributes);
472
        } else {
473
            $subject = $this->resolveSubject($entity, $propertyPath);
474
            $this->denyAccessUnlessGranted($right->attributes, $subject);
475
        }
476 20
    }
477
478
    /**
479
     * @param object $parent
480
     * @param string $subresource
481
     * @param object $entity
482
     *
483
     * @return
484
     */
485 2
    protected function createSubResource($parent, $subresource, $entity)
486
    {
487 2
        return $this->getService()->createAssociation($parent, $subresource, $entity);
488
    }
489
490
    /**
491
     * @return string|null
492
     */
493 30
    protected function getSubresource()
494
    {
495 30
        return $this->getCurrentRequest()->attributes->get('_subresource');
496
    }
497
498 44
    protected function parseIncludes(Request $request)
499
    {
500 44
        $defaultIncludes = $request->attributes->get('_defaultincludes');
501 44
        if (null == $defaultIncludes) {
502 18
            $defaultIncludes = [];
503
        }
504
505 44
        $includeString = $request->query->get('include');
506 44
        if (empty($includeString)) {
507 30
            $includes = [];
508
        } else {
509 14
            $includes = explode(',', $includeString);
510
        }
511
512 44
        return array_merge($defaultIncludes, $includes);
513
    }
514
515 2
    private function parseConstraintViolations(ConstraintViolationListInterface $errors)
516
    {
517 2
        $data = [];
518
        /** @var ConstraintViolationInterface $error */
519 2
        foreach ($errors as $error) {
520 2
            $data[] = [
521 2
                'propertyPath' => $error->getPropertyPath(),
522 2
                'message'      => $error->getMessage(),
523 2
                'value'        => $error->getInvalidValue()
524
            ];
525
        }
526
527 2
        return $data;
528
    }
529
530 12
    private function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
531
    {
532 12
        $response->headers->add(
533
            [
534 12
                'x-pagination-current-page' => $page,
535 12
                'x-pagination-per-page'     => $perPage,
536 12
                'x-pagination-total'        => $total,
537 12
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
538
            ]
539
        );
540 12
    }
541
542 40
    protected function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.')
543
    {
544 40
        if (!$this->getAuthorizationChecker()->isGranted($attributes, $object)) {
545 14
            throw new AccessDeniedException($message);
546
        }
547 26
    }
548
549
    /**
550
     * @return Normalizer
551
     */
552 44
    protected function getNormalizer()
553
    {
554 44
        return $this->container->get('ddr_rest.normalizer');
555
    }
556
557
    /**
558
     * @return ValidatorInterface
559
     */
560 6
    protected function getValidator()
561
    {
562 6
        return $this->container->get('validator');
563
    }
564
565
    /**
566
     * @return RestRequestParser
567
     */
568 6
    protected function getRequestParser()
569
    {
570 6
        return $this->container->get('ddr.rest.parser.request');
571
    }
572
573
    /**
574
     * @return RequestStack
575
     */
576 62
    protected function getRequestStack()
577
    {
578 62
        return $this->container->get('request_stack');
579
    }
580
581
    /**
582
     * @return MetadataFactoryInterface
583
     */
584 62
    protected function getMetadataFactory()
585
    {
586 62
        return $this->container->get('ddr_rest.metadata.factory');
587
    }
588
589
    /**
590
     * @return PropertyAccessorInterface
591
     */
592
    protected function getPropertyAccessor()
593
    {
594
        return $this->container->get('property_accessor');
595
    }
596
597
    /**
598
     * @return AuthorizationCheckerInterface
599
     */
600 40
    protected function getAuthorizationChecker()
601
    {
602 40
        return $this->container->get('security.authorization_checker');
603
    }
604
}
605