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

ReflectionTypeAdapter::read()   F

Complexity

Conditions 32
Paths > 20000

Size

Total Lines 76
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 32

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 43
c 1
b 0
f 0
dl 0
loc 76
ccs 43
cts 43
cp 1
rs 0
cc 32
nc 25665
nop 2
crap 32

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\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\Context\ReaderContext;
26
use Tebru\Gson\TypeAdapter;
27
use Tebru\Gson\Context\WriterContext;
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 17
    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 17
        $this->objectConstructor = $objectConstructor;
146 17
        $this->classMetadata = $classMetadata;
147 17
        $this->excluder = $excluder;
148 17
        $this->typeAdapterProvider = $typeAdapterProvider;
149 17
        $this->classVirtualProperty = $classVirtualProperty;
150 17
        $this->requireExclusionCheck = $requireExclusionCheck;
151 17
        $this->hasPropertyExclusionCheck = $hasPropertyExclusionCheck;
152
153 17
        $this->classAnnotations = $classMetadata->annotations;
154 17
        $this->properties = $classMetadata->properties;
155 17
        $this->skipSerialize = $classMetadata->skipSerialize;
156 17
        $this->skipDeserialize = $classMetadata->skipDeserialize;
157 17
        $this->hasClassSerializationStrategies = $this->excluder->hasClassSerializationStrategies();
158 17
        $this->hasPropertySerializationStrategies = $this->excluder->hasPropertySerializationStrategies();
159 17
        $this->hasClassDeserializationStrategies = $this->excluder->hasClassDeserializationStrategies();
160 17
        $this->hasPropertyDeserializationStrategies = $this->excluder->hasPropertyDeserializationStrategies();
161 17
    }
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 11
    public function read($value, ReaderContext $context)
171
    {
172 11
        if ($this->skipDeserialize || $value === null) {
173 2
            return null;
174
        }
175
176 9
        $object = $this->objectConstructor->construct();
177 9
        $classExclusionCheck = $this->hasClassDeserializationStrategies
178 9
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classAnnotations->get(ExclusionCheck::class) !== null));
179 9
        $propertyExclusionCheck = $this->hasPropertyDeserializationStrategies
180 9
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
181 9
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
182 2
            ? new DefaultDeserializationExclusionData(clone $object, $context)
183 9
            : null;
184
185 9
        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 8
        if ($propertyExclusionCheck && $exclusionData) {
194 1
            $this->excluder->applyPropertyDeserializationExclusionData($exclusionData);
195
        }
196
197 8
        if ($this->classVirtualProperty !== null) {
198 1
            $value = array_pop($value);
199
        }
200
201 8
        $usesExisting = $context->usesExistingObject();
202 8
        $enableScalarAdapters = $context->enableScalarAdapters();
203
204 8
        foreach ($value as $name => $item) {
205 8
            $property = $this->propertyCache[$name] ?? ($this->propertyCache[$name] = ($this->properties->elements[$name] ?? null));
206
207 8
            if ($property === null || $property->skipDeserialize) {
208 8
                continue;
209
            }
210
211 6
            $checkProperty = $this->hasPropertyDeserializationStrategies
212 6
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->annotations->get(ExclusionCheck::class) !== null));
213 6
            if ($checkProperty && $this->excluder->excludePropertyByDeserializationStrategy($property)) {
214 1
                continue;
215
            }
216
217 6
            if (!$enableScalarAdapters && $property->isScalar) {
218 5
                $property->setterStrategy->set($object, $item);
219 5
                continue;
220
            }
221
222 6
            $adapter = $this->adapters[$name] ?? null;
223 6
            if ($adapter === null) {
224 6
                $adapter = $this->getAdapter($property);
225
            }
226
227 6
            if ($usesExisting && $adapter instanceof ObjectConstructorAware) {
228
                try {
229 2
                    $nestedObject = $property->getterStrategy->get($object);
230 1
                } /** @noinspection BadExceptionsProcessingInspection */ catch (TypeError $error) {
231
                    // this may occur when attempting to get a nested object that doesn't exist and
232
                    // the method return is not nullable. The type error only occurs because we are
233
                    // may be calling the getter before data exists.
234 1
                    $nestedObject = null;
235
                }
236
237 2
                if ($nestedObject !== null) {
238 1
                    $adapter->setObjectConstructor(new CreateFromInstance($nestedObject));
239
                }
240
            }
