Passed
Push — master ( 1173f0...070f3f )
by Nate
13:41
created

GsonBuilder::addExclusionStrategy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 3
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;
8
9
use BadMethodCallException;
10
use DateTime;
11
use Doctrine\Common\Annotations\AnnotationReader;
12
use Doctrine\Common\Annotations\CachedReader;
13
use Doctrine\Common\Cache\ArrayCache;
14
use Doctrine\Common\Cache\ChainCache;
15
use Doctrine\Common\Cache\FilesystemCache;
16
use LogicException;
17
use ReflectionProperty;
18
use Tebru\Gson\Internal\AccessorMethodProvider;
19
use Tebru\Gson\Internal\AccessorStrategyFactory;
20
use Tebru\Gson\Internal\ConstructorConstructor;
21
use Tebru\Gson\Internal\Data\AnnotationCollectionFactory;
22
use Tebru\Gson\Internal\Data\PropertyCollectionFactory;
23
use Tebru\Gson\Internal\Data\ReflectionPropertySetFactory;
24
use Tebru\Gson\Internal\Excluder;
25
use Tebru\Gson\Internal\MetadataFactory;
26
use Tebru\Gson\Internal\Naming\PropertyNamer;
27
use Tebru\Gson\Internal\Naming\SnakePropertyNamingStrategy;
28
use Tebru\Gson\Internal\Naming\UpperCaseMethodNamingStrategy;
29
use Tebru\Gson\Internal\DefaultPhpType;
30
use Tebru\Gson\Internal\PhpTypeFactory;
31
use Tebru\Gson\Internal\TypeAdapter\Factory\ArrayTypeAdapterFactory;
32
use Tebru\Gson\Internal\TypeAdapter\Factory\BooleanTypeAdapterFactory;
33
use Tebru\Gson\Internal\TypeAdapter\Factory\CustomWrappedTypeAdapterFactory;
34
use Tebru\Gson\Internal\TypeAdapter\Factory\DateTimeTypeAdapterFactory;
35
use Tebru\Gson\Internal\TypeAdapter\Factory\ExcluderTypeAdapterFactory;
36
use Tebru\Gson\Internal\TypeAdapter\Factory\FloatTypeAdapterFactory;
37
use Tebru\Gson\Internal\TypeAdapter\Factory\IntegerTypeAdapterFactory;
38
use Tebru\Gson\Internal\TypeAdapter\Factory\JsonElementTypeAdapterFactory;
39
use Tebru\Gson\Internal\TypeAdapter\Factory\JsonTypeAdapterFactory;
40
use Tebru\Gson\Internal\TypeAdapter\Factory\NullTypeAdapterFactory;
41
use Tebru\Gson\Internal\TypeAdapter\Factory\ReflectionTypeAdapterFactory;
42
use Tebru\Gson\Internal\TypeAdapter\Factory\StringTypeAdapterFactory;
43
use Tebru\Gson\Internal\TypeAdapter\Factory\WildcardTypeAdapterFactory;
44
use Tebru\Gson\Internal\TypeAdapter\Factory\WrappedTypeAdapterFactory;
45
use Tebru\Gson\Internal\TypeAdapterProvider;
46
47
/**
48
 * Class GsonBuilder
49
 *
50
 * @author Nate Brunette <[email protected]>
51
 */
