Completed
Push — standalone ( f309de...aaef72 )
by Philip
05:10
created

RestRequestParser::isGranted()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 6
cts 8
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 2
crap 3.1406
1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Service;
4
5
use Doctrine\Common\Util\ClassUtils;
6
use Doctrine\DBAL\Types\Type;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
8
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
9
use Metadata\MetadataFactory;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\PropertyAccess\PropertyAccessor;
12
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
13
14
class RestRequestParser
15
{
16
    /**
17
     * @var MetadataFactory
18
     */
19
    private $ddrRestMetadataFactory;
20
21
    /**
22
     * @var PropertyAccessor
23
     */
24
    private $propertyAccessor;
25
26
    /**
27
     * @var AuthorizationCheckerInterface
28
     */
29
    private $authorizationChecker;
30
31 20
    public function __construct(
32
        MetadataFactory $ddrRestMetadataFactory,
33
        PropertyAccessor $propertyAccessor,
34
        AuthorizationCheckerInterface $authorizationChecker
35
    ) {
36 20
        $this->ddrRestMetadataFactory = $ddrRestMetadataFactory;
37 20
        $this->propertyAccessor = $propertyAccessor;
38 20
        $this->authorizationChecker = $authorizationChecker;
39 20
    }
40
41
    /**
42
     * @param Request     $request
43
     * @param string      $entityClass
44
     * @param object|null $entity
45
     *
46
     * @return object
47
     */
48 20
    public function parseEntity(
49
        Request $request,
50
        $entityClass,
51
        $entity = null
52
    ) {
53 20
        $method = $request->getMethod();
54 20
        $format = $request->getRequestFormat();
55
56 20
        if ('json' !== $format) {
57
            throw new \RuntimeException(sprintf('Unsupported format "%s"', $format));
58
        }
59
60 20
        $data = $this->getRequestContent($request);
61
62 20
        if (null === $entity) {
63 12
            $entity = new $entityClass;
64
        }
65
66 20
        $this->updateObject($entity, $method, $data);
67
68 20
        return $entity;
69
    }
70
71
    /**
72
     * @param Request $request
73
     *
74
     * @return array
75
     */
76 20
    protected function getRequestContent(Request $request)
77
    {
78 20
        $content = $request->getContent();
79 20
        if ('' !== $content) {
80 20
            return json_decode($content, true);
81
        }
82
83
        return $request->request->all();
84
    }
85
86
    /**
87
     * @param object $object Access by reference.
88
     * @param string $method
89
     * @param array  $data
90
     */
91 20
    protected function updateObject(
92
        &$object,
93
        $method,
94
        $data
95
    ) {
96 20
        $classMetadata = $this->ddrRestMetadataFactory->getMetadataForClass(ClassUtils::getClass($object));
97
98 20
        foreach ($data as $key => $value) {
99 18
            if (!array_key_exists($key, $classMetadata->propertyMetadata)) {
100
                continue;
101
                //throw new \RuntimeException(sprintf('No field %s for Class %s', $key, get_class($object)));
102
            }
103
            /** @var PropertyMetadata $propertyMetadata */
104 18
            $propertyMetadata = $classMetadata->propertyMetadata[$key];
105 18
            if ($this->isUpdateable($object, $method, $propertyMetadata)) {
106 18
                $this->updateProperty($object, $method, $key, $value, $propertyMetadata);
107
            }
108
        }
109 20
    }
110
111
    /**
112
     * @param object           $object Access by reference.
113
     * @param string           $method
114
     * @param string           $propertyName
115
     * @param mixed            $value
116
     * @param PropertyMetadata $propertyMetadata
117
     */
118 18
    protected function updateProperty(
119
        &$object,
120
        $method,
121
        $propertyName,
0 ignored issues
show
Unused Code introduced by
The parameter $propertyName 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...
122
        $value,
123
        PropertyMetadata $propertyMetadata
124
    ) {
125 18
        if (array_key_exists($propertyMetadata->getType(), Type::getTypesMap())) {
126 18
            $convertedValue = $this->convert($propertyMetadata->getType(), $value);
127 18
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, $convertedValue);
128
        } else {
129 2
            $this->updatePropertyObject($object, $method, $propertyMetadata, $value);
130
        }
131 18
    }
132
133 2
    protected function updatePropertyObject(
134
        &$object,
135
        string $method,
136
        PropertyMetadata $propertyMetadata,
137
        $value
138
    ) {
139 2
        $propertyObject = $this->propertyAccessor->getValue($object, $propertyMetadata->name);
140 2
        if (null === $propertyObject) {
141
            $type = $propertyMetadata->getType();
142
            $propertyObject = new $type;
143
        }
144
145 2
        $this->updateObject($propertyObject, $method, $value);
146 2
        $this->propertyAccessor->setValue($object, $propertyMetadata->name, $propertyObject);
147 2
    }
148
149
    /**
150
     * @param string           $method
151
     * @param object           $object
152
     * @param PropertyMetadata $propertyMetadata
153
     *
154
     * @return bool
155
     */
156 18
    protected function isUpdateable(
157
        $object,
158
        $method,
159
        PropertyMetadata $propertyMetadata
160
    ) {
161 18
        if ((Request::METHOD_PUT === $method || Request::METHOD_PATCH === $method) && $propertyMetadata->isPuttable()) {
162 8
            return $this->isGranted($object, $propertyMetadata->getPuttableRight());
163
        }
164 16
        if (Request:: METHOD_POST === $method && $propertyMetadata->isPostable()) {
165 10
            return $this->isGranted($object, $propertyMetadata->getPostableRight());
166
        }
167
168 12
        return false;
169
    }
170
171 18
    private function isGranted($object, ?Right $right)
172
    {
173 18
        if (null === $right) {
174 18
            return true;
175
        }
176
177 12
        $propertyPath = $right->propertyPath;
178 12
        if (null === $propertyPath) {
179 12
            return $this->authorizationChecker->isGranted($right->attributes);
180
        } else {
181
            $subject = $this->resolveSubject($object, $propertyPath);
182
183
            return $this->authorizationChecker->isGranted($right->attributes, $subject);
184
        }
185
    }
186
187
    private function resolveSubject($entity, $propertyPath)
188
    {
189
        if ('this' === $propertyPath) {
190
            return $entity;
191
        }
192
193
        return $this->propertyAccessor->getValue($entity, $propertyPath);
194
    }
195
196 18
    private function convert(?string $type, $value)
197
    {
198 18
        if (null === $value) {
199
            return $value;
200
        }
201
202
        switch ($type) {
203 18
            case 'datetime':
204 18
            case 'date':
205 18
            case 'time':
206 2
                return new \DateTime($value);
207
            default:
208 18
                return $value;
209
        }
210
    }
211
}
212