Completed
Push — master ( 25e509...cbf4a9 )
by Nate
02:51
created

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