Completed
Push — v0.7 ( 12ee9b...e9367e )
by Nate
02:13
created

ReflectionTypeAdapter::getAdapter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 10
ccs 1
cts 1
cp 1
crap 2
rs 10
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 17
     * @param Excluder $excluder
132
     * @param TypeAdapterProvider $typeAdapterProvider
133
     * @param null|string $classVirtualProperty
134
     * @param bool $requireExclusionCheck
135
     * @param bool $hasPropertyExclusionCheck
136
     */
137
    public function __construct(
138
        ObjectConstructor $objectConstructor,
139
        DefaultClassMetadata $classMetadata,
140 17
        Excluder $excluder,
141 17
        TypeAdapterProvider $typeAdapterProvider,
142 17
        ?string $classVirtualProperty,
143 17
        bool $requireExclusionCheck,
144 17
        bool $hasPropertyExclusionCheck
145 17
    ) {
146 17
        $this->objectConstructor = $objectConstructor;
147
        $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 17
154 17
        $this->classAnnotations = $classMetadata->annotations;
155 17
        $this->properties = $classMetadata->properties;
156
        $this->skipSerialize = $classMetadata->skipSerialize;
157
        $this->skipDeserialize = $classMetadata->skipDeserialize;
158
        $this->hasClassSerializationStrategies = $this->excluder->hasClassSerializationStrategies();
159
        $this->hasPropertySerializationStrategies = $this->excluder->hasPropertySerializationStrategies();
160
        $this->hasClassDeserializationStrategies = $this->excluder->hasClassDeserializationStrategies();
161
        $this->hasPropertyDeserializationStrategies = $this->excluder->hasPropertyDeserializationStrategies();
162 11
    }
163
    /**
164 11
     * Read the next value, convert it to its type and return it
165 1
     *
166 1
     * @param JsonReadable $reader
167
     * @return object
168
     */
169 10
    public function read(JsonReadable $reader)
170 1
    {
171 1
        if ($this->skipDeserialize) {
172
            $reader->skipValue();
173
            return null;
174 9
        }
175 9
176 9
        if ($reader->peek() === JsonToken::NULL) {
177 9
            $reader->nextNull();
178 9
            return null;
179 9
        }
180 2
181 9
        $object = $this->objectConstructor->construct();
182
        $classExclusionCheck = $this->hasClassDeserializationStrategies
183 9
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classAnnotations->get(ExclusionCheck::class) !== null));
184 1
        $propertyExclusionCheck = $this->hasPropertyDeserializationStrategies
185
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
186 1
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
187 1
            ? new DefaultDeserializationExclusionData(clone $object, $reader)
188 1
            : null;
189
190
        if ($classExclusionCheck && $exclusionData) {
191
            $this->excluder->applyClassDeserializationExclusionData($exclusionData);
192 8
193 1
            if ($this->excluder->excludeClassByDeserializationStrategy($this->classMetadata)) {
194
                $reader->skipValue();
195
                return null;
196 8
            }
197
        }
198 8
199 1
        if ($propertyExclusionCheck && $exclusionData) {
200 1
            $this->excluder->applyPropertyDeserializationExclusionData($exclusionData);
201
        }
202
203 8
        $reader->beginObject();
204
205 8
        if ($this->classVirtualProperty !== null) {
206 8
            $reader->nextName();
207 8
            $reader->beginObject();
208
        }
209 8
210 8
        $usesExisting = $reader->getContext()->usesExistingObject();
211 8
212
        while ($reader->hasNext()) {
213
            $name = $reader->nextName();
214 6
            $property = $this->propertyCache[$name] ?? ($this->propertyCache[$name] = ($this->properties->elements[$name] ?? null));
215 6
216 6
            if ($property === null || $property->skipDeserialize) {
217 1
                $reader->skipValue();
218 1
                continue;
219
            }
220
221 6
            $checkProperty = $this->hasPropertyDeserializationStrategies
222
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->annotations->get(ExclusionCheck::class) !== null));
223 6
            if ($checkProperty && $this->excluder->excludePropertyByDeserializationStrategy($property)) {
224 6
                $reader->skipValue();
225
                continue;
226 6
            }
227 6
228 6
            $adapter = $this->adapters[$property->realName] ?? null;
229 4
            if ($adapter === null) {
230 4
                $adapter = $this->getAdapter($property);
231 6
            }
