Completed
Pull Request — master (#44)
by Nate
05:11 queued 02:28
created

ReflectionTypeAdapter   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 129
c 1
b 0
f 0
dl 0
loc 309
ccs 116
cts 116
cp 1
rs 3.6
wmc 60

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getAdapter() 0 10 2
F read() 0 88 31
A __construct() 0 25 1
F write() 0 62 26

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