Completed
Push — master ( 2afc29...29656f )
by Philip
06:07
created

RestRequestParser::setAuthorizationChecker()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Service;
4
5
use Doctrine\Common\Util\ClassUtils;
6
use Doctrine\DBAL\Types\Type;
7
use Doctrine\ORM\EntityManagerInterface;
8
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
9
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
10
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
11
use Metadata\MetadataFactory;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\PropertyAccess\PropertyAccessor;
14
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
15
16
/**
17
 * @author Philip Washington Sorst <[email protected]>
18
 */
19
class RestRequestParser
20
{
21
    /**
22
     * @var MetadataFactory
23
     */
24
    private $metadataFactory;
25
26
    /**
27
     * @var PropertyAccessor
28
     */
29
    private $propertyAccessor;
30
31
    /**
32
     * @var AuthorizationCheckerInterface|null
33
     */
34
    private $authorizationChecker;
35
36
    /**
37
     * @var EntityManagerInterface
38
     */
39
    private $entityManager;
40
41 36
    public function __construct(
42
        MetadataFactory $metadataFactory,
43
        PropertyAccessor $propertyAccessor,
44
        EntityManagerInterface $entityManager
45
    ) {
46 36
        $this->metadataFactory = $metadataFactory;
47 36
        $this->propertyAccessor = $propertyAccessor;
48 36
        $this->entityManager = $entityManager;
49 36
    }
50
51
    /**
52
     * @param Request     $request
53
     * @param string      $entityClass
54
     * @param object|null $entity
55
     *
56
     * @return object
57
     */
58 26
    public function parseEntity(
59
        Request $request,
60
        $entityClass,
61
        $entity = null
62
    ) {
63 26
        $method = $request->getMethod();
64 26
        $format = $request->getRequestFormat();
65
66 26
        if ('json' !== $format) {
67
            throw new \RuntimeException(sprintf('Unsupported format "%s"', $format));
68
        }
69
70 26
        $data = $this->getRequestContent($request);
71
72 26
        if (null === $entity) {
73 14
            $entity = new $entityClass;
74
        }
75
76 26
        $this->updateObject($entity, $method, $data);
77
78 26
        return $entity;
79
    }
80
81
    /**
82
     * @param Request $request
83
     *
84
     * @return array
85
     */
86 26
    protected function getRequestContent(Request $request)
87
    {
88 26
        $content = $request->getContent();
89 26
        if ('' !== $content) {
90 26
            return json_decode($content, true);
91
        }
92
93
        return $request->request->all();
94
    }
95
96
    /**
97
     * @param object $object Access by reference.
98
     * @param string $method
99
     * @param array  $data
100
     */
101 26
    protected function updateObject(
102
        &$object,
103
        $method,
104
        $data
105
    ) {
106 26
        $classMetadata = $this->metadataFactory->getMetadataForClass(ClassUtils::getClass($object));
107
108 26
        foreach ($data as $key => $value) {
109 24
            if (array_key_exists($key, $classMetadata->propertyMetadata)) {
110
                /** @var PropertyMetadata $propertyMetadata */
111 24
                $propertyMetadata = $classMetadata->propertyMetadata[$key];
112 24
                if ($this->isUpdateable($object, $method, $propertyMetadata)) {
113 24
                    $this->updateProperty($object, $method, $propertyMetadata, $value);
114
                }
115
            }
116
        }
117 26
    }
118
119
    /**
120
     * @param object           $object Access by reference.
121
     * @param string           $method
122
     * @param PropertyMetadata $propertyMetadata
123
     * @param mixed            $value
124
     */
125 24
    protected function updateProperty(
126
        &$object,
127
        string $method,
128
        PropertyMetadata $propertyMetadata,
129
        $value
130
    ) {
131 24
        $byReference = $this->isUpdateableByReference($propertyMetadata, $method);
132 24
        if ($byReference) {
133 4
            $this->updateByReference($object, $propertyMetadata, $value);
134 20
        } elseif (array_key_exists($propertyMetadata->getType(), Type::getTypesMap())) {
135 20
            $convertedValue = $this->convert($propertyMetadata->getType(), $value);
136 20
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, $convertedValue);
137
        } else {
138 2
            $this->updatePropertyObject($object, $method, $propertyMetadata, $value);
139
        }
140 24
    }
