GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( e79dc7...bd1e28 )
by Constantin
05:09
created

_detectClassNameFromPropertyComment()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.7333
c 0
b 0
f 0
cc 4
nc 4
nop 2
crap 4
1
<?php
2
/******************************************************************************
3
 * Copyright (c) 2017 Constantin Galbenu <[email protected]>             *
4
 ******************************************************************************/
5
6
namespace Gica\Serialize\ObjectHydrator;
7
8
9
use Gica\CodeAnalysis\Shared\FqnResolver;
10
use Gica\Serialize\ObjectHydrator\Exception\AdapterNotFoundException;
11
use Gica\Serialize\ObjectHydrator\Exception\ValueNotScalar;
12
13
class ObjectHydrator
14
{
15
    /**
16
     * @var ObjectUnserializer
17
     */
18
    private $objectUnserializer;
19
20 19
    public function __construct(
21
        ObjectUnserializer $objectUnserializer
22
    )
23
    {
24 19
        $this->objectUnserializer = $objectUnserializer;
25 19
    }
26
27
    /**
28
     * @param string $objectClass
29
     * @param $serializedValue
30
     * @return object
31
     */
32 19
    public function hydrateObject(string $objectClass, $serializedValue)
33
    {
34
        try {
35 19
            return $this->castValueToBuiltinType($objectClass, $serializedValue);
36 7
        } catch (ValueNotScalar $exception) {
37
            try {
38 7
                return $this->objectUnserializer->tryToUnserializeValue($objectClass, $serializedValue);
39 6
            } catch (AdapterNotFoundException $exception) {
40 6
                return $this->hydrateObjectByReflection($objectClass, $serializedValue);
41
            }
42
        }
43
    }
44
45 6
    private function hydrateObjectByReflection(string $objectClass, $document)
46
    {
47 6
        $reflectionClass = new \ReflectionClass($objectClass);
48
49 6
        $object = unserialize($this->getEmptyObject($objectClass));
50
51 6
        $this->matchAndSetNonConstructorProperties($reflectionClass, $object, $document);
52
53 6
        return $object;
54
    }
55
56 6
    private function getEmptyObject(string $className)
57
    {
58 6
        return 'O:' . strlen($className) . ':"' . $className . '":0:{}';
59
    }
60
61 7
    private function getClassProperty(\ReflectionClass $reflectionClass, string $propertyName): ?\ReflectionProperty
62
    {
63 7
        if (!$reflectionClass->hasProperty($propertyName)) {
64
65 3
            $parentClass = $reflectionClass->getParentClass();
66 3
            if (!$parentClass || !($property = $this->getClassProperty($parentClass, $propertyName))) {
67 1
                throw new \Exception("class {$reflectionClass->name} does not contain the property {$propertyName}");
68
            }
69
70 2
            return $property;
71
        }
72
73 7
        return $reflectionClass->getProperty($propertyName);
74
    }
75
76 19
    private function castValueToBuiltinType($type, $value)
77
    {
78 19
        switch ((string)$type) {
79 19
            case 'string':
80 3
                return strval($value);
81
82 18
            case 'mixed':
83 2
                return $value;
84
85 17
            case 'int':
86 9
                if (\is_int($value)) {
87 6
                    return $value;
88
                }
89 4
                if (ctype_digit($value)) {
90 3
                    return intval($value);
91
                }
92 1
                return null;
93
94 13
            case 'float':
95 2
                return floatval($value);
96
97 12
            case 'bool':
98 8
            case 'boolean':
99 6
                if (\is_bool($value)) {
100 2
                    return $value;
101
                }
102 6
                if ($value === '1' || $value === 'true') {
103 4
                    return true;
104 2
                } else if ($value === '0' || $value === 'false') {
105 2
                    return false;
106
                }
107
                return null;
108
109 8
            case 'null':
110 2
                return null;
111
        }
112
113 7
        throw new ValueNotScalar("Unknown builtin type: $type");
114
    }
115
116 8
    private function detectIfPropertyIsArrayFromComment(\ReflectionClass $reflectionClass, string $propertyName)
117
    {
118 8
        static $cache = [];
119 8
        $cacheId = $reflectionClass->getName() . '-' . $propertyName;
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
120 8
        if (!isset($cache[$cacheId])) {
121 7
            $cache[$cacheId] = $this->_detectIfPropertyIsArrayFromComment($reflectionClass, $propertyName);
122
        }
123 8
        return $cache[$cacheId];
124
    }
125
126 7
    private function _detectIfPropertyIsArrayFromComment(\ReflectionClass $reflectionClass, string $propertyName)
127
    {
128 7
        $shortType = $this->parseTypeFromPropertyVarDoc($reflectionClass, $propertyName);
129 7
        $len = strlen($shortType);
130 7
        if ($len < 3) {
131 1
            return false;
132
        }
133 7
        return $shortType[$len - 2] === '[' && $shortType[$len - 1] === ']';
134
    }
135
136 8
    private function detectClassNameFromPropertyComment(\ReflectionClass $reflectionClass, string $propertyName)
137
    {
138 8
        static $cache = [];
139 8
        $cacheId = $reflectionClass->getName() . '-' . $propertyName;
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
140 8
        if (!isset($cache[$cacheId])) {
141 7
            $cache[$cacheId] = $this->_detectClassNameFromPropertyComment($reflectionClass, $propertyName);
142
        }
143 8
        return $cache[$cacheId];
144
    }
145
146 7
    private function _detectClassNameFromPropertyComment(\ReflectionClass $reflectionClass, string $propertyName)
147
    {
148 7
        $shortType = $this->parseTypeFromPropertyVarDoc($reflectionClass, $propertyName);
149 7
        $shortType = rtrim($shortType, '[]');
150 7
        if ('array' === $shortType) {
151 1
            return null;
152
        }
153 7
        if ('\\' == $shortType[0]) {
154 1
            return ltrim($shortType, '\\');
155
        }
156 7
        if ($this->isScalar($shortType)) {
157 6
            return $shortType;
158
        }
159
160 3
        return ltrim($this->resolveShortClassName($shortType, $reflectionClass), '\\');
161
    }
162
163 3
    private function resolveShortClassName($shortName, \ReflectionClass $contextClass)
164
    {
165 3
        $className = (new FqnResolver())->resolveShortClassName($shortName, $contextClass);
166 3
        if (!class_exists($className)) {
167
            foreach ($contextClass->getTraits() as $trait) {
168
                $className = (new FqnResolver())->resolveShortClassName($shortName, $trait);
169
                if (class_exists($className)) {
170
                    return $className;
171
                }
172
            }
173
        }
174 3
        return $className;
175
    }
176
177 6
    private function matchAndSetNonConstructorProperties(\ReflectionClass $reflectionClass, $object, $document): void
178
    {
179 6
        foreach ($document as $propertyName => $value) {
180 6
            if ('@classes' === $propertyName) {
181 1
                continue;
182
            }
183
184
            try {
185 6
                $actualClassName = isset($document['@classes'][$propertyName]) ? $document['@classes'][$propertyName] : null;
186
187 6
                $result = $this->hydrateProperty($reflectionClass, $propertyName, $value, $actualClassName);
188
189 6
                $property = $this->getClassProperty($reflectionClass, $propertyName);
190 6
                $property->setAccessible(true);
191 6
                $property->setValue($object, $result);
192 6
                $property->setAccessible(false);
193 1
            } catch (\Exception $exception) {
194 6
                continue;
195
            }
196
        }
197 6
    }
198
199 2
    public function hydrateObjectProperty($objectClass, string $propertyName, $document)
200
    {
201 2
        $reflectionClass = new \ReflectionClass($objectClass);
202 2
        return $this->hydrateProperty($reflectionClass, $propertyName, $document);
203
    }
204
205 8
    private function hydrateProperty(\ReflectionClass $reflectionClass, string $propertyName, $document, ?string $actualClassName = null)
206
    {
207 8
        if (null === $document) {
208 1
            return null;
209
        }
210
211 8
        if (!$actualClassName) {
212
            try {
213 8
                $propertyClassName = $this->detectClassNameFromPropertyComment($reflectionClass, $propertyName);
214 3
            } catch (\Exception $exception) {
215 3
                $propertyClassName = null;
216
            }
217
218 8
            if (null === $propertyClassName || 'null' === $propertyClassName) {
219 8
                return $document;
220
            }
221
        } else {
222 1
            $propertyClassName = $actualClassName;
223
        }
224
225 8
        $isArray = $this->detectIfPropertyIsArrayFromComment($reflectionClass, $propertyName);
226
227 8
        if ($isArray) {
228 4
            $result = [];
229 4
            foreach ($document as $k => $item) {
230 4
                $result[$k] = $this->hydrateObject($propertyClassName, $item);
231
            }
232
        } else {
233 7
            $result = $this->hydrateObject($propertyClassName, $document);
234
        }
235
236
237 8
        return $result;
238
    }
239
240
    /**
241
     * @param $shortType
242
     * @return bool
243
     */
244 7
    private function isScalar($shortType): bool
245
    {
246 7
        static $cache = [];
247 7
        $cacheId = $shortType;
248 7
        if (!isset($cache[$cacheId])) {
249 4
            $cache[$cacheId] = $this->_isScalar($shortType);
250
        }
251 7
        return $cache[$cacheId];
252
    }
253
254
    /**
255
     * @param $shortType
256
     * @return bool
257
     */
258 4
    private function _isScalar($shortType): bool
259
    {
260
        try {
261 4
            $this->castValueToBuiltinType($shortType, '1');
262 3
            return true;
263 2
        } catch (ValueNotScalar $exception) {
264 2
            return false;
265
        }
266
    }
267
268 7
    private function parseTypeFromPropertyVarDoc(\ReflectionClass $reflectionClass, string $propertyName)
269
    {
270 7
        $property = $this->getClassProperty($reflectionClass, $propertyName);
271
272 7
        if (!preg_match('#\@var\s+(?P<shortType>[\\\\a-z0-9_\]\[]+)#ims', $property->getDocComment(), $m)) {
273 3
            throw new \Exception("Could not detect type from vardoc for property {$propertyName}");
274
        }
275 7
        return $m['shortType'];
276
    }
277
}