Passed
Push — master ( 37395a...ba940f )
by Nate
01:20 queued 11s
created

ReflectionTypeAdapter   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 299
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 133
dl 0
loc 299
ccs 119
cts 119
cp 1
rs 3.6
c 3
b 0
f 0
wmc 60

3 Methods

Rating   Name   Duplication   Size   Complexity  
F write() 0 67 27
F read() 0 98 32
A __construct() 0 24 1

How to fix   Complexity   

Complex Class

Complex classes like ReflectionTypeAdapter 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.

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 ReflectionTypeAdapter, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Gson\Internal\TypeAdapter;
10
11
use Tebru\Gson\Annotation\ExclusionCheck;
12
use Tebru\Gson\Annotation\JsonAdapter;
13
use Tebru\Gson\Internal\Data\Property;
14
use Tebru\Gson\Internal\Data\PropertyCollection;
15
use Tebru\Gson\Internal\DefaultClassMetadata;
16
use Tebru\Gson\Internal\DefaultDeserializationExclusionData;
17
use Tebru\Gson\Internal\DefaultSerializationExclusionData;
18
use Tebru\Gson\Internal\Excluder;
19
use Tebru\Gson\Internal\ObjectConstructor;
20
use Tebru\Gson\Internal\ObjectConstructor\CreateFromInstance;
21
use Tebru\Gson\Internal\ObjectConstructorAware;
22
use Tebru\Gson\Internal\ObjectConstructorAwareTrait;
23
use Tebru\Gson\Internal\TypeAdapterProvider;
24
use Tebru\Gson\JsonReadable;
25
use Tebru\Gson\JsonToken;
26
use Tebru\Gson\JsonWritable;
27
use Tebru\Gson\TypeAdapter;
28
use TypeError;
29
30
/**
31
 * Class ReflectionTypeAdapter
32
 *
33
 * Uses reflected class properties to read/write object
34
 *
35
 * @author Nate Brunette <[email protected]>
36
 */
37
final class ReflectionTypeAdapter extends TypeAdapter implements ObjectConstructorAware
38
{
39
    use ObjectConstructorAwareTrait;
40
41
    /**
42
     * @var PropertyCollection
43
     */
44
    private $properties;
45
46
    /**
47
     * @var DefaultClassMetadata
48
     */
49
    private $classMetadata;
50
51
    /**
52
     * @var Excluder
53
     */
54
    private $excluder;
55
56
    /**
57
     * @var TypeAdapterProvider
58
     */
59
    private $typeAdapterProvider;
60
61
    /**
62
     * @var null|string
63
     */
64
    private $classVirtualProperty;
65
66
    /**
67
     * @var bool
68
     */
69
    private $skipSerialize;
70
71
    /**
72
     * @var bool
73
     */
74
    private $skipDeserialize;
75
76
    /**
77
     * @var bool
78
     */
79
    private $hasClassSerializationStrategies;
80
81
    /**
82
     * @var bool
83
     */
84
    private $hasPropertySerializationStrategies;
85
86
    /**
87
     * @var bool
88
     */
89
    private $hasClassDeserializationStrategies;
90
91
    /**
92
     * @var bool
93
     */
94
    private $hasPropertyDeserializationStrategies;
95
96
    /**
97
     * An memory cache of used type adapters
98
     *
99
     * @var TypeAdapter[]
100
     */
101
    private $adapters = [];
102
103
    /**
104
     * A memory cache of read properties
105
     *
106
     * @var Property[]
107
     */
108
    private $propertyCache = [];
109
110
    /**
111
     * @var bool
112
     */
113
    private $requireExclusionCheck;
114
115
    /**
116
     * @var bool
117
     */
118
    private $hasPropertyExclusionCheck;
119
120
    /**
121
     * Constructor
122
     *
123
     * @param ObjectConstructor $objectConstructor
124
     * @param DefaultClassMetadata $classMetadata
125
     * @param Excluder $excluder
126
     * @param TypeAdapterProvider $typeAdapterProvider
127
     * @param null|string $classVirtualProperty
128
     * @param bool $requireExclusionCheck
129
     * @param bool $hasPropertyExclusionCheck
130
     */
131 17
    public function __construct(
132
        ObjectConstructor $objectConstructor,
133
        DefaultClassMetadata $classMetadata,
134
        Excluder $excluder,
135
        TypeAdapterProvider $typeAdapterProvider,
136
        ?string $classVirtualProperty,
137
        bool $requireExclusionCheck,
138
        bool $hasPropertyExclusionCheck
139
    ) {
140 17
        $this->objectConstructor = $objectConstructor;
141 17
        $this->classMetadata = $classMetadata;
142 17
        $this->excluder = $excluder;
143 17
        $this->typeAdapterProvider = $typeAdapterProvider;
144 17
        $this->classVirtualProperty = $classVirtualProperty;
145 17
        $this->requireExclusionCheck = $requireExclusionCheck;
146 17
        $this->hasPropertyExclusionCheck = $hasPropertyExclusionCheck;
147
148 17
        $this->properties = $classMetadata->getPropertyCollection();
149 17
        $this->skipSerialize = $classMetadata->skipSerialize();
150 17
        $this->skipDeserialize = $classMetadata->skipDeserialize();
151 17
        $this->hasClassSerializationStrategies = $this->excluder->hasClassSerializationStrategies();
152 17
        $this->hasPropertySerializationStrategies = $this->excluder->hasPropertySerializationStrategies();
153 17
        $this->hasClassDeserializationStrategies = $this->excluder->hasClassDeserializationStrategies();
154 17
        $this->hasPropertyDeserializationStrategies = $this->excluder->hasPropertyDeserializationStrategies();
155 17
    }
156
    /**
157
     * Read the next value, convert it to its type and return it
158
     *
159
     * @param JsonReadable $reader
160
     * @return object
161
     */
162 11
    public function read(JsonReadable $reader)
163
    {
164 11
        if ($this->skipDeserialize) {
165 1
            $reader->skipValue();
166 1
            return null;
167
        }
168
169 10
        if ($reader->peek() === JsonToken::NULL) {
170 1
            $reader->nextNull();
171 1
            return null;
172
        }
173
174 9
        $object = $this->objectConstructor->construct();
175 9
        $classExclusionCheck = $this->hasClassDeserializationStrategies
176 9
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classMetadata->getAnnotation(ExclusionCheck::class) !== null));
177 9
        $propertyExclusionCheck = $this->hasPropertyDeserializationStrategies
