Completed
Pull Request — master (#743)
by Asmir
03:52
created

initializePropertyNamingStrategy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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