Completed
Push — master ( 2ec234...5a4364 )
by Nate
03:39
created

GsonBuilder   C

Complexity

Total Complexity 24

Size/Duplication

Total Lines 343
Duplicated Lines 2.92 %

Coupling/Cohesion

Components 1
Dependencies 34

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 34
dl 10
loc 343
ccs 88
cts 88
cp 1
rs 5
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A addTypeAdapterFactory() 0 6 1
A addInstanceCreator() 0 6 1
A setVersion() 0 6 1
A setExcludedModifier() 0 6 1
A requireExposeAnnotation() 0 6 1
A addExclusionStrategy() 0 6 1
A setPropertyNamingStrategy() 0 6 1
A setMethodNamingStrategy() 0 6 1
A serializeNull() 0 6 1
A setCacheDir() 0 6 1
B registerType() 10 28 6
C build() 0 48 7
B getTypeAdapterFactories() 0 27 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 ReflectionProperty;
16
use Tebru\Gson\Internal\AccessorMethodProvider;
17
use Tebru\Gson\Internal\AccessorStrategyFactory;
18
use Tebru\Gson\Internal\ConstructorConstructor;
19
use Tebru\Gson\Internal\Data\AnnotationCollectionFactory;
20
use Tebru\Gson\Internal\Data\PropertyCollectionFactory;
21
use Tebru\Gson\Internal\Data\ReflectionPropertySetFactory;
22
use Tebru\Gson\Internal\Excluder;
23
use Tebru\Gson\Internal\Naming\PropertyNamer;
24
use Tebru\Gson\Internal\Naming\SnakePropertyNamingStrategy;
25
use Tebru\Gson\Internal\Naming\UpperCaseMethodNamingStrategy;
26
use Tebru\Gson\Internal\PhpTypeFactory;
27
use Tebru\Gson\Internal\TypeAdapter\Factory\ArrayListTypeAdapterFactory;
28
use Tebru\Gson\Internal\TypeAdapter\Factory\ArrayTypeAdapterFactory;
29
use Tebru\Gson\Internal\TypeAdapter\Factory\BooleanTypeAdapterFactory;
30
use Tebru\Gson\Internal\TypeAdapter\Factory\CustomWrappedTypeAdapterFactory;
31
use Tebru\Gson\Internal\TypeAdapter\Factory\DateTimeTypeAdapterFactory;
32
use Tebru\Gson\Internal\TypeAdapter\Factory\ExcluderTypeAdapterFactory;
33
use Tebru\Gson\Internal\TypeAdapter\Factory\FloatTypeAdapterFactory;
34
use Tebru\Gson\Internal\TypeAdapter\Factory\HashMapTypeAdapterFactory;
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
     * Cache directory, if set this enabled filesystem caching
119
     *
120
     * @var string
121
     */
122
    private $cacheDir;
123
124
    /**
125
     * Add a custom type adapter
126
     *
127
     * @param TypeAdapterFactory $typeAdapterFactory
128
     * @return GsonBuilder
129
     */
130 2
    public function addTypeAdapterFactory(TypeAdapterFactory $typeAdapterFactory): GsonBuilder
131
    {
132 2
        $this->typeAdapterFactories[] = $typeAdapterFactory;
133
134 2
        return $this;
135
    }
136
137
    /**
138
     * Add custom handling for a specific type
139
     *
140
     * Handler objects may be of types: TypeAdapter, JsonSerializer, or JsonDeserializer
141
     *
142
     * @param string $type
143
     * @param $handler
144
     * @return GsonBuilder
145
     * @throws \BadMethodCallException If the handler is not supported
146
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
147
     */
148 6
    public function registerType(string $type, $handler): GsonBuilder
149
    {
150 6
        if ($handler instanceof TypeAdapter) {
151 2
            $this->typeAdapters[(string) new PhpType($type)] = $handler;
152
153 2
            return $this;
154
        }
155
156 4
        if ($handler instanceof JsonSerializer && $handler instanceof JsonDeserializer) {
157 1
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(new PhpType($type), $handler, $handler);
158
159 1
            return $this;
160
        }
161
162 3 View Code Duplication
        if ($handler instanceof JsonSerializer) {
163 1
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(new PhpType($type), $handler);
164
165 1
            return $this;
166
        }
167
168 2 View Code Duplication
        if ($handler instanceof JsonDeserializer) {
169 1
            $this->typeAdapterFactories[] = new CustomWrappedTypeAdapterFactory(new PhpType($type), null, $handler);
170
171 1
            return $this;
172
        }
173
174 1
        throw new BadMethodCallException(sprintf('Handler of type "%s" is not supported', get_class($handler)));
175
    }