241
242 6
            $property->setterStrategy->set($object, $adapter->read($item, $context));
243
        }
244
245 8
        return $object;
246
    }
247
248
    /**
249
     * Write the value to the writer for the type
250
     *
251
     * @param object $value
252
     * @param WriterContext $context
253
     * @return array|null
254
     */
255 6
    public function write($value, WriterContext $context): ?array
256
    {
257 6
        if ($this->skipSerialize || $value === null) {
258 2
            return null;
259
        }
260
261 4
        $classExclusionCheck = $this->hasClassSerializationStrategies
262 4
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classAnnotations->get(ExclusionCheck::class) !== null));
263 4
        $propertyExclusionCheck = $this->hasPropertySerializationStrategies
264 4
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
265 4
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
266 2
            ? new DefaultSerializationExclusionData($value, $context)
267 4
            : null;
268
269 4
        if ($classExclusionCheck && $exclusionData) {
270 1
            $this->excluder->applyClassSerializationExclusionData($exclusionData);
271
272 1
            if ($this->excluder->excludeClassBySerializationStrategy($this->classMetadata)) {
273 1
                return null;
274
            }
275
        }
276
277 3
        if ($propertyExclusionCheck && $exclusionData) {
278 1
            $this->excluder->applyPropertySerializationExclusionData($exclusionData);
279
        }
280
281 3
        $enableScalarAdapters = $context->enableScalarAdapters();
282 3
        $serializeNull = $context->serializeNull();
283 3
        $result = [];
284
285
        /** @var Property $property */
286 3
        foreach ($this->properties as $property) {
287 3
            $serializedName = $property->serializedName;
288
289 3
            if ($property->skipSerialize) {
290 3
                continue;
291
            }
292
293 3
            $checkProperty = $this->hasPropertySerializationStrategies
294 3
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->annotations->get(ExclusionCheck::class) !== null));
295 3
            if ($checkProperty && $this->excluder->excludePropertyBySerializationStrategy($property)) {
296 1
                continue;
297
            }
298
299 3
            if (!$enableScalarAdapters && $property->isScalar) {
300 3
                $propertyValue = $property->getterStrategy->get($value);
301 3
                if ($serializeNull || $propertyValue !== null) {
302 3
                    $result[$serializedName] = $propertyValue;
303
                }
304 3
                continue;
305
            }
306
307 3
            $adapter = $this->adapters[$serializedName] ?? null;
308 3
            if ($adapter === null) {
309 3
                $adapter = $this->getAdapter($property);
310
            }
311
312 3
            $propertyValue = $adapter->write($property->getterStrategy->get($value), $context);
313 3
            if ($serializeNull || $propertyValue !== null) {
314 3
                $result[$serializedName] = $propertyValue;
315
            }
316
        }
317
318 3
        if ($this->classVirtualProperty !== null) {
319 1
            $result = [$this->classVirtualProperty => $result];
320
        }
321
322 3
        return $result;
323
    }
324
325
    /**
326
     * Get the next type adapter
327
     *
328
     * @param Property $property
329
     * @return TypeAdapter
330
     */
331 9
    protected function getAdapter(Property $property): TypeAdapter
332
    {
333
        /** @var JsonAdapter $jsonAdapterAnnotation */
334 9
        $jsonAdapterAnnotation = $property->annotations->get(JsonAdapter::class);
335 9
        $adapter = null === $jsonAdapterAnnotation
336 9
            ? $this->typeAdapterProvider->getAdapter($property->type)
337 9
            : $this->typeAdapterProvider->getAdapterFromAnnotation($property->type, $jsonAdapterAnnotation);
338 9
        $this->adapters[$property->serializedName] = $adapter;
339
340 9
        return $adapter;
341
    }
342
}
343