178 9
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
179 9
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
180 2
            ? new DefaultDeserializationExclusionData(clone $object, $reader)
181 9
            : null;
182
183 9
        if ($classExclusionCheck && $exclusionData) {
184 1
            $this->excluder->applyClassDeserializationExclusionData($exclusionData);
185
186 1
            if ($this->excluder->excludeClassByDeserializationStrategy($this->classMetadata)) {
187 1
                $reader->skipValue();
188 1
                return null;
189
            }
190
        }
191
192 8
        if ($propertyExclusionCheck && $exclusionData) {
193 1
            $this->excluder->applyPropertyDeserializationExclusionData($exclusionData);
194
        }
195
196 8
        $reader->beginObject();
197
198 8
        if ($this->classVirtualProperty !== null) {
199 1
            $reader->nextName();
200 1
            $reader->beginObject();
201
        }
202
203 8
        $usesExisting = $reader->getContext()->usesExistingObject();
204
205 8
        while ($reader->hasNext()) {
206 8
            $name = $reader->nextName();
207 8
            $property = $this->propertyCache[$name] ?? $this->propertyCache[$name] = $this->properties->getBySerializedName($name);
208
209 8
            if ($property === null || $property->skipDeserialize()) {
210 8
                $reader->skipValue();
211 8
                continue;
212
            }
213
214 6
            $checkProperty = $this->hasPropertyDeserializationStrategies
215 6
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->getAnnotation(ExclusionCheck::class) !== null));
216 6
            if ($checkProperty && $this->excluder->excludePropertyByDeserializationStrategy($property)) {
217 1
                $reader->skipValue();
218 1
                continue;
219
            }
220
221 6
            $realName = $property->getName();
222
223 6
            $adapter = $this->adapters[$realName] ?? null;
224 6
            if ($adapter === null) {
225
                /** @var JsonAdapter $jsonAdapterAnnotation */
226 6
                $jsonAdapterAnnotation = $property->getAnnotations()->get(JsonAdapter::class);
227 6
                $adapter = null === $jsonAdapterAnnotation
228 6
                    ? $this->typeAdapterProvider->getAdapter($property->getType())
229 4
                    : $this->typeAdapterProvider->getAdapterFromAnnotation(
230 4
                        $property->getType(),
231 6
                        $jsonAdapterAnnotation
232
                    );
233 6
                $this->adapters[$realName] = $adapter;
234
            }