176
177
    /**
178
     * Add an [@see InstanceCreator] for a given type
179
     *
180
     * @param string $type
181
     * @param InstanceCreator $instanceCreator
182
     * @return GsonBuilder
183
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
184
     */
185 1
    public function addInstanceCreator(string $type, InstanceCreator $instanceCreator): GsonBuilder
186
    {
187 1
        $this->instanceCreators[(string) new PhpType($type)] = $instanceCreator;
188
189 1
        return $this;
190
    }
191
192
    /**
193
     * Set the version to be used with [@see Since] and [@see Until] annotations
194
     *
195
     * @param string $version
196
     * @return GsonBuilder
197
     */
198 4
    public function setVersion(string $version): GsonBuilder
199
    {
200 4
        $this->version = $version;
201
202 4
        return $this;
203
    }
204
205
    /**
206
     * Set the property modifiers that should be excluded based on [@see \ReflectionProperty]
207
     *
208
     * This number is a bitmap, so ReflectionProperty::IS_STATIC will exclude all static properties.
209
     * Likewise, passing (ReflectionProperty::IS_STATIC | ReflectionProperty::IS_PRIVATE) will exclude
210
     * all static and private properties.
211
     *
212
     * @param int $modifiers
213
     * @return GsonBuilder
214
     */
215 2
    public function setExcludedModifier(int $modifiers): GsonBuilder
216
    {
217 2
        $this->excludedModifiers = $modifiers;
218
219 2
        return $this;
220
    }
221
222
    /**
223
     * Require the [@see Expose] annotation to serialize or deserialize property
224
     *
225
     * @return GsonBuilder
226
     */
227 2
    public function requireExposeAnnotation(): GsonBuilder
228
    {
229 2
        $this->requireExpose = true;
230
231 2
        return $this;
232
    }
233
234
    /**
235
     * Add an exclusion strategy that should be used during serialization/deserialization
236
     *
237
     * @param ExclusionStrategy $strategy
238
     * @param bool $serialization
239
     * @param bool $deserialization
240
     * @return GsonBuilder
241
     */
242 2
    public function addExclusionStrategy(ExclusionStrategy $strategy, bool $serialization, bool $deserialization): GsonBuilder
243
    {
244 2
        $this->exclusionStrategies[] = [$strategy, $serialization, $deserialization];
245
246 2
        return $this;
247
    }
248
249
    /**
250
     * Set a custom property naming strategy
251
     *
252
     * @param PropertyNamingStrategy $propertyNamingStrategy
253
     * @return GsonBuilder
254
     */
255 1
    public function setPropertyNamingStrategy(PropertyNamingStrategy $propertyNamingStrategy): GsonBuilder
256
    {
257 1
        $this->propertyNamingStrategy = $propertyNamingStrategy;
258
259 1
        return $this;
260
    }
261
262
    /**
263
     * Set a custom method naming strategy
264
     *
265
     * @param MethodNamingStrategy $methodNamingStrategy
266
     * @return GsonBuilder
267
     */
268 1
    public function setMethodNamingStrategy(MethodNamingStrategy $methodNamingStrategy): GsonBuilder
269
    {
270 1
        $this->methodNamingStrategy = $methodNamingStrategy;
271
272 1
        return $this;
273
    }
274
275
    /**
276
     * Set whether we should serialize null
277
     *
278
     * @return GsonBuilder
279
     */
280 1
    public function serializeNull(): GsonBuilder
281
    {
282 1
        $this->serializeNull = true;
283
284 1
        return $this;
285
    }
286
287
    /**
288
     * Setting a cache directory will turn on filesystem caching
289
     *
290
     * @codeCoverageIgnore
291
     * @param string $cacheDir
292
     * @return GsonBuilder
293
     */
294
    public function setCacheDir(string $cacheDir): GsonBuilder
295
    {
296
        $this->cacheDir = $cacheDir.'/gson';
297
298
        return $this;
299
    }
300
301
    /**
302
     * Builds a new [@see Gson] object based on configuration set
303
     *
304
     * @return Gson
305
     * @throws \InvalidArgumentException If there was a problem creating the cache
306
     */
307 24
    public function build(): Gson
