Completed
Push — class-metadata-visitor ( 7b4c97...bdf9cb )
by Nate
02:01
created

ReflectionTypeAdapter   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 134
dl 0
loc 301
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 101 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
     * @var bool
111
     */
112
    private $requireExclusionCheck;
113
114
    /**
115
     * @var bool
116
     */
117
    private $hasPropertyExclusionCheck;
118
119
    /**
120
     * Constructor
121
     *
122
     * @param ObjectConstructor $objectConstructor
123
     * @param DefaultClassMetadata $classMetadata
124
     * @param Excluder $excluder
125
     * @param TypeAdapterProvider $typeAdapterProvider
126
     * @param null|string $classVirtualProperty
127
     * @param bool $requireExclusionCheck
128
     * @param bool $hasPropertyExclusionCheck
129
     */
130
    public function __construct(
131
        ObjectConstructor $objectConstructor,
132
        DefaultClassMetadata $classMetadata,
133
        Excluder $excluder,
134
        TypeAdapterProvider $typeAdapterProvider,
135
        ?string $classVirtualProperty,
136
        bool $requireExclusionCheck,
137
        bool $hasPropertyExclusionCheck
138
    ) {
139
        $this->objectConstructor = $objectConstructor;
140
        $this->classMetadata = $classMetadata;
141
        $this->excluder = $excluder;
142
        $this->typeAdapterProvider = $typeAdapterProvider;
143
        $this->classVirtualProperty = $classVirtualProperty;
144
        $this->requireExclusionCheck = $requireExclusionCheck;
145
        $this->hasPropertyExclusionCheck = $hasPropertyExclusionCheck;
146
147
        $this->properties = $classMetadata->getPropertyCollection();
148
        $this->skipSerialize = $classMetadata->skipSerialize();
149
        $this->skipDeserialize = $classMetadata->skipDeserialize();
150
        $this->hasClassSerializationStrategies = $this->excluder->hasClassSerializationStrategies();
151
        $this->hasPropertySerializationStrategies = $this->excluder->hasPropertySerializationStrategies();
152
        $this->hasClassDeserializationStrategies = $this->excluder->hasClassDeserializationStrategies();
153
        $this->hasPropertyDeserializationStrategies = $this->excluder->hasPropertyDeserializationStrategies();
154
    }
155
    /**
156
     * Read the next value, convert it to its type and return it
157
     *
158
     * @param JsonReadable $reader
159
     * @return object
160
     */
161
    public function read(JsonReadable $reader)
162
    {
163
        if ($this->skipDeserialize) {
164
            $reader->skipValue();
165
            return null;
166
        }
167
168
        if ($reader->peek() === JsonToken::NULL) {
169
            $reader->nextNull();
170
            return null;
171
        }
172
173
        $object = $this->objectConstructor->construct();
174
        $classExclusionCheck = $this->hasClassDeserializationStrategies
175
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classMetadata->getAnnotation(ExclusionCheck::class) !== null));
176
        $propertyExclusionCheck = $this->hasPropertyDeserializationStrategies
177
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
178
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
179
            ? new DefaultDeserializationExclusionData(clone $object, $reader)
180
            : null;
181
182
        if ($classExclusionCheck && $exclusionData) {
183
            $this->excluder->applyClassDeserializationExclusionData($exclusionData);
184
185
            if ($this->excluder->excludeClassByDeserializationStrategy($this->classMetadata)) {
186
                $reader->skipValue();
187
                return null;
188
            }
189
        }
190
191
        if ($propertyExclusionCheck && $exclusionData) {
192
            $this->excluder->applyPropertyDeserializationExclusionData($exclusionData);
193
        }
194
195
        $reader->beginObject();
196
197
        if ($this->classVirtualProperty !== null) {
198
            $reader->nextName();
199
            $reader->beginObject();
200
        }
201
202
        $usesExisting = $reader->getContext()->usesExistingObject();
203
204
        while ($reader->hasNext()) {
205
            $name = $reader->nextName();
206
            $property = $this->propertyCache[$name] ?? $this->propertyCache[$name] = $this->properties->getBySerializedName($name);
207
208
            if ($property === null) {
209
                $reader->skipValue();
210
                continue;
211
            }
212
213
            $checkProperty = $this->hasPropertyDeserializationStrategies
214
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->getAnnotation(ExclusionCheck::class) !== null));
215
            if (
216
                $property->skipDeserialize()
217
                ||  ($checkProperty && $this->excluder->excludePropertyByDeserializationStrategy($property))
218
            ) {
219
                $reader->skipValue();
220
                continue;
221
            }
222
223
            $realName = $property->getName();
224
225
            $adapter = $this->adapters[$realName] ?? null;
226
            if ($adapter === null) {
227
                /** @var JsonAdapter $jsonAdapterAnnotation */
228
                $jsonAdapterAnnotation = $property->getAnnotations()->get(JsonAdapter::class);
229
                $adapter = null === $jsonAdapterAnnotation
230
                    ? $this->typeAdapterProvider->getAdapter($property->getType())
231
                    : $this->typeAdapterProvider->getAdapterFromAnnotation(
232
                        $property->getType(),
233
                        $jsonAdapterAnnotation
234
                    );
235
                $this->adapters[$realName] = $adapter;
236
            }
237
238
            if ($adapter instanceof ObjectConstructorAware && $usesExisting) {
239
                try {
240
                    $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

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

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