Passed
Push — master ( ad7663...2cca5c )
by Nate
03:40
created

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