Passed
Push — master ( 21a5dc...1ca6d2 )
by Nate
40s
created

Excluder::excludePropertyDeserialize()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 4
nop 1
dl 0
loc 14
ccs 7
cts 7
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
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;
10
11
use ReflectionProperty;
12
use Tebru\AnnotationReader\AnnotationCollection;
13
use Tebru\Gson\Annotation\Exclude;
14
use Tebru\Gson\Annotation\Expose;
15
use Tebru\Gson\Annotation\Since;
16
use Tebru\Gson\Annotation\Until;
17
use Tebru\Gson\ClassMetadata;
18
use Tebru\Gson\Exclusion\ClassDeserializationExclusionStrategy;
19
use Tebru\Gson\Exclusion\ClassSerializationExclusionStrategy;
20
use Tebru\Gson\Exclusion\DeserializationExclusionData;
21
use Tebru\Gson\Exclusion\DeserializationExclusionDataAware;
22
use Tebru\Gson\Exclusion\ExclusionStrategy;
23
use Tebru\Gson\Exclusion\PropertyDeserializationExclusionStrategy;
24
use Tebru\Gson\Exclusion\PropertySerializationExclusionStrategy;
25
use Tebru\Gson\Exclusion\SerializationExclusionData;
26
use Tebru\Gson\Exclusion\SerializationExclusionDataAware;
27
use Tebru\Gson\PropertyMetadata;
28
29
/**
30
 * Class Excluder
31
 *
32
 * @author Nate Brunette <[email protected]>
33
 */
