Completed
Push — master ( f00989...53d778 )
by Asmir
06:08 queued 03:18
created

SerializerBuilder::setDeserializationVisitor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
namespace JMS\Serializer;
22
23
use Doctrine\Common\Annotations\AnnotationReader;
24
use Doctrine\Common\Annotations\CachedReader;
25
use Doctrine\Common\Annotations\Reader;
26
use Doctrine\Common\Cache\FilesystemCache;
27
use JMS\Serializer\Accessor\AccessorStrategyInterface;
28
use JMS\Serializer\Accessor\DefaultAccessorStrategy;
29
use JMS\Serializer\Accessor\ExpressionAccessorStrategy;
0 ignored issues
show
Bug introduced by
The type JMS\Serializer\Accessor\ExpressionAccessorStrategy was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
use JMS\Serializer\Builder\DefaultDriverFactory;
31
use JMS\Serializer\Builder\DriverFactoryInterface;
32
use JMS\Serializer\Construction\ObjectConstructorInterface;
33
use JMS\Serializer\Construction\UnserializeObjectConstructor;
34
use JMS\Serializer\ContextFactory\CallableDeserializationContextFactory;
35
use JMS\Serializer\ContextFactory\CallableSerializationContextFactory;
36
use JMS\Serializer\ContextFactory\DeserializationContextFactoryInterface;
37
use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface;
38
use JMS\Serializer\EventDispatcher\EventDispatcher;
39
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
40
use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
41
use JMS\Serializer\Exception\InvalidArgumentException;
42
use JMS\Serializer\Exception\RuntimeException;
43
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
44
use JMS\Serializer\GraphNavigator\Factory\DeserializationGraphNavigatorFactory;
45
use JMS\Serializer\GraphNavigator\Factory\GraphNavigatorFactoryInterface;
46
use JMS\Serializer\GraphNavigator\Factory\SerializationGraphNavigatorFactory;
47
use JMS\Serializer\Handler\ArrayCollectionHandler;
48
use JMS\Serializer\Handler\DateHandler;
49
use JMS\Serializer\Handler\HandlerRegistry;
50
use JMS\Serializer\Handler\HandlerRegistryInterface;
51
use JMS\Serializer\Handler\StdClassHandler;
52
use JMS\Serializer\Naming\CamelCaseNamingStrategy;
53
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
54
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy;
55
use JMS\Serializer\Type\Parser;
56
use JMS\Serializer\Type\ParserInterface;
57
use JMS\Serializer\Visitor\Factory\DeserializationVisitorFactory;
58
use JMS\Serializer\Visitor\Factory\JsonDeserializationVisitorFactory;
59
use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory;
60
use JMS\Serializer\Visitor\Factory\SerializationVisitorFactory;
61
use JMS\Serializer\Visitor\Factory\XmlDeserializationVisitorFactory;
62
use JMS\Serializer\Visitor\Factory\XmlSerializationVisitorFactory;
63
use Metadata\Cache\FileCache;
64
use Metadata\Cache\CacheInterface;
65
use Metadata\MetadataFactory;
66
use Metadata\MetadataFactoryInterface;
67
68
/**
69
 * Builder for serializer instances.
70
 *
71
 * This object makes serializer construction a breeze for projects that do not use
72
 * any special dependency injection container.
73
 *
74
 * @author Johannes M. Schmitt <[email protected]>
75
 */
