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.

ObjectHydrator   F
last analyzed

Complexity

Total Complexity 73

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 96.27%

Importance

Changes 0
Metric Value
wmc 73
lcom 1
cbo 3
dl 0
loc 310
ccs 129
cts 134
cp 0.9627
rs 2.56
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A hydrateObject() 0 12 3
A hydrateObjectByReflection() 0 10 1
A getEmptyObject() 0 4 1
A getClassProperty() 0 9 2
A _getClassProperty() 0 14 4
C castValueToBuiltinType() 0 41 16
A detectIfPropertyIsArrayFromComment() 0 9 2
A _detectIfPropertyIsArrayFromComment() 0 12 4
A detectClassNameFromPropertyComment() 0 9 2
A _detectClassNameFromPropertyComment() 0 19 6
A resolveShortClassName() 0 13 4
A matchAndSetNonConstructorProperties() 0 21 5
A hydrateObjectProperty() 0 5 1
C hydrateProperty() 0 50 12
A isScalar() 0 9 2
A _isScalar() 0 9 2
A parseTypeFromPropertyVarDoc() 0 9 2
A detectClassNameFromPropertyType() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like ObjectHydrator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ObjectHydrator, and based on these observations, apply Extract Interface, too.

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
        $this->objectUnserializer = $objectUnserializer;
24 19
    }
25 19
26
    /**
27
     * @param string $objectClass
28
     * @param $serializedValue
29
     * @return object
30
     */
31
    public function hydrateObject(string $objectClass, $serializedValue)
32 19
    {
33
        try {
34
            return $this->castValueToBuiltinType($objectClass, $serializedValue);
35 19
        } catch (ValueNotScalar $exception) {
36 7
            try {
37
                return $this->objectUnserializer->tryToUnserializeValue($objectClass, $serializedValue);
38 7
            } catch (AdapterNotFoundException $exception) {
39 6
                return $this->hydrateObjectByReflection($objectClass, $serializedValue);
40 6
            }
41
        }
42
    }
43
44
    private function hydrateObjectByReflection(string $objectClass, $document)
45 6
    {
46
        $reflectionClass = new \ReflectionClass($objectClass);
47 6
48
        $object = unserialize($this->getEmptyObject($objectClass));
49 6
50
        $this->matchAndSetNonConstructorProperties($reflectionClass, $object, $document);
51 6
52
        return $object;
53 6
    }
54
55
    private function getEmptyObject(string $className)
56 6
    {
57
        return 'O:' . strlen($className) . ':"' . $className . '":0:{}';
58 6
    }
59
60
    private function getClassProperty(\ReflectionClass $reflectionClass, string $propertyName): ?\ReflectionProperty
61 7
    {
62
        static $cache = [];
63 7
        $cache_id = $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...
64
        if (!isset($cache[$cache_id])) {
65 3
            $cache[$cache_id] = $this->_getClassProperty($reflectionClass, $propertyName);
66 3
        }
67 1
        return $cache[$cache_id];
68
    }
69
70 2
    private function _getClassProperty(\ReflectionClass $reflectionClass, string $propertyName): ?\ReflectionProperty
71
    {
72
        if (!$reflectionClass->hasProperty($propertyName)) {
73 7
74
            $parentClass = $reflectionClass->getParentClass();
75
            if (!$parentClass || !($property = $this->getClassProperty($parentClass, $propertyName))) {
76 19
                throw new \Exception("class {$reflectionClass->name} does not contain the property {$propertyName}");
77
            }
78 19
79 19
            return $property;
80 3
        }
81
82 18
        return $reflectionClass->getProperty($propertyName);
83 2
    }
84
85 17
    private function castValueToBuiltinType($type, $value)
