Passed
Branch master (bf85d9)
by Johannes
05:40
created

SerializerBuilder::setTypeParser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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