Completed
Push — split-exclusion-strategies ( e073a8 )
by Nate
03:36
created

ReflectionTypeAdapter   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 126
dl 0
loc 285
rs 8.8798
c 0
b 0
f 0
wmc 44

3 Methods

Rating   Name   Duplication   Size   Complexity  
F write() 0 64 19
F read() 0 103 24
A __construct() 0 20 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\JsonAdapter;
12
use Tebru\Gson\ClassMetadata;
13
use Tebru\Gson\Internal\Data\Property;
14
use Tebru\Gson\Internal\DefaultDeserializationExclusionData;
15
use Tebru\Gson\Internal\DefaultSerializationExclusionData;
16
use Tebru\Gson\Internal\Excluder;
17
use Tebru\Gson\Internal\ObjectConstructor\CreateFromInstance;
18
use Tebru\Gson\Internal\ObjectConstructorAware;
19
use Tebru\Gson\Internal\ObjectConstructorAwareTrait;
20
use Tebru\Gson\Internal\TypeAdapterProvider;
21
use Tebru\Gson\JsonWritable;
22
use Tebru\Gson\Internal\Data\PropertyCollection;
23
use Tebru\Gson\JsonReadable;
24
use Tebru\Gson\Internal\ObjectConstructor;
25
use Tebru\Gson\JsonToken;
26
use Tebru\Gson\TypeAdapter;
27
use TypeError;
28
29
/**
30
 * Class ReflectionTypeAdapter
31
 *
32
 * Uses reflected class properties to read/write object
33
 *
34
 * @author Nate Brunette <[email protected]>
35
 */
