Completed
Push — split-exclusion-strategies ( e073a8 )
by Nate
03:36
created

GsonBuilder::setPropertyNamingPolicy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
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;
10
11
use DateTime;
12
use Doctrine\Common\Annotations\AnnotationReader;
13
use InvalidArgumentException;
14
use LogicException;
15
use Psr\SimpleCache\CacheInterface;
16
use ReflectionProperty;
17
use Symfony\Component\Cache\Simple\ArrayCache;
18
use Symfony\Component\Cache\Simple\ChainCache;
19
use Symfony\Component\Cache\Simple\NullCache;
20
use Symfony\Component\Cache\Simple\PhpFilesCache;
21
use Tebru\AnnotationReader\AnnotationReaderAdapter;
22
use Tebru\Gson\Exclusion\ExclusionStrategy;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Tebru\Gson\ExclusionStrategy. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
23
use Tebru\Gson\ExclusionStrategy as DeprecatedExclusionStrategy;
24
use Tebru\Gson\Internal\AccessorMethodProvider;
25
use Tebru\Gson\Internal\AccessorStrategyFactory;
26
use Tebru\Gson\Internal\ConstructorConstructor;
27
use Tebru\Gson\Internal\Data\ClassMetadataFactory;
28
use Tebru\Gson\Internal\Data\ReflectionPropertySetFactory;
29
use Tebru\Gson\Internal\DiscriminatorDeserializer;
30
use Tebru\Gson\Internal\Excluder;
31
use Tebru\Gson\Internal\ExclusionStrategyAdapter;
32
use Tebru\Gson\Internal\Naming\DefaultPropertyNamingStrategy;
33
use Tebru\Gson\Internal\Naming\PropertyNamer;
34
use Tebru\Gson\Internal\Naming\UpperCaseMethodNamingStrategy;
35
use Tebru\Gson\Internal\PhpTypeFactory;
36
use Tebru\Gson\Internal\TypeAdapter\Factory\ArrayTypeAdapterFactory;
37
use Tebru\Gson\Internal\TypeAdapter\Factory\BooleanTypeAdapterFactory;
38
use Tebru\Gson\Internal\TypeAdapter\Factory\CustomWrappedTypeAdapterFactory;
39
use Tebru\Gson\Internal\TypeAdapter\Factory\DateTimeTypeAdapterFactory;
40
use Tebru\Gson\Internal\TypeAdapter\Factory\FloatTypeAdapterFactory;
41
use Tebru\Gson\Internal\TypeAdapter\Factory\IntegerTypeAdapterFactory;
42
use Tebru\Gson\Internal\TypeAdapter\Factory\JsonElementTypeAdapterFactory;
43
use Tebru\Gson\Internal\TypeAdapter\Factory\NullTypeAdapterFactory;
44
use Tebru\Gson\Internal\TypeAdapter\Factory\ReflectionTypeAdapterFactory;
45
use Tebru\Gson\Internal\TypeAdapter\Factory\StringTypeAdapterFactory;
46
use Tebru\Gson\Internal\TypeAdapter\Factory\WildcardTypeAdapterFactory;
47
use Tebru\Gson\Internal\TypeAdapter\Factory\WrappedTypeAdapterFactory;
48
use Tebru\Gson\Internal\TypeAdapterProvider;
49
use Tebru\PhpType\TypeToken;
50
51
/**
52
 * Class GsonBuilder
53
 *
54
 * @author Nate Brunette <[email protected]>
55
 */