52
class GsonBuilder
53
{
54
    /**
55
     * Array of type adapter factories
56
     *
57
     * @var TypeAdapterFactory[]
58
     */
59
    private $typeAdapterFactories = [];
60
61
    /**
62
     * @var InstanceCreator[]
63
     */
64
    private $instanceCreators = [];
65
66
    /**
67
     * Strategy for converting property names to serialized names
68
     *
69
     * @var PropertyNamingStrategy
70
     */
71
    private $propertyNamingStrategy;
72
73
    /**
74
     * Strategy for converting property names to method names
75
     *
76
     * @var MethodNamingStrategy
77
     */
78
    private $methodNamingStrategy;
79
80
    /**
81
     * The version that should be used with Since/Until annotations
82
     *
83
     * @var string
84
     */
85
    private $version;
86
87
    /**
88
     * Modifiers from [@see ReflectionProperty] that should be excluded
89
     *
90
     * @var int
91
     */
92
    private $excludedModifiers = ReflectionProperty::IS_STATIC;
93
94
    /**
95
     * True if the [@see Expose] annotation is required for serialization/deserialization
96
     *
97
     * @var bool
98
     */
99
    private $requireExpose = false;
100
101
    /**
102
     * An array of [@see ExclusionStrategy] objects
103
     *
104
     * @var ExclusionStrategy[]
105
     */
106
    private $exclusionStrategies = [];
107
108
    /**
109
     * If we should serialize nulls, defaults to false
110
     *
111
     * @var bool
112
     */
113
    private $serializeNull = false;
114
115
    /**
116
     * Default format for DateTimes
117
     *
118
     * @var string
119
     */
120
    private $dateTimeFormat = DateTime::ATOM;
121
122
    /**
123
     * True if we should be caching
124
     *
125
     * @var bool
126
     */
127
    private $enableCache = false;
128
129
    /**
130
     * Cache directory, if set this enabled filesystem caching
131
     *
132
     * @var string
133
     */
134
    private $cacheDir;
135
136
    /**
137
     * Add a custom type adapter
138
     *
139
     * @param TypeAdapterFactory $typeAdapterFactory
140
     * @return GsonBuilder
141
     */
142 12
    public function addTypeAdapterFactory(TypeAdapterFactory $typeAdapterFactory): GsonBuilder
143
    {
144 12
        $this->typeAdapterFactories[] = $typeAdapterFactory;
145
146 12
        return $this;
147
    }
148
149
    /**
150
     * Add custom handling for a specific type
151
     *
152
     * Handler objects may be of types: TypeAdapter, JsonSerializer, or JsonDeserializer
153
     *
154
     * @param string $type
155
     * @param $handler
156
     * @return GsonBuilder
157
     * @throws \BadMethodCallException If the handler is not supported
158
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
159
     */
160 7
    public function registerType(string $type, $handler): GsonBuilder
161
    {
162 7
        if ($handler instanceof TypeAdapter) {
163 3
            $this->typeAdapterFactories[] = new WrappedTypeAdapterFactory($handler, new DefaultPhpType($type));
164
165 3
            return $this;
166
        }
167
168 4 View Code Duplication
        if ($handler instanceof JsonSerializer && $handler instanceof JsonDeserializer) {
169 1
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(new DefaultPhpType($type), $handler, $handler);
170
171 1
            return $this;
172
        }
173
174 3 View Code Duplication
        if ($handler instanceof JsonSerializer) {
175 1
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(new DefaultPhpType($type), $handler);
176
177 1
            return $this;
178
        }
179
180 2 View Code Duplication
        if ($handler instanceof JsonDeserializer) {
181 1
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(new DefaultPhpType($type), null, $handler);
182
183 1
            return $this;
184
        }
185
186 1
        throw new BadMethodCallException(sprintf('Handler of type "%s" is not supported', get_class($handler)));
187
    }
188
189
    /**
190
     * Add an [@see InstanceCreator] for a given type
191
     *
192
     * @param string $type
193
     * @param InstanceCreator $instanceCreator
194
     * @return GsonBuilder
195
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
196
     */
197 2
    public function addInstanceCreator(string $type, InstanceCreator $instanceCreator): GsonBuilder
198
    {
199 2
        $phpType = new DefaultPhpType($type);
200 2
        $this->instanceCreators[$phpType->getType()] = $instanceCreator;
201
202 2
        return $this;
203
    }
204
205
    /**
206
     * Set the version to be used with [@see Since] and [@see Until] annotations
207
     *
208
     * @param string $version
209
     * @return GsonBuilder
210
     */
211 4
    public function setVersion(string $version): GsonBuilder
212
    {
213 4
        $this->version = $version;
214
215 4
        return $this;
216
    }
217
218
    /**
219
     * Set the property modifiers that should be excluded based on [@see \ReflectionProperty]
220
     *
221
     * This number is a bitmap, so ReflectionProperty::IS_STATIC will exclude all static properties.
222
     * Likewise, passing (ReflectionProperty::IS_STATIC | ReflectionProperty::IS_PRIVATE) will exclude
223
     * all static and private properties.
224
     *
225
     * @param int $modifiers
226
     * @return GsonBuilder
227
     */
228 2
    public function setExcludedModifier(int $modifiers): GsonBuilder
229
    {
230 2
        $this->excludedModifiers = $modifiers;
231
232 2
        return $this;
233
    }
234
235
    /**
236
     * Require the [@see Expose] annotation to serialize or deserialize property
237
     *
238
     * @return GsonBuilder
239
     */
240 2
    public function requireExposeAnnotation(): GsonBuilder
241
    {
242 2
        $this->requireExpose = true;
243
244 2
        return $this;
245
    }
246
247
    /**
248
     * Add an exclusion strategy that should be used during serialization/deserialization
249
     *
250
     * @param ExclusionStrategy $strategy
251
     * @param bool $serialization
252
     * @param bool $deserialization
253
     * @return GsonBuilder
254
     */
255 3
    public function addExclusionStrategy(ExclusionStrategy $strategy, bool $serialization, bool $deserialization): GsonBuilder
256
    {
257 3
        $this->exclusionStrategies[] = [$strategy, $serialization, $deserialization];
258
259 3
        return $this;
260
    }
261
262
    /**
263
     * Set a custom property naming strategy
264
     *
265
     * @param PropertyNamingStrategy $propertyNamingStrategy
266
     * @return GsonBuilder
267
     */
268 1
    public function setPropertyNamingStrategy(PropertyNamingStrategy $propertyNamingStrategy): GsonBuilder
269
    {
270 1
        $this->propertyNamingStrategy = $propertyNamingStrategy;
271
272 1
        return $this;
273
    }
274
275
    /**
276
     * Set a custom method naming strategy
277
     *
278
     * @param MethodNamingStrategy $methodNamingStrategy
279
     * @return GsonBuilder
280
     */
281 1
    public function setMethodNamingStrategy(MethodNamingStrategy $methodNamingStrategy): GsonBuilder
282
    {
283 1
        $this->methodNamingStrategy = $methodNamingStrategy;
284
285 1
        return $this;
286
    }
287
288
    /**
289
     * Set whether we should serialize null
290
     *
291
     * @return GsonBuilder
292
     */
293 1
    public function serializeNull(): GsonBuilder
294
    {
295 1
        $this->serializeNull = true;
296
297 1
        return $this;
298
    }
299
300
    /**
301
     * Set the default datetime format
302
     *
303
     * @param string $format
304
     * @return GsonBuilder
305
     */
306 1
    public function setDateTimeFormat(string $format): GsonBuilder
307
    {
308 1
        $this->dateTimeFormat = $format;
309
310 1
        return $this;
311
    }
312
313
    /**
314
     * Set whether caching is enabled
315
     *
316
     * @param bool $enableCache
317
     * @return GsonBuilder
318
     */
319 1
    public function enableCache(bool $enableCache): GsonBuilder
320
    {
321 1
        $this->enableCache = $enableCache;
322
323 1
        return $this;
324
    }
325
326
    /**
327
     * Setting a cache directory will turn on filesystem caching
328
     *
329
     * @param string $cacheDir
330
     * @return GsonBuilder
331
     */
332 1
    public function setCacheDir(string $cacheDir): GsonBuilder
333
    {
334 1
        $this->cacheDir = $cacheDir.'/gson';
335
336 1
        return $this;
337
    }
338
339
    /**
340
     * Builds a new [@see Gson] object based on configuration set
341
     *
342
     * @return Gson
343
     * @throws \InvalidArgumentException If there was a problem creating the cache
344
     * @throws \LogicException If trying to cache without a cache directory
345
     */
346 30
    public function build(): Gson
347
    {
348 30
        if (null === $this->cacheDir && true === $this->enableCache) {
349 1
            throw new LogicException('Cannot enable cache without a cache directory');
350
        }
351
352 29
        $propertyNamingStrategy = $this->propertyNamingStrategy ?? new SnakePropertyNamingStrategy();
353 29
        $methodNamingStrategy = $this->methodNamingStrategy ?? new UpperCaseMethodNamingStrategy();
354
355 29
        $doctrineAnnotationCache = false === $this->enableCache ? new ArrayCache(): new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
356 29
        $doctrineAnnotationCache->setNamespace('doctrine_annotation_cache');
357 29
        $reader = new CachedReader(new AnnotationReader(), $doctrineAnnotationCache);
358
359 29
        $cache = false === $this->enableCache ? new ArrayCache() : new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
360 29
        $cache->setNamespace('property_collection_cache');
361
362 29
        $annotationCache = false === $this->enableCache ? new ArrayCache(): new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
363 29
        $annotationCache->setNamespace('annotation_cache');
364
365 29
        $typeAdapterCache = new ArrayCache();
366 29
        $typeAdapterCache->setNamespace('type_adapter_cache');
367
368 29
        $annotationCollectionFactory = new AnnotationCollectionFactory($reader, $annotationCache);
369 29
        $excluder = new Excluder();
370 29
        $excluder->setVersion($this->version);
371 29
        $excluder->setExcludedModifiers($this->excludedModifiers);
372 29
        $excluder->setRequireExpose($this->requireExpose);
373 29
        foreach ($this->exclusionStrategies as $strategy) {
374 3
            $excluder->addExclusionStrategy($strategy[0], $strategy[1], $strategy[2]);
375
        }
376
377 29
        $metadataFactory = new MetadataFactory($annotationCollectionFactory);
378 29
        $propertyCollectionFactory = new PropertyCollectionFactory(
379 29
            new ReflectionPropertySetFactory(),
380
            $annotationCollectionFactory,
381
            $metadataFactory,
382 29
            new PropertyNamer($propertyNamingStrategy),
383 29
            new AccessorMethodProvider($methodNamingStrategy),
384 29
            new AccessorStrategyFactory(),
385 29
            new PhpTypeFactory(),
386
            $excluder,
387
            $cache
388
        );
389 29
        $constructorConstructor = new ConstructorConstructor($this->instanceCreators);
390 29
        $typeAdapterProvider = new TypeAdapterProvider(
391 29
            $this->getTypeAdapterFactories($propertyCollectionFactory, $excluder, $annotationCollectionFactory, $metadataFactory, $constructorConstructor),
392
            $typeAdapterCache,
393
            $constructorConstructor
394
        );
395
396 29
        return new Gson($typeAdapterProvider, $propertyCollectionFactory, $this->serializeNull);
397
    }
398
399
    /**
400
     * Merges default factories with user provided factories
401
     *
402
     * @param PropertyCollectionFactory $propertyCollectionFactory
403
     * @param Excluder $excluder
404
     * @param AnnotationCollectionFactory $annotationCollectionFactory
405
     * @param MetadataFactory $metadataFactory
406
     * @param ConstructorConstructor $constructorConstructor
407
     * @return array|TypeAdapterFactory[]
408
     */
409 29
    private function getTypeAdapterFactories(
410
        PropertyCollectionFactory $propertyCollectionFactory,
411
        Excluder $excluder,
412
        AnnotationCollectionFactory $annotationCollectionFactory,
413
        MetadataFactory $metadataFactory,
414
        ConstructorConstructor $constructorConstructor
415
    ): array
416
    {
417 29
        return array_merge(
418
            [
419 29
                new ExcluderTypeAdapterFactory($excluder, $metadataFactory),
420 29
                new JsonTypeAdapterFactory($annotationCollectionFactory),
421
            ],
422 29
            $this->typeAdapterFactories,
423
            [
424 29
                new StringTypeAdapterFactory(),
425 29
                new IntegerTypeAdapterFactory(),
426 29
                new FloatTypeAdapterFactory(),
427 29
                new BooleanTypeAdapterFactory(),
428 29
                new NullTypeAdapterFactory(),
429 29
                new DateTimeTypeAdapterFactory($this->dateTimeFormat),
430 29
                new ArrayTypeAdapterFactory(),
431 29
                new JsonElementTypeAdapterFactory(),
432 29
                new ReflectionTypeAdapterFactory(
433
                    $constructorConstructor,
434
                    $propertyCollectionFactory,
435
                    $metadataFactory,
436
                    $excluder
437
                ),
438 29
                new WildcardTypeAdapterFactory(),
439
            ]
440
        );
441
    }
442
}
443