Completed
Push — master ( cbaa26...beec4b )
by Philip
08:56
created

RestRequestParser::getRequestContent()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 15
ccs 6
cts 8
cp 0.75
rs 9.4285
cc 3
eloc 8
nc 3
nop 1
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 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 implements RestRequestParserInterface
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
     * {@inheritdoc}
53
     */
54 26
    public function parseEntity(Request $request, $entityClass, $entity = null)
55
    {
56 26
        $method = $request->getMethod();
57
58 26
        $data = $this->getRequestContent($request);
59
60 26
        if (null === $entity) {
61 14
            $entity = new $entityClass;
62
        }
63
64 26
        $this->updateObject($entity, $method, $data);
65
66 26
        return $entity;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 26
    public function getRequestContent(Request $request)
73
    {
74 26
        $format = $request->getRequestFormat();
75
76 26
        if ('json' !== $format) {
77
            throw new \RuntimeException(sprintf('Unsupported format "%s"', $format));
78
        }
79
80 26
        $content = $request->getContent();
81 26
        if ('' !== $content) {
82 26
            return json_decode($content, true);
83
        }
84
85
        return $request->request->all();
86
    }
87
88
    /**
89
     * @param object $object Access by reference.
90
     * @param string $method
91
     * @param array  $data
92
     */
93 26
    protected function updateObject(&$object, $method, $data)
94
    {
95 26
        $classMetadata = $this->metadataFactory->getMetadataForClass(ClassUtils::getClass($object));
96
97 26
        foreach ($data as $key => $value) {
98 24
            if (array_key_exists($key, $classMetadata->propertyMetadata)) {
99
                /** @var PropertyMetadata $propertyMetadata */
100 24
                $propertyMetadata = $classMetadata->propertyMetadata[$key];
101 24
                if ($this->isUpdateable($object, $method, $propertyMetadata)) {
102 24
                    $this->updateProperty($object, $method, $propertyMetadata, $value);
103
                }
104
            }
105
        }
106 26
    }
107
108
    /**
109
     * @param object           $object Access by reference.
110
     * @param string           $method
111
     * @param PropertyMetadata $propertyMetadata
112
     * @param mixed            $value
113
     */
114 24
    protected function updateProperty(&$object, string $method, PropertyMetadata $propertyMetadata, $value)
115
    {
116 24
        $byReference = $this->isUpdateableByReference($propertyMetadata, $method);
117 24
        if ($byReference) {
118 4
            $this->updateByReference($object, $propertyMetadata, $value);
119 20
        } elseif (array_key_exists($propertyMetadata->getType(), Type::getTypesMap())) {
120 20
            $convertedValue = $this->convert($propertyMetadata->getType(), $value);
121 20
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, $convertedValue);
122
        } else {
123 2
            $this->updatePropertyObject($object, $method, $propertyMetadata, $value);
124
        }
125 24
    }
126
127 4
    private function updateByReference(&$object, PropertyMetadata $propertyMetadata, $value)
128
    {
129 4
        if (null === $value) {
130
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, null);
131
        } else {
132 4
            $type = $propertyMetadata->getType();
133 4
            $classMetadata = $this->entityManager->getClassMetadata($type);
134 4
            $identifiers = $classMetadata->getIdentifier();
135 4
            $id = [];
136 4
            foreach ($identifiers as $idName) {
137 4
                $id[$idName] = $value[$idName];
138
            }
139 4
            $reference = $this->entityManager->getReference($type, $id);
140 4
            $this->propertyAccessor->setValue($object, $propertyMetadata->name, $reference);
141
        }
142 4
    }
143
144 2
    protected function updatePropertyObject(&$object, string $method, PropertyMetadata $propertyMetadata, $value)
145
    {
146 2
        $propertyObject = $this->propertyAccessor->getValue($object, $propertyMetadata->name);
147 2
        if (null === $propertyObject) {
148
            $type = $propertyMetadata->getType();
149
            $propertyObject = new $type;
150
        }
151
152 2
        $this->updateObject($propertyObject, $method, $value);
153 2
        $this->propertyAccessor->setValue($object, $propertyMetadata->name, $propertyObject);
154 2
    }
155
156
    /**
157
     * @param string           $method
158
     * @param object           $object
159
     * @param PropertyMetadata $propertyMetadata
160
     *
161
     * @return bool
162
     */
163 24
    protected function isUpdateable($object, string $method, PropertyMetadata $propertyMetadata): bool
164
    {
165 24
        if ((Request::METHOD_PUT === $method || Request::METHOD_PATCH === $method) && $propertyMetadata->isPuttable()) {
166 10
            return $this->isGranted($object, $propertyMetadata->getPuttable()->right);
167
        }
168
169 20
        if (Request:: METHOD_POST === $method && $propertyMetadata->isPostable()) {
170 14
            return $this->isGranted($object, $propertyMetadata->getPostable()->right);
171
        }
172
173 12
        return false;
174
    }
175
176 24
    private function isGranted($object, ?Right $right): bool
177
    {
178 24
        if (null === $right) {
179 24
            return true;
180
        }
181
182
        /* If no Security is enabled always deny access */
183 12
        if (null === $this->authorizationChecker) {
184
            return false;
185
        }
186
187 12
        $propertyPath = $right->propertyPath;
188 12
        if (null === $propertyPath) {
189 12
            return $this->authorizationChecker->isGranted($right->attributes);
190
        } else {
191
            $subject = $this->resolveSubject($object, $propertyPath);
192
193
            return $this->authorizationChecker->isGranted($right->attributes, $subject);
194
        }
195
    }
196
197
    private function resolveSubject($entity, $propertyPath)
198
    {
199
        if ('this' === $propertyPath) {
200
            return $entity;
201
        }
202
203
        return $this->propertyAccessor->getValue($entity, $propertyPath);
204
    }
205
206 20
    private function convert(?string $type, $value)
207
    {
208 20
        if (null === $value) {
209
            return $value;
210
        }
211
212
        switch ($type) {
213 20
            case 'datetime':
214 20
            case 'date':
215 20
            case 'time':
216 2
                return new \DateTime($value);
217
            default:
218 20
                return $value;
219
        }
220
    }
221
222 24
    private function isUpdateableByReference(PropertyMetadata $propertyMetadata, string $method)
223
    {
224
        if (
225 24
            Method::PUT === $method
226 10
            && null !== $propertyMetadata->getPuttable() && true === $propertyMetadata->getPuttable()->byReference
227
        ) {
228 2
            return true;
229
        }
230
231
        if (
232 22
            Method::POST === $method
233 14
            && null !== $propertyMetadata->getPostable() && true === $propertyMetadata->getPostable()->byReference
234
        ) {
235 2
            return true;
236
        }
237
238 20
        return false;
239
    }
240
241
    /**
242
     * @param AuthorizationCheckerInterface $authorizationChecker
243
     */
244 36
    public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker)
245
    {
246 36
        $this->authorizationChecker = $authorizationChecker;
247 36
    }
248
}
249