Passed
Pull Request — master (#26)
by Nate
07:26
created

Excluder::excludeClassByDeserializationStrategy()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
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
     * Returns true if we should exclude the class for a given serialization direction
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
     * @param ClassMetadata $classMetadata
140
     * @param ExclusionData $exclusionData
141
     * @return bool
142
     */
143 2
    public function excludeClassBySerializationStrategy(ClassMetadata $classMetadata, ExclusionData $exclusionData): bool
144
    {
145 2
        foreach ($this->serializationStrategies as $exclusionStrategy) {
146 1
            if ($exclusionStrategy->shouldSkipClass($classMetadata, $exclusionData)) {
147 1
                return true;
148
            }
149
        }
150
151 1
        return false;
152
    }
153
154 2
    public function excludeClassByDeserializationStrategy(ClassMetadata $classMetadata, ExclusionData $exclusionData): bool
155
    {
156 2
        foreach ($this->deserializationStrategies as $exclusionStrategy) {
157 1
            if ($exclusionStrategy->shouldSkipClass($classMetadata, $exclusionData)) {
158 1
                return true;
159
            }
160
        }
161
162 1
        return false;
163
    }
164
165
    /**
166
     * Returns true if we should exclude the class for a given serialization direction
167
     *
168
     * @param PropertyMetadata $propertyMetadata
169
     * @param bool $serialize
170
     * @return bool
171
     */
172 21
    public function excludeProperty(PropertyMetadata $propertyMetadata, bool $serialize): bool
173
    {
174
        // exclude the property if the property modifiers are found in the excluded modifiers
175 21
        if (0 !== ($this->excludedModifiers & $propertyMetadata->getModifiers())) {
176 3
            return true;
177
        }
178
179 18
        return $this->excludeByAnnotation($propertyMetadata->getAnnotations(), $serialize);
180
    }
181
182
    /**
183
     * Returns true if we should exclude the class for a given serialization direction
184
     *
185
     * Uses user-defined strategies
186
     *
187
     * @param PropertyMetadata $property
188
     * @param ExclusionData $exclusionData
189
     * @return bool
190
     */
191 2
    public function excludePropertyBySerializationStrategy(PropertyMetadata $property, ExclusionData $exclusionData): bool
192
    {
193 2
        foreach ($this->serializationStrategies as $exclusionStrategy) {
194 1
            if ($exclusionStrategy->shouldSkipProperty($property, $exclusionData)) {
195 1
                return true;
196
            }
197
        }
198
199 1
        return false;
200
    }
201
202
    /**
203
     * Returns true if we should exclude the class for a given serialization direction
204
     *
205
     * Uses user-defined strategies
206
     *
207
     * @param PropertyMetadata $property
208
     * @param ExclusionData $exclusionData
209
     * @return bool
210
     */
211 2
    public function excludePropertyByDeserializationStrategy(PropertyMetadata $property, ExclusionData $exclusionData): bool
212
    {
213 2
        foreach ($this->deserializationStrategies as $exclusionStrategy) {
214 1
            if ($exclusionStrategy->shouldSkipProperty($property, $exclusionData)) {
215 1
                return true;
216
            }
217
        }
218
219 1
        return false;
220
    }
221
222
    /**
223
     * Returns true if serialization strategies exist
224
     *
225
     * @return bool
226
     */
227 5
    public function hasSerializationStrategies(): bool
228
    {
229 5
        return $this->serializationStrategies !== [];
230
    }
231
232
    /**
233
     * Returns true if deserialization strategies exist
234
     *
235
     * @return bool
236
     */
237 5
    public function hasDeserializationStrategies(): bool
238
    {
239 5
        return $this->deserializationStrategies !== [];
240
    }
241
242
    /**
243
     * Checks various annotations to see if the property should be excluded
244
     *
245
     * - [@see Since] / [@see Until]
246
     * - [@see Exclude]
247
     * - [@see Expose] (if requireExpose is set)
248
     *
249
     * @param AnnotationCollection $annotations
250
     * @param bool $serialize
251
     * @return bool
252
     */
253 27
    private function excludeByAnnotation(AnnotationCollection $annotations, bool $serialize): bool
254
    {
255 27
        if (!$this->validVersion($annotations)) {
256 6
            return true;
257
        }
258
259
        /** @var Exclude $exclude */
260 21
        $exclude = $annotations->get(Exclude::class);
261 21
        if (null !== $exclude && $exclude->shouldExclude($serialize)) {
262 4
            return true;
263
        }
264
265
        // if we need an expose annotation
266 20
        if ($this->requireExpose) {
267
            /** @var Expose $expose */
268 5
            $expose = $annotations->get(Expose::class);
269 5
            if (null === $expose || !$expose->shouldExpose($serialize)) {
270 4
                return true;
271
            }
272
        }
273
274 19
        return false;
275
    }
276
277
    /**
278
     * Returns true if the set version is valid for [@see Since] and [@see Until] annotations
279
     *
280
     * @param AnnotationCollection $annotations
281
     * @return bool
282
     */
283 27
    private function validVersion(AnnotationCollection $annotations): bool
284
    {
285 27
        return !$this->shouldSkipSince($annotations) && !$this->shouldSkipUntil($annotations);
286
    }
287
288
    /**
289
     * Returns true if we should skip based on the [@see Since] annotation
290
     *
291
     * @param AnnotationCollection $annotations
292
     * @return bool
293
     */
294 27
    private function shouldSkipSince(AnnotationCollection $annotations): bool
295
    {
296 27
        $sinceAnnotation = $annotations->get(Since::class);
297
298
        return
299 27
            null !== $sinceAnnotation
300 27
            && null !== $this->version
301 27
            && \version_compare($this->version, $sinceAnnotation->getValue(), '<');
302
    }
303
304
    /**
305
     * Returns true if we should skip based on the [@see Until] annotation
306
     *
307
     * @param AnnotationCollection $annotations
308
     * @return bool
309
     */
310 25
    private function shouldSkipUntil(AnnotationCollection $annotations): bool
311
    {
312 25
        $untilAnnotation = $annotations->get(Until::class);
313
314
        return
315 25
            null !== $untilAnnotation
316 25
            && null !== $this->version
317 25
            && \version_compare($this->version, $untilAnnotation->getValue(), '>=');
318
    }
319
}
320