36
final class ReflectionTypeAdapter extends TypeAdapter implements ObjectConstructorAware
37
{
38
    use ObjectConstructorAwareTrait;
39
40
    /**
41
     * @var PropertyCollection
42
     */
43
    private $properties;
44
45
    /**
46
     * @var ClassMetadata
47
     */
48
    private $classMetadata;
49
50
    /**
51
     * @var Excluder
52
     */
53
    private $excluder;
54
55
    /**
56
     * @var TypeAdapterProvider
57
     */
58
    private $typeAdapterProvider;
59
60
    /**
61
     * @var null|string
62
     */
63
    private $classVirtualProperty;
64
65
    /**
66
     * @var bool
67
     */
68
    private $skipSerialize;
69
70
    /**
71
     * @var bool
72
     */
73
    private $skipDeserialize;
74
75
    /**
76
     * @var bool
77
     */
78
    private $hasClassSerializationStrategies;
79
80
    /**
81
     * @var bool
82
     */
83
    private $hasPropertySerializationStrategies;
84
85
    /**
86
     * @var bool
87
     */
88
    private $hasClassDeserializationStrategies;
89
90
    /**
91
     * @var bool
92
     */
93
    private $hasPropertyDeserializationStrategies;
94
95
    /**
96
     * An memory cache of used type adapters
97
     *
98
     * @var TypeAdapter[]
99
     */
100
    private $adapters = [];
101
102
    /**
103
     * A memory cache of read properties
104
     *
105
     * @var Property[]
106
     */
107
    private $propertyCache = [];
108
109
    /**
110
     * Constructor
111
     *
112
     * @param ObjectConstructor $objectConstructor
113
     * @param ClassMetadata $classMetadata
114
     * @param Excluder $excluder
115
     * @param TypeAdapterProvider $typeAdapterProvider
116
     * @param null|string $classVirtualProperty
117
     */
118
    public function __construct(
119
        ObjectConstructor $objectConstructor,
120
        ClassMetadata $classMetadata,
121
        Excluder $excluder,
122
        TypeAdapterProvider $typeAdapterProvider,
123
        ?string $classVirtualProperty
124
    ) {
125
        $this->objectConstructor = $objectConstructor;
126
        $this->classMetadata = $classMetadata;
127
        $this->excluder = $excluder;
128
        $this->typeAdapterProvider = $typeAdapterProvider;
129
        $this->classVirtualProperty = $classVirtualProperty;
130
131
        $this->properties = $classMetadata->getProperties();
132
        $this->skipSerialize = $classMetadata->skipSerialize();
133
        $this->skipDeserialize = $classMetadata->skipDeserialize();
134
        $this->hasClassSerializationStrategies = $this->excluder->hasClassSerializationStrategies();
135
        $this->hasPropertySerializationStrategies = $this->excluder->hasPropertySerializationStrategies();
136
        $this->hasClassDeserializationStrategies = $this->excluder->hasClassDeserializationStrategies();
137
        $this->hasPropertyDeserializationStrategies = $this->excluder->hasPropertyDeserializationStrategies();
138
    }
139
    /**
140
     * Read the next value, convert it to its type and return it
141
     *
142
     * @param JsonReadable $reader
143
     * @return object
144
     */
145
    public function read(JsonReadable $reader)
146
    {
147
        if ($this->skipDeserialize) {
148
            $reader->skipValue();
149
            return null;
150
        }
151
152
        if ($reader->peek() === JsonToken::NULL) {
153
            $reader->nextNull();
154
            return null;
155
        }
156
157
        $object = $this->objectConstructor->construct();
158
        $exclusionData = $this->hasClassDeserializationStrategies || $this->hasPropertyDeserializationStrategies
159
            ? new DefaultDeserializationExclusionData(
160
                $reader->getPayload(),
161
                clone $object,
162
                $reader->getContext(),
163
                function() use ($reader) { return $reader->getPath(); }
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
Coding Style introduced by
Opening brace must be the last content on the line
Loading history...
164
            )
165
            : null;
166
167
        if ($this->hasClassDeserializationStrategies && $exclusionData) {
168
            $this->excluder->applyClassDeserializationExclusionData($exclusionData);
169
170
            if ($this->excluder->excludeClassByDeserializationStrategy($this->classMetadata)) {
171
                $reader->skipValue();
172
                return null;
173
            }
174
        }
175
176
        if ($this->hasPropertyDeserializationStrategies && $exclusionData) {
177
            $this->excluder->applyPropertyDeserializationExclusionData($exclusionData);
178
        }
179
180
        $reader->beginObject();
181
182
        if ($this->classVirtualProperty !== null) {
183
            $reader->nextName();
184
            $reader->beginObject();
185
        }
186
187
        $usesExisting = $reader->getContext()->usesExistingObject();
188
189
        while ($reader->hasNext()) {
190
            $name = $reader->nextName();
191
            $property = $this->propertyCache[$name] ?? $this->propertyCache[$name] = $this->properties->getBySerializedName($name);
192
193
            if ($property === null) {
194
                $reader->skipValue();
195
                continue;
196
            }
197
198
            $realName = $property->getName();
199
200
            if (
201
                $property->skipDeserialize()
202
                || (
203
                    $this->hasPropertyDeserializationStrategies && $exclusionData
204
                    && $this->excluder->excludePropertyByDeserializationStrategy($property)
205
                )
206
            ) {
207
                $reader->skipValue();
208
                continue;
209
            }
210
211
            $adapter = $this->adapters[$realName] ?? null;
212
            if ($adapter === null) {
213
                /** @var JsonAdapter $jsonAdapterAnnotation */
214
                $jsonAdapterAnnotation = $property->getAnnotations()->get(JsonAdapter::class);
215
                $adapter = null === $jsonAdapterAnnotation
216
                    ? $this->typeAdapterProvider->getAdapter($property->getType())
217
                    : $this->typeAdapterProvider->getAdapterFromAnnotation(
218
                        $property->getType(),
219
                        $jsonAdapterAnnotation
220
                    );
221
                $this->adapters[$realName] = $adapter;
222
            }
223
224
            if ($adapter instanceof ObjectConstructorAware && $usesExisting) {
225
                try {
226
                    $nestedObject = $property->get($object);
227
                } /** @noinspection BadExceptionsProcessingInspection */ catch (TypeError $error) {
228
                    // this may occur when attempting to get a nested object that doesn't exist and
229
                    // the method return is not nullable. The type error only occurs because we are
230
                    // may be calling the getter before data exists.
231
                    $nestedObject = null;
232
                }
233
234
                if ($nestedObject !== null) {
235
                    $adapter->setObjectConstructor(new CreateFromInstance($nestedObject));
236
                }
237
            }
238
239
            $property->set($object, $adapter->read($reader));
240
        }
241
        $reader->endObject();
242
243
        if ($this->classVirtualProperty !== null) {
244
            $reader->endObject();
245
        }
246
247
        return $object;
248
    }
249
250
    /**
251
     * Write the value to the writer for the type
252
     *
253
     * @param JsonWritable $writer
254
     * @param object $value
255
     * @return void
256
     */
257
    public function write(JsonWritable $writer, $value): void
258
    {
259
        if ($this->skipSerialize || $value === null) {
260
            $writer->writeNull();
261
            return;
262
        }
263
264
        $exclusionData = $this->hasClassSerializationStrategies || $this->hasPropertySerializationStrategies
265
            ? new DefaultSerializationExclusionData($value, function() use ($writer) { return $writer->getPath(); })
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
Coding Style introduced by
Opening brace must be the last content on the line
Loading history...
266
            : null;
267
268
        if ($this->hasClassSerializationStrategies && $exclusionData) {
269
            $this->excluder->applyClassSerializationExclusionData($exclusionData);
270
271
            if ($this->excluder->excludeClassBySerializationStrategy($this->classMetadata)) {
272
                $writer->writeNull();
273
                return;
274
            }
275
        }
276
277
        if ($this->hasPropertySerializationStrategies && $exclusionData) {
278
            $this->excluder->applyPropertySerializationExclusionData($exclusionData);
279
        }
280
281
        $writer->beginObject();
282
283
        if ($this->classVirtualProperty !== null) {
284
            $writer->name($this->classVirtualProperty);
285
            $writer->beginObject();
286
        }
287
288
        /** @var Property $property */
289
        foreach ($this->properties as $property) {
290
            $realName = $property->getName();
291
            $writer->name($property->getSerializedName());
292
293
            if (
294
                $property->skipSerialize()
295
                || (
296
                    $this->hasPropertySerializationStrategies && $exclusionData
297
                    && $this->excluder->excludePropertyBySerializationStrategy($property)
298
                )
299
            ) {
300
                $writer->writeNull();
301
302
                continue;
303
            }
304
305
            $adapter = $this->adapters[$realName] ?? null;
306
            if ($adapter === null) {
307
                /** @var JsonAdapter $jsonAdapterAnnotation */
308
                $jsonAdapterAnnotation = $property->getAnnotations()->get(JsonAdapter::class);
309
                $adapter = null === $jsonAdapterAnnotation
310
                    ? $this->typeAdapterProvider->getAdapter($property->getType())
311
                    : $this->typeAdapterProvider->getAdapterFromAnnotation($property->getType(), $jsonAdapterAnnotation);
312
                $this->adapters[$realName] = $adapter;
313
            }
314
            $adapter->write($writer, $property->get($value));
315
        }
316
317
        $writer->endObject();
318
319
        if ($this->classVirtualProperty !== null) {
320
            $writer->endObject();
321
        }
322
    }
323
}
324