235
236 6
            if ($adapter instanceof ObjectConstructorAware && $usesExisting) {
237
                try {
238 2
                    $nestedObject = $property->get($object);
0 ignored issues
show
Bug introduced by
The method get() does not exist on Tebru\Gson\PropertyMetadata. Since it exists in all sub-types, consider adding an abstract or default implementation to Tebru\Gson\PropertyMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

238
                    /** @scrutinizer ignore-call */ 
239
                    $nestedObject = $property->get($object);
Loading history...
239 1
                } /** @noinspection BadExceptionsProcessingInspection */ catch (TypeError $error) {
240
                    // this may occur when attempting to get a nested object that doesn't exist and
241
                    // the method return is not nullable. The type error only occurs because we are
242
                    // may be calling the getter before data exists.
243 1
                    $nestedObject = null;
244
                }
245
246 2
                if ($nestedObject !== null) {
247 1
                    $adapter->setObjectConstructor(new CreateFromInstance($nestedObject));
248
                }
249
            }
250
251 6
            $property->set($object, $adapter->read($reader));
0 ignored issues
show
Bug introduced by
The method set() does not exist on Tebru\Gson\PropertyMetadata. Since it exists in all sub-types, consider adding an abstract or default implementation to Tebru\Gson\PropertyMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

251
            $property->/** @scrutinizer ignore-call */ 
252
                       set($object, $adapter->read($reader));
Loading history...
252
        }
253 8
        $reader->endObject();
254
255 8
        if ($this->classVirtualProperty !== null) {
256 1
            $reader->endObject();
257
        }
258
259 8
        return $object;
260
    }
261
262
    /**
263
     * Write the value to the writer for the type
264
     *
265
     * @param JsonWritable $writer
266
     * @param object $value
267
     * @return void
268
     */
269 6
    public function write(JsonWritable $writer, $value): void
270
    {
271 6
        if ($this->skipSerialize || $value === null) {
272 2
            $writer->writeNull();
273 2
            return;
274
        }
275
276 4
        $classExclusionCheck = $this->hasClassSerializationStrategies
277 4
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classMetadata->getAnnotation(ExclusionCheck::class) !== null));
278 4
        $propertyExclusionCheck = $this->hasPropertySerializationStrategies
279 4
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
280 4
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
281 2
            ? new DefaultSerializationExclusionData($value, $writer)
282 4
            : null;
283
284 4
        if ($classExclusionCheck && $exclusionData) {
285 1
            $this->excluder->applyClassSerializationExclusionData($exclusionData);
286
287 1
            if ($this->excluder->excludeClassBySerializationStrategy($this->classMetadata)) {
288 1
                $writer->writeNull();
289 1
                return;
290
            }
291
        }
292
293 3
        if ($propertyExclusionCheck && $exclusionData) {
294 1
            $this->excluder->applyPropertySerializationExclusionData($exclusionData);
295
        }
296
297 3
        $writer->beginObject();
298
299 3
        if ($this->classVirtualProperty !== null) {
300 1
            $writer->name($this->classVirtualProperty);
301 1
            $writer->beginObject();
302
        }
303
304
        /** @var Property $property */
305 3
        foreach ($this->properties as $property) {
306 3
            $writer->name($property->getSerializedName());
307 3
            if ($property->skipSerialize()) {
308 3
                $writer->writeNull();
309 3
                continue;
310
            }
311
312 3
            $checkProperty = $this->hasPropertySerializationStrategies
313 3
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->getAnnotation(ExclusionCheck::class) !== null));
314 3
            if ($checkProperty && $this->excluder->excludePropertyBySerializationStrategy($property)) {
315 1
                $writer->writeNull();
316 1
                continue;
317
            }
318
319 3
            $realName = $property->getName();
320 3
            $adapter = $this->adapters[$realName] ?? null;
321 3
            if ($adapter === null) {
322
                /** @var JsonAdapter $jsonAdapterAnnotation */
323 3
                $jsonAdapterAnnotation = $property->getAnnotations()->get(JsonAdapter::class);
324 3
                $adapter = null === $jsonAdapterAnnotation
325 3
                    ? $this->typeAdapterProvider->getAdapter($property->getType())
326 3
                    : $this->typeAdapterProvider->getAdapterFromAnnotation($property->getType(), $jsonAdapterAnnotation);
327 3
                $this->adapters[$realName] = $adapter;
328
            }
329 3
            $adapter->write($writer, $property->get($value));
330
        }
331
332 3
        $writer->endObject();
333
334 3
        if ($this->classVirtualProperty !== null) {
335 1
            $writer->endObject();
336
        }
337 3
    }
338
}
339