Completed
Pull Request — master (#708)
by Asmir
11:37
created

SerializerBuilder::getAccessorStrategy()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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