76
final class SerializerBuilder
77
{
78
    private $metadataDirs = [];
79
    private $handlerRegistry;
80
    private $handlersConfigured = false;
81
    private $eventDispatcher;
82
    private $listenersConfigured = false;
83
    private $objectConstructor;
84
    private $serializationVisitors;
85
    private $deserializationVisitors;
86
    private $visitorsAdded = false;
87
    private $propertyNamingStrategy;
88
    private $debug = false;
89
    private $cacheDir;
90
    private $annotationReader;
91
    private $includeInterfaceMetadata = false;
92
    private $driverFactory;
93
    private $serializationContextFactory;
94
    private $deserializationContextFactory;
95
    private $typeParser;
96
97
    /**
98
     * @var ExpressionEvaluatorInterface
99
     */
100
    private $expressionEvaluator;
101
102
    /**
103
     * @var AccessorStrategyInterface
104
     */
105
    private $accessorStrategy;
106
107
    /**
108
     * @var CacheInterface
109
     */
110
    private $metadataCache;
111
112 323
    public static function create(...$args)
113
    {
114 323
        return new static(...$args);
0 ignored issues
show
Bug introduced by
$args is expanded, but the parameter $handlerRegistry of JMS\Serializer\SerializerBuilder::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

114
        return new static(/** @scrutinizer ignore-type */ ...$args);
Loading history...
115
    }
116
117 323
    public function __construct(HandlerRegistryInterface $handlerRegistry = null, EventDispatcherInterface $eventDispatcher = null)
118
    {
119 323
        $this->typeParser = new Parser();
120 323
        $this->handlerRegistry = $handlerRegistry ?: new HandlerRegistry();
121 323
        $this->eventDispatcher = $eventDispatcher ?: new EventDispatcher();
122 323
        $this->serializationVisitors = [];
123 323
        $this->deserializationVisitors = [];
124
125 323
        if ($handlerRegistry) {
126 278
            $this->handlersConfigured = true;
127
        }
128 323
        if ($eventDispatcher) {
129 278
            $this->listenersConfigured = true;
130
        }
131 323
    }
132
133
    public function setAccessorStrategy(AccessorStrategyInterface $accessorStrategy): self
134
    {
135
        $this->accessorStrategy = $accessorStrategy;
136
        return $this;
137
    }
138
139 323
    private function getAccessorStrategy(): AccessorStrategyInterface
140
    {
141 323
        if (!$this->accessorStrategy) {
142 323
            $this->accessorStrategy = new DefaultAccessorStrategy($this->expressionEvaluator);
143
        }
144 323
        return $this->accessorStrategy;
145
    }
146
147 23
    public function setExpressionEvaluator(ExpressionEvaluatorInterface $expressionEvaluator): self
148
    {
149 23
        $this->expressionEvaluator = $expressionEvaluator;
150
151 23
        return $this;
152
    }
153
154 1
    public function setTypeParser(ParserInterface $parser): self
155
    {
156 1
        $this->typeParser = $parser;
157
158 1
        return $this;
159
    }
160
161
    public function setAnnotationReader(Reader $reader): self
162
    {
163
        $this->annotationReader = $reader;
164
165
        return $this;
166
    }
167
168
    public function setDebug(bool $bool): self
169
    {
170
        $this->debug = $bool;
171
172
        return $this;
173
    }
174
175 1
    public function setCacheDir(string $dir): self
176
    {
177 1
        if (!is_dir($dir)) {
178 1
            $this->createDir($dir);
179
        }
180 1
        if (!is_writable($dir)) {
181
            throw new InvalidArgumentException(sprintf('The cache directory "%s" is not writable.', $dir));
182
        }
183
184 1
        $this->cacheDir = $dir;
185
186 1
        return $this;
187
    }
188
189 68
    public function addDefaultHandlers(): self
190
    {
191 68
        $this->handlersConfigured = true;
192 68
        $this->handlerRegistry->registerSubscribingHandler(new DateHandler());
193 68
        $this->handlerRegistry->registerSubscribingHandler(new StdClassHandler());
194 68
        $this->handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler());
195
196 68
        return $this;
197
    }
198
199 3
    public function configureHandlers(\Closure $closure): self
200
    {
201 3
        $this->handlersConfigured = true;
202 3
        $closure($this->handlerRegistry);
203
204 3
        return $this;
205
    }
206
207 70
    public function addDefaultListeners(): self
208
    {
209 70
        $this->listenersConfigured = true;
210 70
        $this->eventDispatcher->addSubscriber(new DoctrineProxySubscriber());
211
212 70
        return $this;
213
    }