56
class GsonBuilder
57
{
58
    /**
59
     * Array of type adapter factories
60
     *
61
     * @var TypeAdapterFactory[]
62
     */
63
    private $typeAdapterFactories = [];
64
65
    /**
66
     * @var InstanceCreator[]
67
     */
68
    private $instanceCreators = [];
69
70
    /**
71
     * Strategy for converting property names to serialized names
72
     *
73
     * @var PropertyNamingStrategy
74
     */
75
    private $propertyNamingStrategy;
76
77
    /**
78
     * Property naming policy
79
     *
80
     * Defaults to converting camel case to snake case
81
     *
82
     * @var string
83
     */
84
    private $propertyNamingPolicy = PropertyNamingPolicy::LOWER_CASE_WITH_UNDERSCORES;
85
86
    /**
87
     * Strategy for converting property names to method names
88
     *
89
     * @var MethodNamingStrategy
90
     */
91
    private $methodNamingStrategy;
92
93
    /**
94
     * The version that should be used with Since/Until annotations
95
     *
96
     * @var string
97
     */
98
    private $version;
99
100
    /**
101
     * Modifiers from [@see ReflectionProperty] that should be excluded
102
     *
103
     * @var int
104
     */
105
    private $excludedModifiers = ReflectionProperty::IS_STATIC;
106
107
    /**
108
     * True if the [@see Expose] annotation is required for serialization/deserialization
109
     *
110
     * @var bool
111
     */
112
    private $requireExpose = false;
113
114
    /**
115
     * An array of [@see ExclusionStrategy] objects
116
     *
117
     * @var ExclusionStrategy[]
118
     */
119
    private $exclusionStrategies = [];
120
121
    /**
122
     * An array of Cacheable [@see ExclusionStrategy] objects
123
     *
124
     * @var ExclusionStrategy[]
125
     */
126
    private $cachedExclusionStrategies = [];
127
128
    /**
129
     * If we should serialize nulls, defaults to false
130
     *
131
     * @var bool
132
     */
133
    private $serializeNull = false;
134
135
    /**
136
     * Default format for DateTimes
137
     *
138
     * @var string
139
     */
140
    private $dateTimeFormat = DateTime::ATOM;
141
142
    /**
143
     * A cache interface to be used in place of defaults
144
     *
145
     * If this is set, [@see GsonBuilder::$enableCache] will be ignored
146
     *
147
     * @var CacheInterface
148
     */
149
    private $cache;
150
151
    /**
152
     * True if we should be caching
153
     *
154
     * @var bool
155
     */
156
    private $enableCache = false;
157
158
    /**
159
     * Cache directory, if set this enabled filesystem caching
160
     *
161
     * @var string
162
     */
163
    private $cacheDir;
164
165
    /**
166
     * Add a custom type adapter
167
     *
168
     * @param TypeAdapterFactory $typeAdapterFactory
169
     * @return GsonBuilder
170
     */
171
    public function addTypeAdapterFactory(TypeAdapterFactory $typeAdapterFactory): GsonBuilder
172
    {
173
        $this->typeAdapterFactories[] = $typeAdapterFactory;
174
175
        return $this;
176
    }
177
178
    /**
179
     * Adds a [@see Discriminator] as a type adapter factory
180
     *
181
     * @param string $type
182
     * @param Discriminator $discriminator
183
     * @return GsonBuilder
184
     */
185
    public function addDiscriminator(string $type, Discriminator $discriminator): GsonBuilder
186
    {
187
        $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(
188
            TypeToken::create($type),
189
            true,
190
            null,
191
            new DiscriminatorDeserializer($discriminator)
192
        );
193
194
        return $this;
195
    }
196
197
    /**
198
     * Add custom handling for a specific type
199
     *
200
     * Handler objects may be of types: TypeAdapter, JsonSerializer, or JsonDeserializer. Passing
201
     * $strict=true will match the specified type exactly, as opposed to checking anywhere in the
202
     * inheritance chain.
203
     *
204
     * @param string $type
205
     * @param $handler
206
     * @param bool $strict
207
     * @return GsonBuilder
208
     */
209
    public function registerType(string $type, $handler, bool $strict = false): GsonBuilder
210
    {
211
        if ($handler instanceof TypeAdapter) {
212
            $this->typeAdapterFactories[] = new WrappedTypeAdapterFactory($handler, TypeToken::create($type), $strict);
213
214
            return $this;
215
        }
216
217
        if ($handler instanceof JsonSerializer && $handler instanceof JsonDeserializer) {
218
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(TypeToken::create($type), $strict, $handler, $handler);
219
220
            return $this;
221
        }
222
223
        if ($handler instanceof JsonSerializer) {
224
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(TypeToken::create($type), $strict, $handler);
225
226
            return $this;
227
        }
228
229
        if ($handler instanceof JsonDeserializer) {
230
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(TypeToken::create($type), $strict, null, $handler);
231
232
            return $this;
233
        }
234
235
        throw new InvalidArgumentException(\sprintf('Handler of type "%s" is not supported', \get_class($handler)));
236
    }
237
238
    /**
239
     * Add an [@see InstanceCreator] for a given type
240
     *
241
     * @param string $type
242
     * @param InstanceCreator $instanceCreator
243
     * @return GsonBuilder
244
     */
245
    public function addInstanceCreator(string $type, InstanceCreator $instanceCreator): GsonBuilder
246
    {
247
        $phpType = TypeToken::create($type);
248
        $this->instanceCreators[$phpType->getRawType()] = $instanceCreator;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Set the version to be used with [@see Since] and [@see Until] annotations
255
     *
256
     * @param string $version
257
     * @return GsonBuilder
258
     */
259
    public function setVersion(string $version): GsonBuilder
260
    {
261
        $this->version = $version;
262
263
        return $this;
264
    }
265
266
    /**
267
     * Set the property modifiers that should be excluded based on [@see \ReflectionProperty]
268
     *
269
     * This number is a bitmap, so ReflectionProperty::IS_STATIC will exclude all static properties.
270
     * Likewise, passing (ReflectionProperty::IS_STATIC | ReflectionProperty::IS_PRIVATE) will exclude
271
     * all static and private properties.
272
     *
273
     * @param int $modifiers
274
     * @return GsonBuilder
275
     */
276
    public function setExcludedModifier(int $modifiers): GsonBuilder
277
    {
278
        $this->excludedModifiers = $modifiers;
279
280
        return $this;
281
    }
282
283
    /**
284
     * Require the [@see Expose] annotation to serialize or deserialize property
285
     *
286
     * @return GsonBuilder
287
     */
288
    public function requireExposeAnnotation(): GsonBuilder
289
    {
290
        $this->requireExpose = true;
291
292
        return $this;
293
    }
294
295
    /**
296
     * Add an exclusion strategy that should be used during serialization/deserialization
297
     *
298
     * @param DeprecatedExclusionStrategy $strategy
299
     * @param bool $serialization
300
     * @param bool $deserialization
301
     * @return GsonBuilder
302
     * @deprecated Since v0.6.0 to be removed in v0.7.0. Use GsonBuilder::addExclusion() instead.
303
     */
304
    public function addExclusionStrategy(DeprecatedExclusionStrategy $strategy, bool $serialization, bool $deserialization): GsonBuilder
305
    {
306
        @trigger_error('Gson: GsonBuilder::addExclusionStrategy() is deprecated since v0.6.0 and will be removed in v0.7.0. Use GsonBuilder::addExclusion() instead.', E_USER_DEPRECATED);
307
308
        $this->addExclusion(new ExclusionStrategyAdapter($strategy, $serialization, $deserialization));
309
310
        return $this;
311
    }
312
313
    /**
314
     * Add an [@see ExclusionStrategy]
315
     *
316
     * @param ExclusionStrategy $exclusionStrategy
317
     * @return GsonBuilder
318
     */
319
    public function addExclusion(ExclusionStrategy $exclusionStrategy): GsonBuilder
320
    {
321
        if ($exclusionStrategy->shouldCache()) {
322
            $this->cachedExclusionStrategies[] = $exclusionStrategy;
323
            return $this;
324
        }
325
326
        $this->exclusionStrategies[] = $exclusionStrategy;
327
328
        return $this;
329
    }
330
331
    /**
332
     * Set a custom property naming strategy
333
     *
334
     * @param PropertyNamingStrategy $propertyNamingStrategy
335
     * @return GsonBuilder
336
     */
337
    public function setPropertyNamingStrategy(PropertyNamingStrategy $propertyNamingStrategy): GsonBuilder
338
    {
339
        $this->propertyNamingStrategy = $propertyNamingStrategy;
340
341
        return $this;
342
    }
343
344
    /**
345
     * Set one of [@see PropertyNamingPolicy]
346
     *
347
     * @param string $policy
348
     * @return GsonBuilder
349
     */
350
    public function setPropertyNamingPolicy(string $policy): GsonBuilder
351
    {
352
        $this->propertyNamingPolicy = $policy;
353
354
        return $this;
355
    }
356
357
    /**
358
     * Set a custom method naming strategy
359
     *
360
     * @param MethodNamingStrategy $methodNamingStrategy
361
     * @return GsonBuilder
362
     */
363
    public function setMethodNamingStrategy(MethodNamingStrategy $methodNamingStrategy): GsonBuilder
364
    {
365
        $this->methodNamingStrategy = $methodNamingStrategy;
366
367
        return $this;
368
    }
369
370
    /**
371
     * Set whether we should serialize null
372
     *
373
     * @return GsonBuilder
374
     */
375
    public function serializeNull(): GsonBuilder
376
    {
377
        $this->serializeNull = true;
378
379
        return $this;
380
    }
381
382
    /**
383
     * Set the default datetime format
384
     *
385
     * @param string $format
386
     * @return GsonBuilder
387
     */
388
    public function setDateTimeFormat(string $format): GsonBuilder
389
    {
390
        $this->dateTimeFormat = $format;
391
392
        return $this;
393
    }
394
395
    /**
396
     * Override default cache adapters
397
     *
398
     * @param CacheInterface $cache
399
     * @return GsonBuilder
400
     */
401
    public function setCache(CacheInterface $cache): GsonBuilder
402
    {
403
        $this->cache = $cache;
404
405
        return $this;
406
    }
407
408
409
    /**
410
     * Set whether caching is enabled
411
     *
412
     * @param bool $enableCache
413
     * @return GsonBuilder
414
     */
415
    public function enableCache(bool $enableCache): GsonBuilder
416
    {
417
        $this->enableCache = $enableCache;
418
419
        return $this;
420
    }
421
422
    /**
423
     * Setting a cache directory will turn on filesystem caching
424
     *
425
     * @param string $cacheDir
426
     * @return GsonBuilder
427
     */
428
    public function setCacheDir(string $cacheDir): GsonBuilder
429
    {
430
        $this->cacheDir = $cacheDir.'/gson';
431
432
        return $this;
433
    }
434
435
    /**
436
     * Builds a new [@see Gson] object based on configuration set
437
     *
438
     * @return Gson
439
     * @throws \LogicException
440
     */
441
    public function build(): Gson
442
    {
443
        if (null === $this->cacheDir && true === $this->enableCache) {
444
            throw new LogicException('Cannot enable cache without a cache directory');
445
        }
446
447
        $propertyNamingStrategy = $this->propertyNamingStrategy ?? new DefaultPropertyNamingStrategy($this->propertyNamingPolicy);
448
        $methodNamingStrategy = $this->methodNamingStrategy ?? new UpperCaseMethodNamingStrategy();
449
450
        if ($this->cache === null) {
451
            $this->cache = false === $this->enableCache
452
                ? new ArrayCache(0, false)
453
                : new ChainCache([new ArrayCache(0, false), new PhpFilesCache('', 0, $this->cacheDir)]);
454
        }
455
456
        $annotationReader = new AnnotationReaderAdapter(new AnnotationReader(), new NullCache());
457
        $excluder = new Excluder();
458
        $excluder->setVersion($this->version);
459
        $excluder->setExcludedModifiers($this->excludedModifiers);
460
        $excluder->setRequireExpose($this->requireExpose);
461
462
        foreach ($this->exclusionStrategies as $strategy) {
463
            $excluder->addExclusionStrategy($strategy);
464
        }
465
466
        foreach ($this->cachedExclusionStrategies as $strategy) {
467
            $excluder->addCachedExclusionStrategy($strategy);
468
        }
469
470
        $classMetadataFactory = new ClassMetadataFactory(
471
            new ReflectionPropertySetFactory(),
472
            $annotationReader,
473
            new PropertyNamer($propertyNamingStrategy),
474
            new AccessorMethodProvider($methodNamingStrategy),
475
            new AccessorStrategyFactory(),
476
            new PhpTypeFactory(),
477
            $excluder,
478
            $this->cache
479
        );
480
        $constructorConstructor = new ConstructorConstructor($this->instanceCreators);
481
        $typeAdapterProvider = new TypeAdapterProvider(
482
            $this->getTypeAdapterFactories($classMetadataFactory, $excluder, $annotationReader, $constructorConstructor),
483
            $constructorConstructor
484
        );
485
486
        return new Gson($typeAdapterProvider, $this->serializeNull);
487
    }
488
489
    /**
490
     * Merges default factories with user provided factories
491
     *
492
     * @param ClassMetadataFactory $classMetadataFactory
493
     * @param Excluder $excluder
494
     * @param AnnotationReaderAdapter $annotationReader
495
     * @param ConstructorConstructor $constructorConstructor
496
     * @return array|TypeAdapterFactory[]
497
     */
498
    private function getTypeAdapterFactories(
499
        ClassMetadataFactory $classMetadataFactory,
500
        Excluder $excluder,
501
        AnnotationReaderAdapter $annotationReader,
502
        ConstructorConstructor $constructorConstructor
503
    ): array {
504
        return \array_merge(
505
            $this->typeAdapterFactories,
506
            [
507
                new StringTypeAdapterFactory(),
508
                new IntegerTypeAdapterFactory(),
509
                new FloatTypeAdapterFactory(),
510
                new BooleanTypeAdapterFactory(),
511
                new NullTypeAdapterFactory(),
512
                new DateTimeTypeAdapterFactory($this->dateTimeFormat),
513
                new ArrayTypeAdapterFactory(),
514
                new JsonElementTypeAdapterFactory(),
515
                new ReflectionTypeAdapterFactory(
516
                    $constructorConstructor,
517
                    $classMetadataFactory,
518
                    $annotationReader,
519
                    $excluder
520
                ),
521
                new WildcardTypeAdapterFactory(),
522
            ]
523
        );
524
    }
525
}
526