Passed
Branch master (f07ed3)
by Nate
02:39
created

Excluder::excludeClassByDeserializationStrategy()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 3
rs 10
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\ExclusionData;
19
use Tebru\Gson\ExclusionStrategy;
20
use Tebru\Gson\PropertyMetadata;
21
22
/**
23
 * Class Excluder
24
 *
25
 * @author Nate Brunette <[email protected]>
26
 */
27
final class Excluder
28
{
29
    /**
30
     * Version, if set, will be used with [@see Since] and [@see Until] annotations
31
     *
32
     * @var string
33
     */
34
    private $version;
35
36
    /**
37
     * Which modifiers are excluded
38
     *
39
     * By default only static properties are excluded
40
     *
41
     * @var int
42
     */
43
    private $excludedModifiers = ReflectionProperty::IS_STATIC;
44
45
    /**
46
     * If this is true, properties will need to explicitly have an [@see Expose] annotation
47
     * to be serialized or deserialized
48
     *
49
     * @var bool
50
     */
51
    private $requireExpose = false;
52
53
    /**
54
     * Exclusions strategies during serialization
55
     *
56
     * @var ExclusionStrategy[]
57
     */
58
    private $serializationStrategies = [];
59
60
    /**
61
     * Exclusion strategies during deserialization
62
     *
63
     * @var ExclusionStrategy[]
64
     */
65
    private $deserializationStrategies = [];
66
67
68
    /**
69
     * Set the version to test against
70
     *
71
     * @param string $version
72
     * @return Excluder
73
     */
74 15
    public function setVersion(?string $version): Excluder
75
    {
76 15
        $this->version = $version;
77
78 15
        return $this;
79
    }
80
81
    /**
82
     * Set an integer representing the property modifiers that should be excluded
83
     *
84
     * @param int $modifiers
85
     * @return Excluder
86
     */
87 5
    public function setExcludedModifiers(int $modifiers): Excluder
88
    {
89 5
        $this->excludedModifiers = $modifiers;
90
91 5
        return $this;
92
    }
93
94
    /**
95
     * Require the [@see Expose] annotation to serialize properties
96
     *
97
     * @param bool $requireExpose
98
     * @return Excluder
99
     */
100 8
    public function setRequireExpose(bool $requireExpose): Excluder
101
    {
102 8
        $this->requireExpose = $requireExpose;
103
104 8
        return $this;
105
    }
106
107
    /**
108
     * Add an exclusion strategy and specify if it should be used during serialization or deserialization
109
     *
110
     * @param ExclusionStrategy $strategy
111
     * @param bool $serialization
112
     * @param bool $deserialization
113
     * @return void
114
     */
115 3
    public function addExclusionStrategy(ExclusionStrategy $strategy, bool $serialization, bool $deserialization): void
116
    {
117 3
        if ($serialization) {
118 2
            $this->serializationStrategies[] = $strategy;
119
        }
120
121 3
        if ($deserialization) {
122 2
            $this->deserializationStrategies[] = $strategy;
123
        }
124 3
    }
125
126
    /**
127
     * Compile time exclusion checking of classes
128
     *
129
     * @param ClassMetadata $classMetadata
130
     * @param bool $serialize
131
     * @return bool
132
     */
133 11
    public function excludeClass(ClassMetadata $classMetadata, bool $serialize): bool
134
    {
135 11
        return $this->excludeByAnnotation($classMetadata->getAnnotations(), $serialize);
136
    }
137
138
    /**
139
     * Runtime exclusion checking of classes by strategy during serialization
140
     *
141
     * Uses user-defined strategies
142
     *
143
     * @param ClassMetadata $classMetadata
144
     * @param ExclusionData $exclusionData
145
     * @return bool
146
     */
147 2
    public function excludeClassBySerializationStrategy(ClassMetadata $classMetadata, ExclusionData $exclusionData): bool
148
    {
149 2
        foreach ($this->serializationStrategies as $exclusionStrategy) {
150 1
            if ($exclusionStrategy->shouldSkipClass($classMetadata, $exclusionData)) {
151 1
                return true;
152
            }
153
        }
154
155 1
        return false;
156
    }
157
158
    /**
159
     * Runtime exclusion checking of classes by strategy during deserialization
160
     *
161
     * Uses user-defined strategies
162
     *
163
     * @param ClassMetadata $classMetadata
164
     * @param ExclusionData $exclusionData
165
     * @return bool
166
     */
167 2
    public function excludeClassByDeserializationStrategy(ClassMetadata $classMetadata, ExclusionData $exclusionData): bool
168
    {
169 2
        foreach ($this->deserializationStrategies as $exclusionStrategy) {
170 1
            if ($exclusionStrategy->shouldSkipClass($classMetadata, $exclusionData)) {
171 1
                return true;
172
            }
173
        }
174
175 1
        return false;
176
    }
177
178
    /**
179
     * Compile time exclusion checking of properties
180
     *
181
     * @param PropertyMetadata $propertyMetadata
182
     * @param bool $serialize
183
     * @return bool
184
     */
185 21
    public function excludeProperty(PropertyMetadata $propertyMetadata, bool $serialize): bool
186
    {
187
        // exclude the property if the property modifiers are found in the excluded modifiers
188 21
        if (0 !== ($this->excludedModifiers & $propertyMetadata->getModifiers())) {
189 3
            return true;
190
        }
191
192 18
        return $this->excludeByAnnotation($propertyMetadata->getAnnotations(), $serialize);
193
    }
194
195
    /**
196
     * Runtime exclusion checking of properties by strategy during serialization
197
     *
198
     * Uses user-defined strategies
199
     *
200
     * @param PropertyMetadata $property
201
     * @param ExclusionData $exclusionData
202
     * @return bool
203
     */
204 2
    public function excludePropertyBySerializationStrategy(PropertyMetadata $property, ExclusionData $exclusionData): bool
205
    {
206 2
        foreach ($this->serializationStrategies as $exclusionStrategy) {
207 1
            if ($exclusionStrategy->shouldSkipProperty($property, $exclusionData)) {
208 1
                return true;
209
            }
210
        }
211
212 1
        return false;
213
    }
214
215
    /**
216
     * Runtime exclusion checking of properties by strategy during deserialization
217
     *
218
     * Uses user-defined strategies
219
     *
220
     * @param PropertyMetadata $property
221
     * @param ExclusionData $exclusionData
222
     * @return bool
223
     */
224 2
    public function excludePropertyByDeserializationStrategy(PropertyMetadata $property, ExclusionData $exclusionData): bool
225
    {
226 2
        foreach ($this->deserializationStrategies as $exclusionStrategy) {
227 1
            if ($exclusionStrategy->shouldSkipProperty($property, $exclusionData)) {
228 1
                return true;
229
            }
230
        }
231
232 1
        return false;
233
    }
234
235
    /**
236
     * Returns true if serialization strategies exist
237
     *
238
     * @return bool
239
     */
240 5
    public function hasSerializationStrategies(): bool
241
    {
242 5
        return $this->serializationStrategies !== [];
243
    }
244
245
    /**
246
     * Returns true if deserialization strategies exist
247
     *
248
     * @return bool
249
     */
250 5
    public function hasDeserializationStrategies(): bool
251
    {
252 5
        return $this->deserializationStrategies !== [];
253
    }
254
255
    /**
256
     * Checks various annotations to see if the property should be excluded
257
     *
258
     * - [@see Since] / [@see Until]
259
     * - [@see Exclude]
260
     * - [@see Expose] (if requireExpose is set)
261
     *
262
     * @param AnnotationCollection $annotations
263
     * @param bool $serialize
264
     * @return bool
265
     */
266 27
    private function excludeByAnnotation(AnnotationCollection $annotations, bool $serialize): bool
267
    {
268 27
        if (!$this->validVersion($annotations)) {
269 6
            return true;
270
        }
271
272
        /** @var Exclude $exclude */
273 21
        $exclude = $annotations->get(Exclude::class);
274 21
        if (null !== $exclude && $exclude->shouldExclude($serialize)) {
275 4
            return true;
276
        }
277
278
        // if we need an expose annotation
279 20
        if ($this->requireExpose) {
280
            /** @var Expose $expose */
281 5
            $expose = $annotations->get(Expose::class);
282 5
            if (null === $expose || !$expose->shouldExpose($serialize)) {
283 4
                return true;
284
            }
285
        }
286
287 19
        return false;
288
    }
289
290
    /**
291
     * Returns true if the set version is valid for [@see Since] and [@see Until] annotations
292
     *
293
     * @param AnnotationCollection $annotations
294
     * @return bool
295
     */
296 27
    private function validVersion(AnnotationCollection $annotations): bool
297
    {
298 27
        return !$this->shouldSkipSince($annotations) && !$this->shouldSkipUntil($annotations);
299
    }
300
301
    /**
302
     * Returns true if we should skip based on the [@see Since] annotation
303
     *
304
     * @param AnnotationCollection $annotations
305
     * @return bool
306
     */
307 27
    private function shouldSkipSince(AnnotationCollection $annotations): bool
308
    {
309 27
        $sinceAnnotation = $annotations->get(Since::class);
310
311
        return
312 27
            null !== $sinceAnnotation
313 27
            && null !== $this->version
314 27
            && \version_compare($this->version, $sinceAnnotation->getValue(), '<');
315
    }
316
317
    /**
318
     * Returns true if we should skip based on the [@see Until] annotation
319
     *
320
     * @param AnnotationCollection $annotations
321
     * @return bool
322
     */
323 25
    private function shouldSkipUntil(AnnotationCollection $annotations): bool
324
    {
325 25
        $untilAnnotation = $annotations->get(Until::class);
326
327
        return
328 25
            null !== $untilAnnotation
329 25
            && null !== $this->version
330 25
            && \version_compare($this->version, $untilAnnotation->getValue(), '>=');
331
    }
332
}
333