Completed
Push — master ( ac0b2b...25e509 )
by Nate
02:57
created

Excluder::excludeClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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