308
    {
309 24
        $propertyNamingStrategy = $this->propertyNamingStrategy ?? new SnakePropertyNamingStrategy();
310 24
        $methodNamingStrategy = $this->methodNamingStrategy ?? new UpperCaseMethodNamingStrategy();
311
312 24
        $doctrineAnnotationCache = null === $this->cacheDir ? new ArrayCache(): new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
313 24
        $doctrineAnnotationCache->setNamespace('doctrine_annotation_cache');
314 24
        $reader = new CachedReader(new AnnotationReader(), $doctrineAnnotationCache);
315
316 24
        $cache = null === $this->cacheDir ? new ArrayCache() : new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
317 24
        $cache->setNamespace('property_collection_cache');
318
319 24
        $annotationCache = null === $this->cacheDir ? new ArrayCache(): new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
320 24
        $annotationCache->setNamespace('annotation_cache');
321
322 24
        $typeAdapterCache = null === $this->cacheDir ? new ArrayCache(): new ChainCache([new ArrayCache(), new FilesystemCache($this->cacheDir)]);
323 24
        $typeAdapterCache->setNamespace('type_adapter_cache');
324
325 24
        $annotationCollectionFactory = new AnnotationCollectionFactory($reader, $annotationCache);
326 24
        $excluder = new Excluder($annotationCollectionFactory);
327 24
        $excluder->setVersion($this->version);
328 24
        $excluder->setExcludedModifiers($this->excludedModifiers);
329 24
        $excluder->setRequireExpose($this->requireExpose);
330 24
        foreach ($this->exclusionStrategies as $strategy) {
331 2
            $excluder->addExclusionStrategy($strategy[0], $strategy[1], $strategy[2]);
332
        }
333
334 24
        $propertyCollectionFactory = new PropertyCollectionFactory(
335 24
            new ReflectionPropertySetFactory(),
336
            $annotationCollectionFactory,
337 24
            new PropertyNamer($propertyNamingStrategy),
338 24
            new AccessorMethodProvider($methodNamingStrategy),
339 24
            new AccessorStrategyFactory(),
340 24
            new PhpTypeFactory(),
341
            $excluder,
342
            $cache
343
        );
344 24
        $typeAdapterProvider = new TypeAdapterProvider(
345 24
            $this->getTypeAdapterFactories($propertyCollectionFactory, $excluder, $annotationCollectionFactory),
346
            $typeAdapterCache
347
        );
348
349 24
        foreach ($this->typeAdapters as $type => $typeAdapter) {
350 2
            $typeAdapterProvider->addTypeAdapter($type, $typeAdapter);
351
        }
352
353 24
        return new Gson($typeAdapterProvider, $propertyCollectionFactory, $this->serializeNull);
354
    }
355
356
    /**
357
     * Merges default factories with user provided factories
358
     *
359
     * @param PropertyCollectionFactory $propertyCollectionFactory
360
     * @param Excluder $excluder
361
     * @param AnnotationCollectionFactory $annotationCollectionFactory
362
     * @return array|TypeAdapterFactory[]
363
     */
364 24
    private function getTypeAdapterFactories(
365
        PropertyCollectionFactory $propertyCollectionFactory,
366
        Excluder $excluder,
367
        AnnotationCollectionFactory $annotationCollectionFactory
368
    ): array {
369 24
        return array_merge(
370
            [
371 24
                new ExcluderTypeAdapterFactory($excluder),
372 24
                new JsonTypeAdapterFactory($annotationCollectionFactory),
373
            ],
374 24
            $this->typeAdapterFactories,
375
            [
376 24
                new StringTypeAdapterFactory(),
377 24
                new IntegerTypeAdapterFactory(),
378 24
                new FloatTypeAdapterFactory(),
379 24
                new BooleanTypeAdapterFactory(),
380 24
                new NullTypeAdapterFactory(),
381 24
                new DateTimeTypeAdapterFactory(),
382 24
                new ArrayTypeAdapterFactory(),
383 24
                new ArrayListTypeAdapterFactory(),
384 24
                new HashMapTypeAdapterFactory(),
385 24
                new JsonElementTypeAdapterFactory(),
386 24
                new ReflectionTypeAdapterFactory(new ConstructorConstructor($this->instanceCreators), $propertyCollectionFactory, $excluder),
387 24
                new WildcardTypeAdapterFactory(),
388
            ]
389
        );
390
    }
391
}
392