ReflectionTypeAdapter::write()   F
last analyzed

Complexity

Conditions 30
Paths 11329

Size

Total Lines 63
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 38
c 1
b 0
f 0
dl 0
loc 63
ccs 39
cts 39
cp 1
rs 0
cc 30
nc 11329
nop 2
crap 30

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