Completed
Pull Request — master (#934)
by Asmir
04:07 queued 01:19
created

SerializerBuilder::setDebug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 0
cts 3
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\MetadataFactory;
65
use Metadata\MetadataFactoryInterface;
66
67
/**
68
 * Builder for serializer instances.
69
 *
70
 * This object makes serializer construction a breeze for projects that do not use
71
 * any special dependency injection container.
72
 *
73
 * @author Johannes M. Schmitt <[email protected]>
74
 */
75
final class SerializerBuilder
76
{
77
    private $metadataDirs = [];
78
    private $handlerRegistry;
79
    private $handlersConfigured = false;
80
    private $eventDispatcher;
81
    private $listenersConfigured = false;
82
    private $objectConstructor;
83
    private $serializationVisitors;
84
    private $deserializationVisitors;
85
    private $visitorsAdded = false;
86
    private $propertyNamingStrategy;
87
    private $debug = false;
88
    private $cacheDir;
89
    private $annotationReader;
90
    private $includeInterfaceMetadata = false;
91
    private $driverFactory;
92
    private $serializationContextFactory;
93
    private $deserializationContextFactory;
94
    private $typeParser;
95
96
    /**
97
     * @var ExpressionEvaluatorInterface
98
     */
99
    private $expressionEvaluator;
100
101
    /**
102
     * @var AccessorStrategyInterface
103
     */
104
    private $accessorStrategy;
105
106 317
    public static function create(...$args)
107
    {
108 317
        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

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