Completed
Push — master ( dc53d4...3886f0 )
by Nate
02:49
created

Excluder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
namespace Tebru\Gson\Internal;
8
9
use ReflectionProperty;
10
use Tebru\Gson\Annotation\Exclude;
11
use Tebru\Gson\Annotation\Expose;
12
use Tebru\Gson\Annotation\Since;
13
use Tebru\Gson\Annotation\Until;
14
use Tebru\Gson\ClassMetadata;
15
use Tebru\Gson\ExclusionStrategy;
16
use Tebru\Gson\Internal\Data\AnnotationSet;
17
use Tebru\Gson\PropertyMetadata;
18
19
/**
20
 * Class Excluder
21
 *
22
 * @author Nate Brunette <[email protected]>
23
 */
24
final class Excluder
25
{
26
    /**
27
     * Version, if set, will be used with [@see Since] and [@see Until] annotations
28
     *
29
     * @var string
30
     */
31
    private $version;
32
33
    /**
34
     * Which modifiers are excluded
35
     *
36
     * By default only static properties are excluded
37
     *
38
     * @var int
39
     */
40
    private $excludedModifiers = ReflectionProperty::IS_STATIC;
41
42
    /**
43
     * If this is true, properties will need to explicitly have an [@see Expose] annotation
44
     * to be serialized or deserialized
45
     *
46
     * @var bool
47
     */
48
    private $requireExpose = false;
49
50
    /**
51
     * Exclusions strategies during serialization
52
     *
53
     * @var ExclusionStrategy[]
54
     */
55
    private $serializationStrategies = [];
56
57
    /**
58
     * Exclusion strategies during deserialization
59
     *
60
     * @var ExclusionStrategy[]
61
     */
62
    private $deserializationStrategies = [];
63
64
65
    /**
66
     * Set the version to test against
67
     *
68
     * @param string $version
69
     * @return Excluder
70
     */
71 12
    public function setVersion(?string $version): Excluder
72
    {
73 12
        $this->version = $version;
74
75 12
        return $this;
76
    }
77
78
    /**
79
     * Set an integer representing the property modifiers that should be excluded
80
     *
81
     * @param int $modifiers
82
     * @return Excluder
83
     */
84 2
    public function setExcludedModifiers(int $modifiers): Excluder
85
    {
86 2
        $this->excludedModifiers = $modifiers;
87
88 2
        return $this;
89
    }
90
91
    /**
92
     * Require the [@see Expose] annotation to serialize properties
93
     *
94
     * @param bool $requireExpose
95
     * @return Excluder
96
     */
97 5
    public function setRequireExpose(bool $requireExpose): Excluder
98
    {
99 5
        $this->requireExpose = $requireExpose;
100
101 5
        return $this;
102
    }
103
104
    /**
105
     * Add an exclusion strategy and specify if it should be used during serialization or deserialization
106
     *
107
     * @param ExclusionStrategy $strategy
108
     * @param bool $serialization
109
     * @param bool $deserialization
110
     */
111 3
    public function addExclusionStrategy(ExclusionStrategy $strategy, bool $serialization, bool $deserialization)
112
    {
113 3
        if ($serialization) {
114 2
            $this->serializationStrategies[] = $strategy;
115
        }
116
117 3
        if ($deserialization) {
118 2
            $this->deserializationStrategies[] = $strategy;
119
        }
120 3
    }
121
122
    /**
123
     * Returns true if we should exclude the class for a given serialization direction
124
     *
125
     * @param ClassMetadata $classMetadata
126
     * @param bool $serialize
127
     * @return bool
128
     * @throws \InvalidArgumentException If the type does not exist
129
     */
130 8
    public function excludeClass(ClassMetadata $classMetadata, bool $serialize): bool
131
    {
132 8
        return $this->excludeByAnnotation($classMetadata->getAnnotations(), $serialize, AnnotationSet::TYPE_CLASS);
133
    }
134
135
    /**
136
     * @param ClassMetadata $classMetadata
137
     * @param bool $serialize
138
     * @return bool
139
     */
140 2
    public function excludeClassByStrategy(ClassMetadata $classMetadata, bool $serialize): bool
141
    {
142 2
        $strategies = $serialize ? $this->serializationStrategies : $this->deserializationStrategies;
143 2
        foreach ($strategies as $exclusionStrategy) {
144 2
            if ($exclusionStrategy->shouldSkipClass($classMetadata)) {
145 2
                return true;
146
            }
147
        }
148
149 2
        return false;
150
    }
151
152
    /**
153
     * Returns true if we should exclude the class for a given serialization direction
154
     *
155
     * @param PropertyMetadata $propertyMetadata
156
     * @param bool $serialize
157
     * @return bool
158
     */
159 19
    public function excludeProperty(PropertyMetadata $propertyMetadata, bool $serialize): bool
160
    {
161
        // exclude the property if the property modifiers are found in the excluded modifiers
162 19
        if (0 !== ($this->excludedModifiers & $propertyMetadata->getModifiers())) {
163 3
            return true;
164
        }
165
166 16
        return $this->excludeByAnnotation($propertyMetadata->getAnnotations(), $serialize, AnnotationSet::TYPE_PROPERTY);
167
    }
168
169
    /**
170
     * Returns true if we should exclude the class for a given serialization direction
171
     *
172
     * Uses user-defined strategies
173
     *
174
     * @param PropertyMetadata $property
175
     * @param bool $serialize
176
     * @return bool
177
     */
178 2
    public function excludePropertyByStrategy(PropertyMetadata $property, bool $serialize): bool
179
    {
180 2
        $strategies = $serialize ? $this->serializationStrategies : $this->deserializationStrategies;
181 2
        foreach ($strategies as $exclusionStrategy) {
182 1
            if ($exclusionStrategy->shouldSkipProperty($property)) {
183 1
                return true;
184
            }
185
        }
186
187 1
        return false;
188
    }
189
190
    /**
191
     * Checks various annotations to see if the property should be excluded
192
     *
193
     * - [@see Since] / [@see Until]
194
     * - [@see Exclude]
195
     * - [@see Expose] (if requireExpose is set)
196
     *
197
     * @param AnnotationSet $annotations
198
     * @param bool $serialize
199
     * @param int $filter
200
     * @return bool
201
     */
202 24
    private function excludeByAnnotation(AnnotationSet $annotations, bool $serialize, int $filter): bool
203
    {
204 24
        if (!$this->validVersion($annotations, $filter)) {
205 6
            return true;
206
        }
207
208
        /** @var Exclude $exclude */
209 18
        $exclude = $annotations->getAnnotation(Exclude::class, $filter);
210 18
        if (null !== $exclude && $exclude->shouldExclude($serialize)) {
211 4
            return true;
212
        }
213
214
        // if we need an expose annotation
215 17
        if ($this->requireExpose) {
216
            /** @var Expose $expose */
217 5
            $expose = $annotations->getAnnotation(Expose::class, $filter);
218 5
            if (null === $expose || !$expose->shouldExpose($serialize)) {
219 4
                return true;
220
            }
221
        }
222
223 16
        return false;
224
    }
225
226
    /**
227
     * Returns true if the set version is valid for [@see Since] and [@see Until] annotations
228
     *
229
     * @param AnnotationSet $annotations
230
     * @param int $filter
231
     * @return bool
232
     */
233 24
    private function validVersion(AnnotationSet $annotations, int $filter): bool
234
    {
235 24
        return !$this->shouldSkipSince($annotations, $filter) && !$this->shouldSkipUntil($annotations, $filter);
236
    }
237
238
    /**
239
     * Returns true if we should skip based on the [@see Since] annotation
240
     *
241
     * @param AnnotationSet $annotations
242
     * @param int $filter
243
     * @return bool
244
     */
245 24
    private function shouldSkipSince(AnnotationSet $annotations, int $filter): bool
246
    {
247
        /** @var Since $sinceAnnotation */
248 24
        $sinceAnnotation = $annotations->getAnnotation(Since::class, $filter);
249
250
        return
251 24
            null !== $sinceAnnotation
252 24
            && null !== $this->version
253 24
            && version_compare($this->version, $sinceAnnotation->getVersion(), '<');
254
    }
255
256
    /**
257
     * Returns true if we should skip based on the [@see Until] annotation
258
     *
259
     * @param AnnotationSet $annotations
260
     * @param int $filter
261
     * @return bool
262
     */
263 22
    private function shouldSkipUntil(AnnotationSet $annotations, int $filter): bool
264
    {
265
        /** @var Until $sinceAnnotation */
266 22
        $untilAnnotation = $annotations->getAnnotation(Until::class, $filter);
267
268
        return
269 22
            null !== $untilAnnotation
270 22
            && null !== $this->version
271 22
            && version_compare($this->version, $untilAnnotation->getVersion(), '>=');
272
    }
273
}
274