Completed
Push — class-metadata-visitor ( 0a5a84...7b4c97 )
by Nate
02:04
created

ReflectionTypeAdapter::write()   F

Complexity

Conditions 22
Paths 1221

Size

Total Lines 79
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 47
c 1
b 0
f 0
nc 1221
nop 2
dl 0
loc 79
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

251
                    /** @scrutinizer ignore-call */ 
252
                    $nestedObject = $property->get($object);
Loading history...
252
                } /** @noinspection BadExceptionsProcessingInspection */ catch (TypeError $error) {
253
                    // this may occur when attempting to get a nested object that doesn't exist and
254
                    // the method return is not nullable. The type error only occurs because we are
255
                    // may be calling the getter before data exists.
256
                    $nestedObject = null;
257
                }
258
259
                if ($nestedObject !== null) {
260
                    $adapter->setObjectConstructor(new CreateFromInstance($nestedObject));
261
                }
262
            }
263
264
            $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

264
            $property->/** @scrutinizer ignore-call */ 
265
                       set($object, $adapter->read($reader));
Loading history...
265
        }
266
        $reader->endObject();
267
268
        if ($this->classVirtualProperty !== null) {
269
            $reader->endObject();
270
        }
271
272
        return $object;
273
    }
274
275
    /**
276
     * Write the value to the writer for the type
277
     *
278
     * @param JsonWritable $writer
279
     * @param object $value
280
     * @return void
281
     */
282
    public function write(JsonWritable $writer, $value): void
283
    {
284
        if ($this->skipSerialize || $value === null) {
285
            $writer->writeNull();
286
            return;
287
        }
288
289
        $exclusionData = $this->hasClassSerializationStrategies || $this->hasPropertySerializationStrategies
290
            ? new DefaultSerializationExclusionData($value, $writer)
291
            : null;
292
293
        if ($this->hasClassSerializationStrategies && $exclusionData) {
294
            $this->excluder->applyClassSerializationExclusionData($exclusionData);
295
296
            if ($this->excluder->excludeClassBySerializationStrategy($this->classMetadata)) {
297
                $writer->writeNull();
298
                return;
299
            }
300
        }
301
302
        if ($this->hasPropertySerializationStrategies && $exclusionData) {
303
            $this->excluder->applyPropertySerializationExclusionData($exclusionData);
304
        }
305
306
        $writer->beginObject();
307
308
        if ($this->classVirtualProperty !== null) {
309
            $writer->name($this->classVirtualProperty);
310
            $writer->beginObject();
311
        }
312
313
        /** @var Property $property */
314
        foreach ($this->properties as $property) {
315
            $realName = $property->getName();
316
            $writer->name($property->getSerializedName());
317
318
            $exclusionAnnotation = $property->getAnnotation(ExclusionStrategy::class);
319
            if ($exclusionAnnotation !== null) {
320
                $type = TypeToken::create($exclusionAnnotation->getValue());
321
                if ($type->isA(PropertySerializationExclusionStrategy::class)) {
322
                    /** @var PropertySerializationExclusionStrategy $exclusionStrategy */
323
                    $exclusionStrategy = $this->constructorConstructor->get($type)->construct();
324
                    if ($exclusionStrategy instanceof SerializationExclusionDataAware) {
325
                        $propertyExclusionData = new DefaultSerializationExclusionData($value, $writer);
326
                        $exclusionStrategy->setSerializationExclusionData($propertyExclusionData);
327
                    }
328
329
                    if ($exclusionStrategy->skipSerializingProperty($property)) {
330
                        $writer->writeNull();
331
                        continue;
332
                    }
333
                }
334
            }
335
336
            if (
337
                $property->skipSerialize()
338
                || ($this->hasPropertySerializationStrategies && $this->excluder->excludePropertyBySerializationStrategy($property))
339
            ) {
340
                $writer->writeNull();
341
342
                continue;
343
            }
344
345
            $adapter = $this->adapters[$realName] ?? null;
346
            if ($adapter === null) {
347
                /** @var JsonAdapter $jsonAdapterAnnotation */
348
                $jsonAdapterAnnotation = $property->getAnnotations()->get(JsonAdapter::class);
349
                $adapter = null === $jsonAdapterAnnotation
350
                    ? $this->typeAdapterProvider->getAdapter($property->getType())
351
                    : $this->typeAdapterProvider->getAdapterFromAnnotation($property->getType(), $jsonAdapterAnnotation);
352
                $this->adapters[$realName] = $adapter;
353
            }
354
            $adapter->write($writer, $property->get($value));
355
        }
356
357
        $writer->endObject();
358
359
        if ($this->classVirtualProperty !== null) {
360
            $writer->endObject();
361
        }
362
    }
363
}
364