34
final class Excluder
35
{
36
    /**
37
     * Version, if set, will be used with [@see Since] and [@see Until] annotations
38
     *
39
     * @var string
40
     */
41
    private $version;
42
43
    /**
44
     * Which modifiers are excluded
45
     *
46
     * By default only static properties are excluded
47
     *
48
     * @var int
49
     */
50
    private $excludedModifiers = ReflectionProperty::IS_STATIC;
51
52
    /**
53
     * If this is true, properties will need to explicitly have an [@see Expose] annotation
54
     * to be serialized or deserialized
55
     *
56
     * @var bool
57
     */
58
    private $requireExpose = false;
59
60
    /**
61
     * Class Exclusion strategies during serialization
62
     *
63
     * @var ClassSerializationExclusionStrategy[]
64
     */
65
    private $classSerializationStrategies = [];
66
67
    /**
68
     * Property Exclusion strategies during serialization
69
     *
70
     * @var PropertySerializationExclusionStrategy[]
71
     */
72
    private $propertySerializationStrategies = [];
73
74
    /**
75
     * Class Exclusion strategies during deserialization
76
     *
77
     * @var ClassDeserializationExclusionStrategy[]
78
     */
79
    private $classDeserializationStrategies = [];
80
81
    /**
82
     * Property Exclusion strategies during deserialization
83
     *
84
     * @var PropertyDeserializationExclusionStrategy[]
85
     */
86
    private $propertyDeserializationStrategies = [];
87
88
    /**
89
     * Exclusion strategies that can be cached
90
     *
91
     * @var ExclusionStrategy[]
92
     */
93
    private $cachedStrategies = [];
94
95
96
    /**
97
     * Set the version to test against
98
     *
99
     * @param string $version
100
     * @return Excluder
101
     */
102 15
    public function setVersion(?string $version): Excluder
103
    {
104 15
        $this->version = $version;
105
106 15
        return $this;
107
    }
108
109
    /**
110
     * Set an integer representing the property modifiers that should be excluded
111
     *
112
     * @param int $modifiers
113
     * @return Excluder
114
     */
115 5
    public function setExcludedModifiers(int $modifiers): Excluder
116
    {
117 5
        $this->excludedModifiers = $modifiers;
118
119 5
        return $this;
120
    }
121
122
    /**
123
     * Require the [@see Expose] annotation to serialize properties
124
     *
125
     * @param bool $requireExpose
126
     * @return Excluder
127
     */
128 8
    public function setRequireExpose(bool $requireExpose): Excluder
129
    {
130 8
        $this->requireExpose = $requireExpose;
131
132 8
        return $this;
133
    }
134
135
    /**
136
     * Add an exclusion strategy
137
     *
138
     * @param ExclusionStrategy $strategy
139
     * @return void
140
     */
141 5
    public function addExclusionStrategy(ExclusionStrategy $strategy): void
142
    {
143 5
        if ($strategy instanceof ClassSerializationExclusionStrategy) {
144 3
            $this->classSerializationStrategies[] = $strategy;
145
        }
146
147 5
        if ($strategy instanceof PropertySerializationExclusionStrategy) {
148 3
            $this->propertySerializationStrategies[] = $strategy;
149
        }
150
151 5
        if ($strategy instanceof ClassDeserializationExclusionStrategy) {
152 3
            $this->classDeserializationStrategies[] = $strategy;
153
        }
154
155 5
        if ($strategy instanceof PropertyDeserializationExclusionStrategy) {
156 3
            $this->propertyDeserializationStrategies[] = $strategy;
157
        }
158 5
    }
159
160
    /**
161
     * Add an exclusion strategy that can be cached
162
     *
163
     * @param ExclusionStrategy $strategy
164
     */
165 1
    public function addCachedExclusionStrategy(ExclusionStrategy $strategy): void
166
    {
167 1
        $this->cachedStrategies[] = $strategy;
168 1
    }
169
170
    /**
171
     * Compile time exclusion checking of classes to determine if we should exclude during serialization
172
     *
173
     * @param ClassMetadata $classMetadata
174
     * @return bool
175
     */
176 12
    public function excludeClassSerialize(ClassMetadata $classMetadata): bool
177
    {
178 12
        foreach ($this->cachedStrategies as $strategy) {
179 1
            if ($strategy instanceof ClassSerializationExclusionStrategy && $strategy->skipSerializingClass($classMetadata)) {
180 1
                return true;
181
            }
182
        }
183
184 11
        return $this->excludeByAnnotation($classMetadata->getAnnotations(), true);
185
    }
186
187
    /**
188
     * Compile time exclusion checking of classes to determine if we should exclude during deserialization
189
     *
190
     * @param ClassMetadata $classMetadata
191
     * @return bool
192
     */
193 12
    public function excludeClassDeserialize(ClassMetadata $classMetadata): bool
194
    {
195 12
        foreach ($this->cachedStrategies as $strategy) {
196 1
            if ($strategy instanceof ClassDeserializationExclusionStrategy && $strategy->skipDeserializingClass($classMetadata)) {
197 1
                return true;
198
            }
199
        }
200
201 11
        return $this->excludeByAnnotation($classMetadata->getAnnotations(), false);
202
    }
203
204
    /**
205
     * Add [@see SerializationExclusionData] to class exclusion strategies
206
     *
207
     * @param SerializationExclusionData $exclusionData
208
     */
209 1
    public function applyClassSerializationExclusionData(SerializationExclusionData $exclusionData): void
210
    {
211 1
        foreach ($this->classSerializationStrategies as $exclusionStrategy) {
212 1
            if ($exclusionStrategy instanceof SerializationExclusionDataAware) {
213 1
                $exclusionStrategy->setSerializationExclusionData($exclusionData);
214
            }
215
        }
216 1
    }
217
218
    /**
219
     * Add [@see SerializationExclusionData] to property exclusion strategies
220
     *
221
     * @param SerializationExclusionData $exclusionData
222
     */
223 1
    public function applyPropertySerializationExclusionData(SerializationExclusionData $exclusionData): void
224
    {
225 1
        foreach ($this->propertySerializationStrategies as $exclusionStrategy) {
226 1
            if ($exclusionStrategy instanceof SerializationExclusionDataAware) {
227 1
                $exclusionStrategy->setSerializationExclusionData($exclusionData);
228
            }
229
        }
230 1
    }
231
232
    /**
233
     * Add [@see SerializationExclusionData] to class deserialization strategies
234
     *
235
     * @param DeserializationExclusionData $exclusionData
236
     */
237 1
    public function applyClassDeserializationExclusionData(DeserializationExclusionData $exclusionData): void
238
    {
239 1
        foreach ($this->classDeserializationStrategies as $exclusionStrategy) {
240 1
            if ($exclusionStrategy instanceof DeserializationExclusionDataAware) {
241 1
                $exclusionStrategy->setDeserializationExclusionData($exclusionData);
242
            }
243
        }
244 1
    }
245
246
    /**
247
     * Add [@see SerializationExclusionData] to property deserialization strategies
248
     *
249
     * @param DeserializationExclusionData $exclusionData
250
     */
251 1
    public function applyPropertyDeserializationExclusionData(DeserializationExclusionData $exclusionData): void
252
    {
253 1
        foreach ($this->propertyDeserializationStrategies as $exclusionStrategy) {
254 1
            if ($exclusionStrategy instanceof DeserializationExclusionDataAware) {
255 1
                $exclusionStrategy->setDeserializationExclusionData($exclusionData);
256
            }
257
        }
258 1
    }
259
260
    /**
261
     * Runtime exclusion checking of classes by strategy during serialization
262
     *
263
     * Uses user-defined strategies
264
     *
265
     * @param ClassMetadata $classMetadata
266
     * @return bool
267
     */
268 2
    public function excludeClassBySerializationStrategy(ClassMetadata $classMetadata): bool
269
    {
270 2
        foreach ($this->classSerializationStrategies as $exclusionStrategy) {
271 1
            if ($exclusionStrategy->skipSerializingClass($classMetadata)) {
272 1
                return true;
273
            }
274
        }
275
276 1
        return false;
277
    }
278
279
    /**
280
     * Runtime exclusion checking of classes by strategy during deserialization
281
     *
282
     * Uses user-defined strategies
283
     *
284
     * @param ClassMetadata $classMetadata
285
     * @return bool
286
     */
287 2
    public function excludeClassByDeserializationStrategy(ClassMetadata $classMetadata): bool
288
    {
289 2
        foreach ($this->classDeserializationStrategies as $exclusionStrategy) {
290 1
            if ($exclusionStrategy->skipDeserializingClass($classMetadata)) {
291 1
                return true;
292
            }
293
        }
294
295 1
        return false;
296
    }
297
298
    /**
299
     * Compile time exclusion checking of properties
300
     *
301
     * @param PropertyMetadata $propertyMetadata
302
     * @return bool
303
     */
304 22
    public function excludePropertySerialize(PropertyMetadata $propertyMetadata): bool
305
    {
306
        // exclude the property if the property modifiers are found in the excluded modifiers
307 22
        if (0 !== ($this->excludedModifiers & $propertyMetadata->getModifiers())) {
308 3
            return true;
309
        }
310
311 19
        foreach ($this->cachedStrategies as $strategy) {
312 1
            if ($strategy instanceof PropertySerializationExclusionStrategy && $strategy->skipSerializingProperty($propertyMetadata)) {
313 1
                return true;
314
            }
315
        }
316
317 18
        return $this->excludeByAnnotation($propertyMetadata->getAnnotations(), true);
318
    }
319
320
    /**
321
     * Compile time exclusion checking of properties
322
     *
323
     * @param PropertyMetadata $propertyMetadata
324
     * @return bool
325
     */
326 22
    public function excludePropertyDeserialize(PropertyMetadata $propertyMetadata): bool
327
    {
328
        // exclude the property if the property modifiers are found in the excluded modifiers
329 22
        if (0 !== ($this->excludedModifiers & $propertyMetadata->getModifiers())) {
330 3
            return true;
331
        }
332
333 19
        foreach ($this->cachedStrategies as $strategy) {
334 1
            if ($strategy instanceof PropertyDeserializationExclusionStrategy && $strategy->skipDeserializingProperty($propertyMetadata)) {
335 1
                return true;
336
            }
337
        }
338
339 18
        return $this->excludeByAnnotation($propertyMetadata->getAnnotations(), false);
340
    }
341
342
    /**
343
     * Runtime exclusion checking of properties by strategy during serialization
344
     *
345
     * Uses user-defined strategies
346
     *
347
     * @param PropertyMetadata $property
348
     * @return bool
349
     */
350 2
    public function excludePropertyBySerializationStrategy(PropertyMetadata $property): bool
351
    {
352 2
        foreach ($this->propertySerializationStrategies as $exclusionStrategy) {
353 1
            if ($exclusionStrategy->skipSerializingProperty($property)) {
354 1
                return true;
355
            }
356
        }
357
358 1
        return false;
359
    }
360
361
    /**
362
     * Runtime exclusion checking of properties by strategy during deserialization
363
     *
364
     * Uses user-defined strategies
365
     *
366
     * @param PropertyMetadata $property
367
     * @return bool
368
     */
369 2
    public function excludePropertyByDeserializationStrategy(PropertyMetadata $property): bool
370
    {
371 2
        foreach ($this->propertyDeserializationStrategies as $exclusionStrategy) {
372 1
            if ($exclusionStrategy->skipDeserializingProperty($property)) {
373 1
                return true;
374
            }
375
        }
376
377 1
        return false;
378
    }
379
380
    /**
381
     * Returns true if class serialization strategies exist
382
     *
383
     * @return bool
384
     */
385 5
    public function hasClassSerializationStrategies(): bool
386
    {
387 5
        return $this->classSerializationStrategies !== [];
388
    }
389
390
    /**
391
     * Returns true if property serialization strategies exist
392
     *
393
     * @return bool
394
     */
395 5
    public function hasPropertySerializationStrategies(): bool
396
    {
397 5
        return $this->propertySerializationStrategies !== [];
398
    }
399
400
    /**
401
     * Returns true if class deserialization strategies exist
402
     *
403
     * @return bool
404
     */
405 5
    public function hasClassDeserializationStrategies(): bool
406
    {
407 5
        return $this->classDeserializationStrategies !== [];
408
    }
409
410
    /**
411
     * Returns true if property deserialization strategies exist
412
     *
413
     * @return bool
414
     */
415 5
    public function hasPropertyDeserializationStrategies(): bool
416
    {
417 5
        return $this->propertyDeserializationStrategies !== [];
418
    }
419
420
    /**
421
     * Checks various annotations to see if the property should be excluded
422
     *
423
     * - [@see Since] / [@see Until]
424
     * - [@see Exclude]
425
     * - [@see Expose] (if requireExpose is set)
426
     *
427
     * @param AnnotationCollection $annotations
428
     * @param bool $serialize
429
     * @return bool
430
     */
431 27
    private function excludeByAnnotation(AnnotationCollection $annotations, bool $serialize): bool
432
    {
433 27
        if (!$this->validVersion($annotations)) {
434 6
            return true;
435
        }
436
437
        /** @var Exclude $exclude */
438 21
        $exclude = $annotations->get(Exclude::class);
439 21
        if (null !== $exclude && $exclude->shouldExclude($serialize)) {
440 4
            return true;
441
        }
442
443
        // if we need an expose annotation
444 20
        if ($this->requireExpose) {
445
            /** @var Expose $expose */
446 5
            $expose = $annotations->get(Expose::class);
447 5
            if (null === $expose || !$expose->shouldExpose($serialize)) {
448 4
                return true;
449
            }
450
        }
451
452 19
        return false;
453
    }
454
455
    /**
456
     * Returns true if the set version is valid for [@see Since] and [@see Until] annotations
457
     *
458
     * @param AnnotationCollection $annotations
459
     * @return bool
460
     */
461 27
    private function validVersion(AnnotationCollection $annotations): bool
462
    {
463 27
        return !$this->shouldSkipSince($annotations) && !$this->shouldSkipUntil($annotations);
464
    }
465
466
    /**
467
     * Returns true if we should skip based on the [@see Since] annotation
468
     *
469
     * @param AnnotationCollection $annotations
470
     * @return bool
471
     */
472 27
    private function shouldSkipSince(AnnotationCollection $annotations): bool
473
    {
474 27
        $sinceAnnotation = $annotations->get(Since::class);
475
476
        return
477 27
            null !== $sinceAnnotation
478 27
            && null !== $this->version
479 27
            && \version_compare($this->version, $sinceAnnotation->getValue(), '<');
480
    }
481
482
    /**
483
     * Returns true if we should skip based on the [@see Until] annotation
484
     *
485
     * @param AnnotationCollection $annotations
486
     * @return bool
487
     */
488 25
    private function shouldSkipUntil(AnnotationCollection $annotations): bool
489
    {
490 25
        $untilAnnotation = $annotations->get(Until::class);
491
492
        return
493 25
            null !== $untilAnnotation
494 25
            && null !== $this->version
495 25
            && \version_compare($this->version, $untilAnnotation->getValue(), '>=');
496
    }
497
}
498