Completed
Push — master ( 068c5a...89419d )
by Asmir
21:45 queued 02:18
created

SerializerBuilder::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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