214
215 1
    public function configureListeners(\Closure $closure): self
216
    {
217 1
        $this->listenersConfigured = true;
218 1
        $closure($this->eventDispatcher);
219
220 1
        return $this;
221
    }
222
223 3
    public function setObjectConstructor(ObjectConstructorInterface $constructor): self
224
    {
225 3
        $this->objectConstructor = $constructor;
226
227 3
        return $this;
228
    }
229
230
    public function setPropertyNamingStrategy(PropertyNamingStrategyInterface $propertyNamingStrategy): self
231
    {
232
        $this->propertyNamingStrategy = $propertyNamingStrategy;
233
234
        return $this;
235
    }
236
237 2
    public function setSerializationVisitor($format, SerializationVisitorFactory $visitor): self
238
    {
239 2
        $this->visitorsAdded = true;
240 2
        $this->serializationVisitors[$format] = $visitor;
241
242 2
        return $this;
243
    }
244
245
    public function setDeserializationVisitor($format, DeserializationVisitorFactory $visitor): self
246
    {
247
        $this->visitorsAdded = true;
248
        $this->deserializationVisitors[$format] = $visitor;
249
250
        return $this;
251
    }
252
253 322
    public function addDefaultSerializationVisitors(): self
254
    {
255 322
        $this->visitorsAdded = true;
256 322
        $this->serializationVisitors = [
257 322
            'xml' => new XmlSerializationVisitorFactory(),
258 322
            'json' => new JsonSerializationVisitorFactory(),
259
        ];
260
261 322
        return $this;
262
    }
263
264 322
    public function addDefaultDeserializationVisitors(): self
265
    {
266 322
        $this->visitorsAdded = true;
267 322
        $this->deserializationVisitors = [
268 322
            'xml' => new XmlDeserializationVisitorFactory(),
269 322
            'json' => new JsonDeserializationVisitorFactory(),
270
        ];
271
272 322
        return $this;
273
    }
274
275
    /**
276
     * @param Boolean $include Whether to include the metadata from the interfaces
277
     *
278
     * @return SerializerBuilder
279
     */
280 1
    public function includeInterfaceMetadata(bool $include): self
281
    {
282 1
        $this->includeInterfaceMetadata = $include;
283
284 1
        return $this;
285
    }
286
287
    /**
288
     * Sets a map of namespace prefixes to directories.
289
     *
290
     * This method overrides any previously defined directories.
291
     *
292
     * @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...
293
     *
294
     * @return SerializerBuilder
295
     *
296
     * @throws InvalidArgumentException When a directory does not exist
297
     */
298
    public function setMetadataDirs(array $namespacePrefixToDirMap): self
299
    {
300
        foreach ($namespacePrefixToDirMap as $dir) {
301
            if (!is_dir($dir)) {
302
                throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
303
            }
304
        }
305
306
        $this->metadataDirs = $namespacePrefixToDirMap;
307
308
        return $this;
309
    }
310
311
    /**
312
     * Adds a directory where the serializer will look for class metadata.
313
     *
314
     * The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume
315
     * that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace.
316
     *
317
     * If you use an empty prefix, your metadata files would need to look like:
318
     *
319
     * ``my-dir/MyApplication.Entity.SomeObject.yml``
320
     * ``my-dir/MyApplication.Entity.OtherObject.xml``
321
     *
322
     * If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like:
323
     *
324
     * ``my-dir/SomeObject.yml``
325
     * ``my-dir/OtherObject.yml``
326
     *
327
     * Please keep in mind that you currently may only have one directory per namespace prefix.
328
     *
329
     * @param string $dir The directory where metadata files are located.
330
     * @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory.
331
     *
332
     * @return SerializerBuilder
333
     *
334
     * @throws InvalidArgumentException When a directory does not exist
335
     * @throws InvalidArgumentException When a directory has already been registered
336
     */
337
    public function addMetadataDir(string $dir, string $namespacePrefix = ''): self
