Completed
Pull Request — master (#645)
by Asmir
03:59
created

SerializerBuilder   F

Complexity

Total Complexity 51

Size/Duplication

Total Lines 396
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 28

Test Coverage

Coverage 71.75%

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 28
dl 0
loc 396
ccs 127
cts 177
cp 0.7175
rs 2.5647
c 0
b 0
f 0

26 Methods

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

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