Completed
Pull Request — master (#869)
by Asmir
10:42
created

SerializerBuilder   F

Complexity

Total Complexity 57

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 31

Test Coverage

Coverage 70.86%

Importance

Changes 0
Metric Value
wmc 57
lcom 1
cbo 31
dl 0
loc 433
ccs 124
cts 175
cp 0.7086
rs 1.3043
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 4 1
A setAccessorStrategy() 0 4 1
A setExpressionEvaluator() 0 6 1
A setAnnotationReader() 0 6 1
A setDebug() 0 6 1
A addDefaultHandlers() 0 11 1
A configureHandlers() 0 7 1
A addDefaultListeners() 0 7 1
A configureListeners() 0 7 1
A setSerializationVisitor() 0 7 1
A setDeserializationVisitor() 0 7 1
A includeInterfaceMetadata() 0 6 1
A addMetadataDir() 0 14 3
A replaceMetadataDir() 0 14 3
A setMetadataDriverFactory() 0 6 1
A initializePropertyNamingStrategy() 0 8 2
A createDir() 0 10 4
A __construct() 0 8 1
A getAccessorStrategy() 0 11 3
A setCacheDir() 0 13 3
A setObjectConstructor() 0 6 1
A setPropertyNamingStrategy() 0 6 1
A addDefaultSerializationVisitors() 0 13 1
A addDefaultDeserializationVisitors() 0 12 1
A setMetadataDirs() 0 12 3
A addMetadataDirs() 0 8 2
A setSerializationContextFactory() 0 14 3
A setDeserializationContextFactory() 0 14 3
C build() 0 57 10

How to fix   Complexity   

Complex Class

Complex classes like SerializerBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SerializerBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer;
20
21
use Doctrine\Common\Annotations\AnnotationReader;
22
use Doctrine\Common\Annotations\CachedReader;
23
use Doctrine\Common\Annotations\Reader;
24
use Doctrine\Common\Cache\FilesystemCache;
25
use JMS\Serializer\Accessor\AccessorStrategyInterface;
26
use JMS\Serializer\Accessor\DefaultAccessorStrategy;
27
use JMS\Serializer\Accessor\ExpressionAccessorStrategy;
28
use JMS\Serializer\Builder\DefaultDriverFactory;
29
use JMS\Serializer\Builder\DriverFactoryInterface;
30
use JMS\Serializer\Construction\ObjectConstructorInterface;
31
use JMS\Serializer\Construction\UnserializeObjectConstructor;
32
use JMS\Serializer\ContextFactory\CallableDeserializationContextFactory;
33
use JMS\Serializer\ContextFactory\CallableSerializationContextFactory;
34
use JMS\Serializer\ContextFactory\DeserializationContextFactoryInterface;
35
use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface;
36
use JMS\Serializer\EventDispatcher\EventDispatcher;
37
use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
38
use JMS\Serializer\Exception\InvalidArgumentException;
39
use JMS\Serializer\Exception\RuntimeException;
40
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
41
use JMS\Serializer\Handler\ArrayCollectionHandler;
42
use JMS\Serializer\Handler\DateHandler;
43
use JMS\Serializer\Handler\HandlerRegistry;
44
use JMS\Serializer\Handler\PhpCollectionHandler;
45
use JMS\Serializer\Handler\PropelCollectionHandler;
46
use JMS\Serializer\Handler\StdClassHandler;
47
use JMS\Serializer\Naming\CamelCaseNamingStrategy;
48
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
49
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy;
50
use Metadata\Cache\FileCache;
51
use Metadata\MetadataFactory;
52
use PhpCollection\Map;
53
54
/**
55
 * Builder for serializer instances.
56
 *
57
 * This object makes serializer construction a breeze for projects that do not use
58
 * any special dependency injection container.
59
 *
60
 * @author Johannes M. Schmitt <[email protected]>
61
 */
62
class SerializerBuilder
63
{
64
    private $metadataDirs = array();
65
    private $handlerRegistry;
66
    private $handlersConfigured = false;
67
    private $eventDispatcher;
68
    private $listenersConfigured = false;
69
    private $objectConstructor;
70
    private $serializationVisitors;
71
    private $deserializationVisitors;
72
    private $visitorsAdded = false;
73
    private $propertyNamingStrategy;
74
    private $debug = false;
75
    private $cacheDir;
76
    private $annotationReader;
77
    private $includeInterfaceMetadata = false;
78
    private $driverFactory;
79
    private $serializationContextFactory;
80
    private $deserializationContextFactory;
81
82
    /**
83
     * @var ExpressionEvaluatorInterface
84
     */
85
    private $expressionEvaluator;
86
87
    /**
88
     * @var AccessorStrategyInterface
89
     */
90
    private $accessorStrategy;
91
92 29
    public static function create()
93
    {
94 29
        return new static();
95
    }
96
97 29
    public function __construct()
98
    {
99 29
        $this->handlerRegistry = new HandlerRegistry();
100 29
        $this->eventDispatcher = new EventDispatcher();
101 29
        $this->driverFactory = new DefaultDriverFactory();
102 29
        $this->serializationVisitors = new Map();
103 29
        $this->deserializationVisitors = new Map();
104 29
    }
105
106
    public function setAccessorStrategy(AccessorStrategyInterface $accessorStrategy)
107
    {
108
        $this->accessorStrategy = $accessorStrategy;
109
    }
110
111 28
    protected function getAccessorStrategy()
112
    {
113 28
        if (!$this->accessorStrategy) {
114 28
            $this->accessorStrategy = new DefaultAccessorStrategy();
115
116 28
            if ($this->expressionEvaluator) {
117 3
                $this->accessorStrategy = new ExpressionAccessorStrategy($this->expressionEvaluator, $this->accessorStrategy);
118
            }
119
        }
120 28
        return $this->accessorStrategy;
121
    }
122
123 3
    public function setExpressionEvaluator(ExpressionEvaluatorInterface $expressionEvaluator)
124
    {
125 3
        $this->expressionEvaluator = $expressionEvaluator;
126
127 3
        return $this;
128
    }
129
130
    public function setAnnotationReader(Reader $reader)
131
    {
132
        $this->annotationReader = $reader;
133
134
        return $this;
135
    }
136
137
    public function setDebug($bool)
138
    {
139
        $this->debug = (boolean)$bool;
140
141
        return $this;
142
    }
143
144 1
    public function setCacheDir($dir)
145
    {
146 1
        if (!is_dir($dir)) {
147 1
            $this->createDir($dir);
148
        }
149 1
        if (!is_writable($dir)) {
150
            throw new InvalidArgumentException(sprintf('The cache directory "%s" is not writable.', $dir));
151
        }
152
153 1
        $this->cacheDir = $dir;
154
155 1
        return $this;
156
    }
157
158 28
    public function addDefaultHandlers()
159
    {
160 28
        $this->handlersConfigured = true;
161 28
        $this->handlerRegistry->registerSubscribingHandler(new DateHandler());
162 28
        $this->handlerRegistry->registerSubscribingHandler(new StdClassHandler());
163 28
        $this->handlerRegistry->registerSubscribingHandler(new PhpCollectionHandler());
164 28
        $this->handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler());
165 28
        $this->handlerRegistry->registerSubscribingHandler(new PropelCollectionHandler());
166
167 28
        return $this;
168
    }
169
170 1
    public function configureHandlers(\Closure $closure)
171
    {
172 1
        $this->handlersConfigured = true;
173 1
        $closure($this->handlerRegistry);
174
175 1
        return $this;
176
    }
177
178 28
    public function addDefaultListeners()
179
    {
180 28
        $this->listenersConfigured = true;
181 28
        $this->eventDispatcher->addSubscriber(new DoctrineProxySubscriber());
182
183 28
        return $this;
184
    }
185
186 1
    public function configureListeners(\Closure $closure)
187
    {
188 1
        $this->listenersConfigured = true;
189 1
        $closure($this->eventDispatcher);
190
191 1
        return $this;
192
    }
193
194
    public function setObjectConstructor(ObjectConstructorInterface $constructor)
195
    {
196
        $this->objectConstructor = $constructor;
197
198
        return $this;
199
    }
200
201
    public function setPropertyNamingStrategy(PropertyNamingStrategyInterface $propertyNamingStrategy)
202
    {
203
        $this->propertyNamingStrategy = $propertyNamingStrategy;
204
205
        return $this;
206
    }
207
208 1
    public function setSerializationVisitor($format, VisitorInterface $visitor)
209
    {
210 1
        $this->visitorsAdded = true;
211 1
        $this->serializationVisitors->set($format, $visitor);
212
213 1
        return $this;
214
    }
215
216
    public function setDeserializationVisitor($format, VisitorInterface $visitor)
217
    {
218
        $this->visitorsAdded = true;
219
        $this->deserializationVisitors->set($format, $visitor);
220
221
        return $this;
222
    }
223
224 28
    public function addDefaultSerializationVisitors()
225
    {
226 28
        $this->initializePropertyNamingStrategy();
227
228 28
        $this->visitorsAdded = true;
229 28
        $this->serializationVisitors->setAll(array(
230 28
            'xml' => new XmlSerializationVisitor($this->propertyNamingStrategy, $this->getAccessorStrategy()),
231 28
            'yml' => new YamlSerializationVisitor($this->propertyNamingStrategy, $this->getAccessorStrategy()),
232 28
            'json' => new JsonSerializationVisitor($this->propertyNamingStrategy, $this->getAccessorStrategy()),
233
        ));
234
235 28
        return $this;
236
    }
237
238 28
    public function addDefaultDeserializationVisitors()
239
    {
240 28
        $this->initializePropertyNamingStrategy();
241
242 28
        $this->visitorsAdded = true;
243 28
        $this->deserializationVisitors->setAll(array(
244 28
            'xml' => new XmlDeserializationVisitor($this->propertyNamingStrategy),
245 28
            'json' => new JsonDeserializationVisitor($this->propertyNamingStrategy),
246
        ));
247
248 28
        return $this;
249
    }
250
251
    /**
252
     * @param Boolean $include Whether to include the metadata from the interfaces
253
     *
254
     * @return SerializerBuilder
255
     */
256 1
    public function includeInterfaceMetadata($include)
257
    {
258 1
        $this->includeInterfaceMetadata = (Boolean)$include;
259
260 1
        return $this;
261
    }
262
263
    /**
264
     * Sets a map of namespace prefixes to directories.
265
     *
266
     * This method overrides any previously defined directories.
267
     *
268
     * @param array <string,string> $namespacePrefixToDirMap
269
     *
270
     * @return SerializerBuilder
271
     *
272
     * @throws InvalidArgumentException When a directory does not exist
273
     */
274
    public function setMetadataDirs(array $namespacePrefixToDirMap)
275
    {
276
        foreach ($namespacePrefixToDirMap as $dir) {
277
            if (!is_dir($dir)) {
278
                throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
279
            }
280
        }
281
282
        $this->metadataDirs = $namespacePrefixToDirMap;
283
284
        return $this;
285
    }
286
287
    /**
288
     * Adds a directory where the serializer will look for class metadata.
289
     *
290
     * The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume
291
     * that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace.
292
     *
293
     * If you use an empty prefix, your metadata files would need to look like:
294
     *
295
     * ``my-dir/MyApplication.Entity.SomeObject.yml``
296
     * ``my-dir/MyApplication.Entity.OtherObject.xml``
297
     *
298
     * If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like:
299
     *
300
     * ``my-dir/SomeObject.yml``
301
     * ``my-dir/OtherObject.yml``
302
     *
303
     * Please keep in mind that you currently may only have one directory per namespace prefix.
304
     *
305
     * @param string $dir The directory where metadata files are located.
306
     * @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory.
307
     *
308
     * @return SerializerBuilder
309
     *
310
     * @throws InvalidArgumentException When a directory does not exist
311
     * @throws InvalidArgumentException When a directory has already been registered
312
     */
313
    public function addMetadataDir($dir, $namespacePrefix = '')
314
    {
315
        if (!is_dir($dir)) {
316
            throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
317
        }
318
319
        if (isset($this->metadataDirs[$namespacePrefix])) {
320
            throw new InvalidArgumentException(sprintf('There is already a directory configured for the namespace prefix "%s". Please use replaceMetadataDir() to override directories.', $namespacePrefix));
321
        }
322
323
        $this->metadataDirs[$namespacePrefix] = $dir;
324
325
        return $this;
326
    }
327
328
    /**
329
     * Adds a map of namespace prefixes to directories.
330
     *
331
     * @param array <string,string> $namespacePrefixToDirMap
332
     *
333
     * @return SerializerBuilder
334
     */
335
    public function addMetadataDirs(array $namespacePrefixToDirMap)
336
    {
337
        foreach ($namespacePrefixToDirMap as $prefix => $dir) {
338
            $this->addMetadataDir($dir, $prefix);
339
        }
340
341
        return $this;
342
    }
343
344
    /**
345
     * Similar to addMetadataDir(), but overrides an existing entry.
346
     *
347
     * @param string $dir
348
     * @param string $namespacePrefix
349
     *
350
     * @return SerializerBuilder
351
     *
352
     * @throws InvalidArgumentException When a directory does not exist
353
     * @throws InvalidArgumentException When no directory is configured for the ns prefix
354
     */
355
    public function replaceMetadataDir($dir, $namespacePrefix = '')
356
    {
357
        if (!is_dir($dir)) {
358
            throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
359
        }
360
361
        if (!isset($this->metadataDirs[$namespacePrefix])) {
362
            throw new InvalidArgumentException(sprintf('There is no directory configured for namespace prefix "%s". Please use addMetadataDir() for adding new directories.', $namespacePrefix));
363
        }
364
365
        $this->metadataDirs[$namespacePrefix] = $dir;
366
367
        return $this;
368
    }
369
370 12
    public function setMetadataDriverFactory(DriverFactoryInterface $driverFactory)
371
    {
372 12
        $this->driverFactory = $driverFactory;
373
374 12
        return $this;
375
    }
376
377
    /**
378
     * @param SerializationContextFactoryInterface|callable $serializationContextFactory
379
     *
380
     * @return self
381
     */
382 3
    public function setSerializationContextFactory($serializationContextFactory)
383
    {
384 3
        if ($serializationContextFactory instanceof SerializationContextFactoryInterface) {
385 1
            $this->serializationContextFactory = $serializationContextFactory;
386 2
        } elseif (is_callable($serializationContextFactory)) {
387 2
            $this->serializationContextFactory = new CallableSerializationContextFactory(
388 2
                $serializationContextFactory
389
            );
390
        } else {
391
            throw new InvalidArgumentException('expected SerializationContextFactoryInterface or callable.');
392
        }
393
394 3
        return $this;
395
    }
396
397
    /**
398
     * @param DeserializationContextFactoryInterface|callable $deserializationContextFactory
399
     *
400
     * @return self
401
     */
402 1
    public function setDeserializationContextFactory($deserializationContextFactory)
403
    {
404 1
        if ($deserializationContextFactory instanceof DeserializationContextFactoryInterface) {
405 1
            $this->deserializationContextFactory = $deserializationContextFactory;
406
        } elseif (is_callable($deserializationContextFactory)) {
407
            $this->deserializationContextFactory = new CallableDeserializationContextFactory(
408
                $deserializationContextFactory
409
            );
410
        } else {
411
            throw new InvalidArgumentException('expected DeserializationContextFactoryInterface or callable.');
412
        }
413
414 1
        return $this;
415
    }
416
417 29
    public function build()
418
    {
419 29
        $annotationReader = $this->annotationReader;
420 29
        if (null === $annotationReader) {
421 29
            $annotationReader = new AnnotationReader();
422
423 29
            if (null !== $this->cacheDir) {
424 1
                $this->createDir($this->cacheDir . '/annotations');
425 1
                $annotationsCache = new FilesystemCache($this->cacheDir . '/annotations');
426 1
                $annotationReader = new CachedReader($annotationReader, $annotationsCache, $this->debug);
427
            }
428
        }
429
430 29
        $metadataDriver = $this->driverFactory->createDriver($this->metadataDirs, $annotationReader);
431 29
        $metadataFactory = new MetadataFactory($metadataDriver, null, $this->debug);
432
433 29
        $metadataFactory->setIncludeInterfaces($this->includeInterfaceMetadata);
434
435 29
        if (null !== $this->cacheDir) {
436 1
            $this->createDir($this->cacheDir . '/metadata');
437 1
            $metadataFactory->setCache(new FileCache($this->cacheDir . '/metadata'));
438
        }
439
440 29
        if (!$this->handlersConfigured) {
441 27
            $this->addDefaultHandlers();
442
        }
443
444 29
        if (!$this->listenersConfigured) {
445 28
            $this->addDefaultListeners();
446
        }
447
448 29
        if (!$this->visitorsAdded) {
449 28
            $this->addDefaultSerializationVisitors();
450 28
            $this->addDefaultDeserializationVisitors();
451
        }
452
453 29
        $serializer = new Serializer(
454 29
            $metadataFactory,
455 29
            $this->handlerRegistry,
456 29
            $this->objectConstructor ?: new UnserializeObjectConstructor(),
457 29
            $this->serializationVisitors,
458 29
            $this->deserializationVisitors,
459 29
            $this->eventDispatcher,
460 29
            null,
461 29
            $this->expressionEvaluator
462
        );
463
464 29
        if (null !== $this->serializationContextFactory) {
465 3
            $serializer->setSerializationContextFactory($this->serializationContextFactory);
466
        }
467
468 29
        if (null !== $this->deserializationContextFactory) {
469 1
            $serializer->setDeserializationContextFactory($this->deserializationContextFactory);
470
        }
471
472 29
        return $serializer;
473
    }
474
475 28
    private function initializePropertyNamingStrategy()
476
    {
477 28
        if (null !== $this->propertyNamingStrategy) {
478 28
            return;
479
        }
480
481 28
        $this->propertyNamingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy());
482 28
    }
483
484 1
    private function createDir($dir)
485
    {
486 1
        if (is_dir($dir)) {
487
            return;
488
        }
489
490 1
        if (false === @mkdir($dir, 0777, true) && false === is_dir($dir)) {
491
            throw new RuntimeException(sprintf('Could not create directory "%s".', $dir));
492
        }
493 1
    }
494
}
495