86 9
    {
87 6
        switch ((string)$type) {
88
            case 'string':
89 4
                return strval($value);
90 3
91
            case 'mixed':
92 1
                return $value;
93
94 13
            case 'int':
95 2
                if (\is_int($value)) {
96
                    return $value;
97 12
                }
98 8
                if (is_string($value) && ($value === (string)((int)$value))) {
99 6
                    return (int)$value;
100 2
                }
101
                return null;
102 6
103 4
            case 'float':
104 2
                return floatval($value);
105 2
106
            case 'bool':
107
            case 'boolean':
108
                if (\is_bool($value)) {
109 8
                    return $value;
110 2
                }
111
                if ($value === '1' || $value === 'true') {
112
                    return true;
113 7
                } else {
114
                    if ($value === '0' || $value === 'false') {
115
                        return false;
116 8
                    }
117
                }
118 8
                return null;
119 8
120 8
            case 'null':
121 7
                return null;
122
        }
123 8
124
        throw new ValueNotScalar("Unknown builtin type: $type");
125
    }
126 7
127
    private function detectIfPropertyIsArrayFromComment(\ReflectionClass $reflectionClass, string $propertyName)
128 7
    {
129 7
        static $cache = [];
130 7
        $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...
131 1
        if (!isset($cache[$cacheId])) {
132
            $cache[$cacheId] = $this->_detectIfPropertyIsArrayFromComment($reflectionClass, $propertyName);
133 7
        }
134
        return $cache[$cacheId];
135
    }
136 8
137
    private function _detectIfPropertyIsArrayFromComment(\ReflectionClass $reflectionClass, string $propertyName)
138 8
    {
139 8
        $shortType = $this->parseTypeFromPropertyVarDoc($reflectionClass, $propertyName);
140 8
        if (null === $shortType) {
141 7
            return false;
142
        }
143 8
        $len = strlen($shortType);
144
        if ($len < 3) {
145
            return false;
146 7
        }
147
        return $shortType[$len - 2] === '[' && $shortType[$len - 1] === ']';
148 7
    }
149 7
150 7
    private function detectClassNameFromPropertyComment(\ReflectionClass $reflectionClass, string $propertyName)
151 1
    {
152
        static $cache = [];
153 7
        $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...
154 1
        if (!isset($cache[$cacheId])) {
155
            $cache[$cacheId] = $this->_detectClassNameFromPropertyComment($reflectionClass, $propertyName);
156 7
        }
157 6
        return $cache[$cacheId];
158
    }
159
160 3
    private function _detectClassNameFromPropertyComment(\ReflectionClass $reflectionClass, string $propertyName)
161
    {
162
        $shortType = $this->parseTypeFromPropertyVarDoc($reflectionClass, $propertyName);
163 3
        $shortType = rtrim($shortType, '[]');
164
        if ($shortType === '' || null === $shortType) {
165 3
            return null;
166 3
        }
167
        if ('array' === $shortType) {
168
            return null;
169
        }
170
        if ('\\' == $shortType[0]) {
171
            return ltrim($shortType, '\\');
172
        }
173
        if ($this->isScalar($shortType)) {
174 3
            return $shortType;
175
        }
176
177 6
        return ltrim($this->resolveShortClassName($shortType, $reflectionClass), '\\');
178
    }
179 6
180 6
    private function resolveShortClassName($shortName, \ReflectionClass $contextClass)
181 1
    {
182
        $className = (new FqnResolver())->resolveShortClassName($shortName, $contextClass);
183
        if (!class_exists($className)) {
184
            foreach ($contextClass->getTraits() as $trait) {
185 6
                $className = (new FqnResolver())->resolveShortClassName($shortName, $trait);
186
                if (class_exists($className)) {
187 6
                    return $className;
188
                }
189 6
            }
190 6
        }
191 6
        return $className;
192 6
    }
193 1
194 6
    private function matchAndSetNonConstructorProperties(\ReflectionClass $reflectionClass, $object, $document): void
195
    {
196
        foreach ($document as $propertyName => $value) {
197 6
            if ('@classes' === $propertyName) {
198
                continue;
199 2
            }
200
201 2
            try {
202 2
                $actualClassName = isset($document['@classes'][$propertyName]) ? $document['@classes'][$propertyName] : null;
203
204
                $result = $this->hydrateProperty($reflectionClass, $propertyName, $value, $actualClassName);
205 8
206
                $property = $this->getClassProperty($reflectionClass, $propertyName);
207 8
                $property->setAccessible(true);
208 1
                $property->setValue($object, $result);
209
                $property->setAccessible(false);
210
            } catch (\Exception $exception) {
211 8
                continue;
212
            }
213 8
        }
214 3
    }
