Completed
Pull Request — master (#859)
by
unknown
05:02
created

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