Completed
Push — standalone ( 7c3a2c...83bb11 )
by Philip
05:16
created

EntityController::assertRightGranted()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 5
cts 7
cp 0.7143
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
crap 2.0932
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 6
    public function listAction(Request $request)
24
    {
25 6
        $page = $request->query->get('page', 1);
26 6
        $perPage = $request->query->get('perPage', 50);
27
28 6
        $this->assertListGranted();
29
30 4
        $paginator = $this->listEntities($page, $perPage);
31 4
        $total = $paginator->count();
32 4
        $entities = $paginator->getIterator()->getArrayCopy();
33
34 4
        $normalizer = $this->get('ddr_rest.normalizer');
35 4
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
36
37 4
        $response = new JsonResponse($content, Response::HTTP_OK);
38 4
        $this->addPaginationHeaders($response, $page, $perPage, $total);
39
40 4
        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 6
    public function getAction(Request $request, $id)
65
    {
66 6
        $entity = $this->fetchEntity($id);
67 6
        $this->assertGetGranted($entity);
68
69 4
        $normalizer = $this->get('ddr_rest.normalizer');
70 4
        $content = $normalizer->normalize($entity, $this->parseIncludes($request));
71
72 4
        return new JsonResponse($content);
73
    }
74
75 6
    public function putAction(Request $request, $id)
76
    {
77 6
        $entity = $this->fetchEntity($id);
78 6
        $this->assertPutGranted($entity);
79 2
        $entity = $this->parseRequest($request, $entity, $this->getEntityClass());
80 2
        $entity = $this->postProcessPuttedEntity($entity);
81
82
        /** @var ValidatorInterface $validator */
83 2
        $validator = $this->get('validator');
84 2
        $errors = $validator->validate($entity);
85 2
        if ($errors->count() > 0) {
86
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
87
        }
88
89 2
        $entity = $this->updateEntity($entity);
90
91 2
        $normalizer = $this->get('ddr_rest.normalizer');
92 2
        $content = $normalizer->normalize($entity);
93
94 2
        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
        $subresource = $this->getSubresource();
109
        $entity = $this->fetchEntity($id);
110
        $this->assertSubresourceListGranted($entity, $subresource);
111
        $result = $this->listSubresource(
112
            $entity,
113
            $subresource,
114
            $request->query->get('page', 1),
115
            $request->query->get('perPage', 50)
116
        );
117
        $view = $this->createViewFromListResult($result);
0 ignored issues
show
Bug introduced by
The method createViewFromListResult() 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...
118
        $view->getContext()->addGroups($this->getSubresourceSerializationGroups($subresource));
119
120
        return $this->handleView($view);
0 ignored issues
show
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...
121
    }
122
123
    public function postSubresourceAction(Request $request, $id)
124
    {
125
        $subresource = $this->getSubresource();
126
        $parent = $this->fetchEntity($id);
127
        $this->assertSubresourcePostGranted($parent, $subresource);
128
        $entity = $this->parseRequest($request, null, $this->getSubResourceEntityClass($subresource));
129
        $entity = $this->postProcessSubResourcePostedEntity($subresource, $entity, $parent);
130
        $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...
131
        if ($errors->count() > 0) {
132
            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...
133
        }
134
        $entity = $this->saveSubResource($subresource, $entity);
135
136
        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...
137
    }
138
139
    /**
140
     * @return CrudServiceInterface
141
     */
142 18
    protected function getService()
143
    {
144 18
        $serviceId = $this->getServiceId();
145 18
        if (null === $serviceId) {
146 18
            $entityClass = $this->getEntityClass();
147 18
            if (null === $entityClass) {
148
                throw new \RuntimeException('No service or entity class given');
149
            }
150 18
            $entityManager = $this->get('doctrine.orm.entity_manager');
151
152 18
            return new DoctrineEntityRepositoryCrudService(
153
                $entityManager,
154
                $entityClass
155
            );
156
        } else {
157
            /** @var CrudServiceInterface $service */
158
            $service = $this->get($serviceId);
159
160
            return $service;
161
        }
162
    }
163
164 2
    protected function parseRequest(Request $request, $entity = null, $entityClass = null)
165
    {
166 2
        return $this->get('ddr.rest.parser.request')->parseEntity($request, $entityClass, $entity);
167
    }
168
169
    /**
170
     * @param object $entity
171
     *
172
     * @return object
173
     */
174
    protected function postProcessPostedEntity($entity)
175
    {
176
        return $entity;
177
    }
178
179
    /**
180
     * @param object $entity
181
     *
182
     * @return object
183
     */
184 2
    protected function postProcessPuttedEntity($entity)
185
    {
186 2
        return $entity;
187
    }
188
189
    /**
190
     * @param string $subresource
191
     * @param object $parent
192
     * @param object $entity
193
     *
194
     * @return object
195
     */
196
    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...
197
    {
198
        return $entity;
199
    }
200
201 14
    protected function fetchEntity($id)
202
    {
203 14
        $entity = $this->getService()->findById($id);
204 14
        if (null === $entity) {
205
            throw new NotFoundHttpException();
206
        }
207
208 14
        return $entity;
209
    }
210
211 4
    protected function listEntities(int $page = 1, int $perPage = 50): Paginator
212
    {
213 4
        $service = $this->getService();
214
215 4
        return $service->listPaginated($page, $perPage);
216
    }
217
218
    protected function createEntity($entity)
219
    {
220
        return $this->getService()->create($entity);
221
    }
222
223 2
    protected function updateEntity($entity)
224
    {
225 2
        return $this->getService()->update($entity);
226
    }
227
228
    protected function listSubresource($entity, $subresource, $page = 1, $perPage = 50)
0 ignored issues
show
Unused Code introduced by
The parameter $page 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 $perPage 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...
229
    {
230
        $propertyAccessor = $this->container->get('property_accessor');
231
232
        return $propertyAccessor->getValue($entity, $subresource);
233
    }
234
235 22
    protected function getEntityClass()
236
    {
237 22
        return $this->getCurrentRequest()->attributes->get('_entityClass');
238
    }
239
240
    protected function getShortName()
241
    {
242
        return Inflector::tableize($this->getClassMetadata()->reflection->getShortName());
243
    }
244
245 18
    protected function getServiceId()
246
    {
247 18
        return $this->getCurrentRequest()->attributes->get('_service');
248
    }
249
250 22
    protected function getCurrentRequest()
251
    {
252 22
        return $this->get('request_stack')->getCurrentRequest();
253
    }
254
255 6
    protected function assertListGranted()
256
    {
257 6
        $classMetadata = $this->getClassMetadata();
258 6
        $right = $classMetadata->getListRight();
259 6
        if (null === $right) {
260 2
            return;
261
        }
262
263 4
        $this->denyAccessUnlessGranted($right->attributes);
264 2
    }
265
266 2
    protected function assertPostGranted()
267
    {
268 2
        $classMetadata = $this->getClassMetadata();
269 2
        $right = $classMetadata->getPostRight();
270 2
        if (null === $right) {
271 2
            throw $this->createAccessDeniedException();
272
        }
273
274
        $this->denyAccessUnlessGranted($right->attributes);
275
    }
276
277 6
    protected function assertGetGranted($entity)
278
    {
279 6
        $classMetadata = $this->getClassMetadata();
280 6
        $right = $classMetadata->getGetRight();
281 6
        if (null === $right) {
282 2
            return;
283
        }
284
285 4
        $this->assertRightGranted($entity, $right);
286 2
    }
287
288 6
    protected function assertPutGranted($entity)
289
    {
290 6
        $classMetadata = $this->getClassMetadata();
291 6
        $right = $classMetadata->getPutRight();
292 6
        if (null === $right) {
293 2
            throw $this->createAccessDeniedException();
294
        }
295
296 4
        $this->assertRightGranted($entity, $right);
297 2
    }
298
299 2
    protected function assertDeleteGranted($entity)
300
    {
301 2
        $classMetadata = $this->getClassMetadata();
302 2
        $right = $classMetadata->getDeleteRight();
303 2
        if (null === $right) {
304 2
            throw $this->createAccessDeniedException();
305
        }
306
307
        $this->assertRightGranted($entity, $right);
308
    }
309
310
    protected function assertSubresourceListGranted($entity, $subresource)
311
    {
312
        $classMetadata = $this->getClassMetadata();
313
        /** @var PropertyMetadata $propertyMetadata */
314
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
315
        $right = $propertyMetadata->getSubResourceListRight();
316
        if (null === $right) {
317
            return;
318
        }
319
320
        $this->assertRightGranted($entity, $right);
321
    }
322
323
    protected function assertSubresourcePostGranted($entity, $subresource)
324
    {
325
        $classMetadata = $this->getClassMetadata();
326
        /** @var PropertyMetadata $propertyMetadata */
327
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
328
        $right = $propertyMetadata->getSubResourcePostRight();
329
        if (null === $right) {
330
            throw $this->createAccessDeniedException();
331
        }
332
333
        $this->assertRightGranted($entity, $right);
334
    }
335
336
    /**
337
     * @return ClassMetadata
338
     */
339 22
    protected function getClassMetadata()
340
    {
341 22
        $metaDataFactory = $this->get('ddr_rest.metadata.factory');
342
        /** @var ClassMetadata $classMetaData */
343 22
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
344
345 22
        return $classMetaData;
346
    }
347
348
    protected function getSubResourceEntityClass($subresource)
349
    {
350
        /** @var PropertyMetadata $propertyMetadata */
351
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
352
353
        return $propertyMetadata->getTargetClass();
354
    }
355
356
    protected function resolveSubject($entity, $propertyPath)
357
    {
358
        if ('this' === $propertyPath) {
359
            return $entity;
360
        }
361
        $propertyAccessor = $this->get('property_accessor');
362
363
        return $propertyAccessor->getValue($entity, $propertyPath);
364
    }
365
366
    /**
367
     * @param object $entity
368
     * @param Right  $right
369
     */
370 8
    protected function assertRightGranted($entity, Right $right)
371
    {
372 8
        $propertyPath = $right->propertyPath;
373 8
        if (null === $propertyPath) {
374 8
            $this->denyAccessUnlessGranted($right->attributes);
375
        } else {
376
            $subject = $this->resolveSubject($entity, $propertyPath);
377
            $this->denyAccessUnlessGranted($right->attributes, $subject);
378
        }
379 4
    }
380
381
    /**
382
     * @return string[]
383
     */
384
    protected function getSubresourceSerializationGroups($subresource)
385
    {
386
        return ['Default', 'ddr.rest.subresource', 'ddr.rest.' . $this->getShortName() . '.' . $subresource];
387
    }
388
389
    /**
390
     * @param string $subresource
391
     * @param object $entity
392
     *
393
     * @return
394
     */
395
    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...
396
    {
397
        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...
398
    }
399
400
    /**
401
     * @return string|null
402
     */
403
    protected function getSubresource()
404
    {
405
        return $this->getCurrentRequest()->attributes->get('_subresource');
406
    }
407
408 8
    protected function parseIncludes(Request $request)
409
    {
410 8
        $includeString = $request->query->get('include');
411 8
        if (empty($includeString)) {
412 8
            return [];
413
        }
414
415
        return explode(',', $includeString);
416
    }
417
418
    private function parseConstraintViolations(ConstraintViolationListInterface $errors)
419
    {
420
        $data = [];
421
        /** @var ConstraintViolationInterface $error */
422
        foreach ($errors as $error) {
423
            $data[] = [
424
                'propertyPath' => $error->getPropertyPath(),
425
                'message'      => $error->getMessage(),
426
                'value'        => $error->getInvalidValue()
427
            ];
428
        }
429
430
        return $data;
431
    }
432
433 4
    private function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
434
    {
435 4
        $response->headers->add(
436
            [
437 4
                'x-pagination-current-page' => $page,
438 4
                'x-pagination-per-page'     => $perPage,
439 4
                'x-pagination-total'        => $total,
440 4
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
441
            ]
442
        );
443 4
    }
444
}
445