215 3
216
    public function hydrateObjectProperty($objectClass, string $propertyName, $document)
217
    {
218 8
        $reflectionClass = new \ReflectionClass($objectClass);
219 8
        return $this->hydrateProperty($reflectionClass, $propertyName, $document);
220
    }
221
222 1
    private function hydrateProperty(\ReflectionClass $reflectionClass, string $propertyName, $document, ?string $actualClassName = null)
223
    {
224
        if (null === $document) {
225 8
            return null;
226
        }
227 8
228 4
        $isArray = null;
229 4
230 4
        if (!$actualClassName) {
231
            try {
232
                $reflectionType = $this->detectClassNameFromPropertyType($reflectionClass, $propertyName);
233 7
                $reflectionTypeString = null;
234
                if ($reflectionType) {
235
                    $isArray = false;
236
                    if ($reflectionType->isBuiltin()) {
237 8
                        if ($actualClassName === 'array') {
238
                            $isArray = true;
239
                        } else {
240
                            return $this->castValueToBuiltinType($reflectionType->__toString(), $document);
241
                        }
242
                    }
243
                    $reflectionTypeString = $reflectionType->__toString();
244 7
                }
245
                $propertyClassName = $reflectionTypeString ?? $this->detectClassNameFromPropertyComment($reflectionClass, $propertyName);
246 7
            } catch (\Exception $exception) {
247 7
                $propertyClassName = null;
248 7
            }
249 4
250
            if (null === $propertyClassName || 'null' === $propertyClassName) {
251 7
                return $document;
252
            }
253
        } else {
254
            $propertyClassName = $actualClassName;
255
        }
256
257
        if (null === $isArray) {
258 4
            $isArray = $this->detectIfPropertyIsArrayFromComment($reflectionClass, $propertyName);
259
        }
260
261 4
        if ($isArray) {
262 3
            $result = [];
263 2
            foreach ($document as $k => $item) {
264 2
                $result[$k] = $this->hydrateObject($propertyClassName, $item);
265
            }
266
        } else {
267
            $result = $this->hydrateObject($propertyClassName, $document);
268 7
        }
269
270 7
        return $result;
271
    }
272 7
273 3
    /**
274
     * @param $shortType
275 7
     * @return bool
276
     */
277
    private function isScalar($shortType): bool
278
    {
279
        static $cache = [];
280
        $cacheId = $shortType;
281
        if (!isset($cache[$cacheId])) {
282
            $cache[$cacheId] = $this->_isScalar($shortType);
283
        }
284
        return $cache[$cacheId];
285
    }
286
287
    /**
288
     * @param $shortType
289
     * @return bool
290
     */
291
    private function _isScalar($shortType): bool
292
    {
293
        try {
294
            $this->castValueToBuiltinType($shortType, '1');
295
            return true;
296
        } catch (ValueNotScalar $exception) {
297
            return false;
298
        }
299
    }
300
301
    private function parseTypeFromPropertyVarDoc(\ReflectionClass $reflectionClass, string $propertyName)
302
    {
303
        $property = $this->getClassProperty($reflectionClass, $propertyName);
304
305
        if (!preg_match('#\@var\s+(?P<shortType>[\\\\a-z0-9_\]\[]+)#ims', $property->getDocComment() ?? '', $m)) {
306
            return null;
307
        }
308
        return $m['shortType'];
309
    }
310
311
    private function detectClassNameFromPropertyType(\ReflectionClass $reflectionClass, string $propertyName): ?\ReflectionType
312
    {
313
        $property = $this->getClassProperty($reflectionClass, $propertyName);
314
        if (!$property) {
315
            return null;
316
        }
317
        if ($property->hasType()) {
318
            return $property->getType();
319
        }
320
        return null;
321
    }
322
}