338
    {
339
        if (!is_dir($dir)) {
340
            throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
341
        }
342
343
        if (isset($this->metadataDirs[$namespacePrefix])) {
344
            throw new InvalidArgumentException(sprintf('There is already a directory configured for the namespace prefix "%s". Please use replaceMetadataDir() to override directories.', $namespacePrefix));
345
        }
346
347
        $this->metadataDirs[$namespacePrefix] = $dir;
348
349
        return $this;
350
    }
351
352
    /**
353
     * Adds a map of namespace prefixes to directories.
354
     *
355
     * @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...
356
     *
357
     * @return SerializerBuilder
358
     */
359
    public function addMetadataDirs(array $namespacePrefixToDirMap): self
360
    {
361
        foreach ($namespacePrefixToDirMap as $prefix => $dir) {
362
            $this->addMetadataDir($dir, $prefix);
363
        }
364
365
        return $this;
366
    }
367
368
    /**
369
     * Similar to addMetadataDir(), but overrides an existing entry.
370
     *
371
     * @param string $dir
372
     * @param string $namespacePrefix
373
     *
374
     * @return SerializerBuilder
375
     *
376
     * @throws InvalidArgumentException When a directory does not exist
377
     * @throws InvalidArgumentException When no directory is configured for the ns prefix
378
     */
379
    public function replaceMetadataDir(string $dir, $namespacePrefix = ''): self
380
    {
381
        if (!is_dir($dir)) {
382
            throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
383
        }
384
385
        if (!isset($this->metadataDirs[$namespacePrefix])) {
386
            throw new InvalidArgumentException(sprintf('There is no directory configured for namespace prefix "%s". Please use addMetadataDir() for adding new directories.', $namespacePrefix));
387
        }
388
389
        $this->metadataDirs[$namespacePrefix] = $dir;
390
391
        return $this;
392
    }
393
394 12
    public function setMetadataDriverFactory(DriverFactoryInterface $driverFactory): self
395
    {
396 12
        $this->driverFactory = $driverFactory;
397
398 12
        return $this;
399
    }
400
401
    /**
402
     * @param SerializationContextFactoryInterface|callable $serializationContextFactory
403
     *
404
     * @return self
405
     */
406 5
    public function setSerializationContextFactory($serializationContextFactory): self
407
    {
408 5
        if ($serializationContextFactory instanceof SerializationContextFactoryInterface) {
409 3
            $this->serializationContextFactory = $serializationContextFactory;
410 2
        } elseif (is_callable($serializationContextFactory)) {
411 2
            $this->serializationContextFactory = new CallableSerializationContextFactory(
412 2
                $serializationContextFactory
413
            );
414
        } else {
415
            throw new InvalidArgumentException('expected SerializationContextFactoryInterface or callable.');
416
        }
417
418 5
        return $this;
419
    }
420
421
    /**
422
     * @param DeserializationContextFactoryInterface|callable $deserializationContextFactory
423
     *
424
     * @return self
425
     */
426 3
    public function setDeserializationContextFactory($deserializationContextFactory): self
427
    {
428 3
        if ($deserializationContextFactory instanceof DeserializationContextFactoryInterface) {
429 3
            $this->deserializationContextFactory = $deserializationContextFactory;
430
        } elseif (is_callable($deserializationContextFactory)) {
431
            $this->deserializationContextFactory = new CallableDeserializationContextFactory(
432
                $deserializationContextFactory
433
            );
434
        } else {
435
            throw new InvalidArgumentException('expected DeserializationContextFactoryInterface or callable.');
436
        }
437
438 3
        return $this;
439
    }
440
441
    public function setMetadataCache(CacheInterface $cache)
442
    {
443
        $this->metadataCache = $cache;
444
    }
445
446 323
    public function build(): SerializerInterface