232
233 6
            if ($adapter instanceof ObjectConstructorAware && $usesExisting) {
234
                try {
235
                    $nestedObject = $property->getterStrategy->get($object);
236 6
                } /** @noinspection BadExceptionsProcessingInspection */ catch (TypeError $error) {
237
                    // this may occur when attempting to get a nested object that doesn't exist and
238 2
                    // the method return is not nullable. The type error only occurs because we are
239 1
                    // may be calling the getter before data exists.
240
                    $nestedObject = null;
241
                }
242
243 1
                if ($nestedObject !== null) {
244
                    $adapter->setObjectConstructor(new CreateFromInstance($nestedObject));
245
                }
246 2
            }
247 1
248
            $property->setterStrategy->set($object, $adapter->read($reader));
249
        }
250
        $reader->endObject();
251 6
252
        if ($this->classVirtualProperty !== null) {
253 8
            $reader->endObject();
254
        }
255 8
256 1
        return $object;
257
    }
258
259 8
    /**
260
     * Write the value to the writer for the type
261
     *
262
     * @param JsonWritable $writer
263
     * @param object $value
264
     * @return void
265
     */
266
    public function write(JsonWritable $writer, $value): void
267
    {
268
        if ($this->skipSerialize || $value === null) {
269 6
            $writer->writeNull();
270
            return;
271 6
        }
272 2
273 2
        $classExclusionCheck = $this->hasClassSerializationStrategies
274
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->classAnnotations->get(ExclusionCheck::class) !== null));
275
        $propertyExclusionCheck = $this->hasPropertySerializationStrategies
276 4
            && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $this->hasPropertyExclusionCheck));
277 4
        $exclusionData = $classExclusionCheck || $propertyExclusionCheck
278 4
            ? new DefaultSerializationExclusionData($value, $writer)
279 4
            : null;
280 4
281 2
        if ($classExclusionCheck && $exclusionData) {
282 4
            $this->excluder->applyClassSerializationExclusionData($exclusionData);
283
284 4
            if ($this->excluder->excludeClassBySerializationStrategy($this->classMetadata)) {
285 1
                $writer->writeNull();
286
                return;
287 1
            }
288 1
        }
289 1
290
        if ($propertyExclusionCheck && $exclusionData) {
291
            $this->excluder->applyPropertySerializationExclusionData($exclusionData);
292
        }
293 3
294 1
        $writer->beginObject();
295
296
        if ($this->classVirtualProperty !== null) {
297 3
            $writer->name($this->classVirtualProperty);
298
            $writer->beginObject();
299 3
        }
300 1
301 1
        /** @var Property $property */
302
        foreach ($this->properties as $property) {
303
            $writer->name($property->getSerializedName());
304
            if ($property->skipSerialize) {
305 3
                $writer->writeNull();
306 3
                continue;
307 3
            }
308 3
309 3
            $checkProperty = $this->hasPropertySerializationStrategies
310
                && (!$this->requireExclusionCheck || ($this->requireExclusionCheck && $property->annotations->get(ExclusionCheck::class) !== null));
311
            if ($checkProperty && $this->excluder->excludePropertyBySerializationStrategy($property)) {
312 3
                $writer->writeNull();
313 3
                continue;
314 3
            }
315 1
316 1
            $adapter = $this->adapters[$property->realName] ?? null;
317
            if ($adapter === null) {
318
                $adapter = $this->getAdapter($property);
319 3
            }
320 3
321 3
            $adapter->write($writer, $property->getterStrategy->get($value));
322
        }
323 3
324 3
        $writer->endObject();
325 3
326 3
        if ($this->classVirtualProperty !== null) {
327 3
            $writer->endObject();
328
        }
329 3
    }
330
331
    /**
332 3
     * Get the next type adapter
333
     *
334 3
     * @param Property $property
335 1
     * @return TypeAdapter
336
     */
337 3
    private function getAdapter(Property $property): TypeAdapter
338
    {
339
        /** @var JsonAdapter $jsonAdapterAnnotation */
340
        $jsonAdapterAnnotation = $property->annotations->get(JsonAdapter::class);
341
        $adapter = null === $jsonAdapterAnnotation
342
            ? $this->typeAdapterProvider->getAdapter($property->type)
343
            : $this->typeAdapterProvider->getAdapterFromAnnotation($property->type, $jsonAdapterAnnotation);
344
        $this->adapters[$property->realName] = $adapter;
345
346
        return $adapter;
347
    }
348
}
349