Completed
Pull Request — master (#859)
by
unknown
04:25
created

SerializerBuilder::setAdvancedNamingStrategy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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