141
142 4
    private function updateByReference(&$object, PropertyMetadata $propertyMetadata, $value)
143
    {
144 4
        if (null === $value) {
145
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, null);
146
        } else {
147 4
            $type = $propertyMetadata->getType();
148 4
            $classMetadata = $this->entityManager->getClassMetadata($type);
149 4
            $identifiers = $classMetadata->getIdentifier();
150 4
            $id = [];
151 4
            foreach ($identifiers as $idName) {
152 4
                $id[$idName] = $value[$idName];
153
            }
154 4
            $reference = $this->entityManager->getReference($type, $id);
155 4
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, $reference);
156
        }
157 4
    }
158
159 2
    protected function updatePropertyObject(
160
        &$object,
161
        string $method,
162
        PropertyMetadata $propertyMetadata,
163
        $value
164
    ) {
165 2
        $propertyObject = $this->propertyAccessor->getValue($object, $propertyMetadata->name);
166 2
        if (null === $propertyObject) {
167
            $type = $propertyMetadata->getType();
168
            $propertyObject = new $type;
169
        }
170
171 2
        $this->updateObject($propertyObject, $method, $value);
172 2
        $this->propertyAccessor->setValue($object, $propertyMetadata->name, $propertyObject);
173 2
    }
174
175
    /**
176
     * @param string           $method
177
     * @param object           $object
178
     * @param PropertyMetadata $propertyMetadata
179
     *
180
     * @return bool
181
     */
182 24
    protected function isUpdateable(
183
        $object,
184
        string $method,
185
        PropertyMetadata $propertyMetadata
186
    ): bool {
187 24
        if ((Request::METHOD_PUT === $method || Request::METHOD_PATCH === $method) && $propertyMetadata->isPuttable()) {
188 10
            return $this->isGranted($object, $propertyMetadata->getPuttable()->right);
189
        }
190
191 20
        if (Request:: METHOD_POST === $method && $propertyMetadata->isPostable()) {
192 14
            return $this->isGranted($object, $propertyMetadata->getPostable()->right);
193
        }
194
195 12
        return false;
196
    }
197
198 24
    private function isGranted($object, ?Right $right): bool
199
    {
200 24
        if (null === $right) {
201 24
            return true;
202
        }
203
204
        /* If no Security is enabled always deny access */
205 12
        if (null === $this->authorizationChecker) {
206
            return false;
207
        }
208
209 12
        $propertyPath = $right->propertyPath;
210 12
        if (null === $propertyPath) {
211 12
            return $this->authorizationChecker->isGranted($right->attributes);
212
        } else {
213
            $subject = $this->resolveSubject($object, $propertyPath);
214
215
            return $this->authorizationChecker->isGranted($right->attributes, $subject);
216
        }
217
    }
218
219
    private function resolveSubject($entity, $propertyPath)
220
    {
221
        if ('this' === $propertyPath) {
222
            return $entity;
223
        }
224
225
        return $this->propertyAccessor->getValue($entity, $propertyPath);
226
    }
227
228 20
    private function convert(?string $type, $value)
229
    {
230 20
        if (null === $value) {
231
            return $value;
232
        }
233
234
        switch ($type) {
235 20
            case 'datetime':
236 20
            case 'date':
237 20
            case 'time':
238 2
                return new \DateTime($value);
239
            default:
240 20
                return $value;
241
        }
242
    }
243
244 24
    private function isUpdateableByReference(PropertyMetadata $propertyMetadata, string $method)
245
    {
246
        if (
247 24
            Method::PUT === $method
248 10
            && null !== $propertyMetadata->getPuttable() && true === $propertyMetadata->getPuttable()->byReference
249
        ) {
250 2
            return true;
251
        }
252
253
        if (
254 22
            Method::POST === $method
255 14
            && null !== $propertyMetadata->getPostable() && true === $propertyMetadata->getPostable()->byReference
256
        ) {
257 2
            return true;
258
        }
259
260 20
        return false;
261
    }
262
263
    /**
264
     * @param AuthorizationCheckerInterface $authorizationChecker
265
     */
266 36
    public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker)
267
    {
268 36
        $this->authorizationChecker = $authorizationChecker;
269 36
    }
270
}
271