Completed
Pull Request — master (#44)
by Nate
04:08 queued 02:15
created

ReflectionTypeAdapter   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 299
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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

3 Methods

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

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

234
                    /** @scrutinizer ignore-call */ 
235
                    $nestedObject = $property->get($object);
Loading history...
235
                } /** @noinspection BadExceptionsProcessingInspection */ catch (TypeError $error) {
236 6
                    // this may occur when attempting to get a nested object that doesn't exist and
237
                    // the method return is not nullable. The type error only occurs because we are
238 2
                    // may be calling the getter before data exists.
239 1
                    $nestedObject = null;
240
                }
241
242
                if ($nestedObject !== null) {
243 1
                    $adapter->setObjectConstructor(new CreateFromInstance($nestedObject));
244
                }
245
            }
246 2
247 1
            $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

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