447
    {
448 323
        $annotationReader = $this->annotationReader;
449 323
        if (null === $annotationReader) {
450 323
            $annotationReader = new AnnotationReader();
451
452 323
            if (null !== $this->cacheDir) {
453 1
                $this->createDir($this->cacheDir . '/annotations');
454 1
                $annotationsCache = new FilesystemCache($this->cacheDir . '/annotations');
455 1
                $annotationReader = new CachedReader($annotationReader, $annotationsCache, $this->debug);
456
            }
457
        }
458
459 323
        if (null === $this->driverFactory) {
460 311
            $this->initializePropertyNamingStrategy();
461 311
            $this->driverFactory = new DefaultDriverFactory($this->propertyNamingStrategy, $this->typeParser);
462
        }
463
464 323
        $metadataDriver = $this->driverFactory->createDriver($this->metadataDirs, $annotationReader);
465 323
        $metadataFactory = new MetadataFactory($metadataDriver, null, $this->debug);
466
467 323
        $metadataFactory->setIncludeInterfaces($this->includeInterfaceMetadata);
468
469 323
        if ($this->metadataCache !== null) {
470
            $metadataFactory->setCache($this->metadataCache);
471 323
        } elseif (null !== $this->cacheDir) {
472 1
            $this->createDir($this->cacheDir . '/metadata');
473 1
            $metadataFactory->setCache(new FileCache($this->cacheDir . '/metadata'));
474
        }
475
476 323
        if (!$this->handlersConfigured) {
477 68
            $this->addDefaultHandlers();
478
        }
479
480 323
        if (!$this->listenersConfigured) {
481 70
            $this->addDefaultListeners();
482
        }
483
484 323
        if (!$this->visitorsAdded) {
485 322
            $this->addDefaultSerializationVisitors();
486 322
            $this->addDefaultDeserializationVisitors();
487
        }
488
        $navigatorFactories = [
489 323
            GraphNavigatorInterface::DIRECTION_SERIALIZATION => $this->getSerializationNavigatorFactory($metadataFactory),
490 323
            GraphNavigatorInterface::DIRECTION_DESERIALIZATION => $this->getDeserializationNavigatorFactory($metadataFactory),
491
        ];
492
493 323
        $serializer = new Serializer(
494 323
            $metadataFactory,
495 323
            $navigatorFactories,
496 323
            $this->serializationVisitors,
497 323
            $this->deserializationVisitors,
498 323
            $this->serializationContextFactory,
499 323
            $this->deserializationContextFactory,
500 323
            $this->typeParser
501
        );
502
503 323
        return $serializer;
504
    }
505
506 323
    private function getSerializationNavigatorFactory(MetadataFactoryInterface $metadataFactory): GraphNavigatorFactoryInterface
507
    {
508 323
        return new SerializationGraphNavigatorFactory(
509 323
            $metadataFactory,
510 323
            $this->handlerRegistry,
511 323
            $this->getAccessorStrategy(),
512 323
            $this->eventDispatcher,
513 323
            $this->expressionEvaluator
514
        );
515
    }
516
517 323
    private function getDeserializationNavigatorFactory(MetadataFactoryInterface $metadataFactory): GraphNavigatorFactoryInterface
518
    {
519 323
        return new DeserializationGraphNavigatorFactory(
520 323
            $metadataFactory,
521 323
            $this->handlerRegistry,
522 323
            $this->objectConstructor ?: new UnserializeObjectConstructor(),
523 323
            $this->getAccessorStrategy(),
524 323
            $this->eventDispatcher,
525 323
            $this->expressionEvaluator
526
        );
527
    }
528
529 311
    private function initializePropertyNamingStrategy()
530
    {
531 311
        if (null !== $this->propertyNamingStrategy) {
532
            return;
533
        }
534
535 311
        $this->propertyNamingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy());
536 311
    }
537
538 1
    private function createDir($dir)
539
    {
540 1
        if (is_dir($dir)) {
541
            return;
542
        }
543
544 1
        if (false === @mkdir($dir, 0777, true) && false === is_dir($dir)) {
545
            throw new RuntimeException(sprintf('Could not create directory "%s".', $dir));
546
        }
547 1
    }
548
}
549
550