Completed
Push — master ( 751c6b...94b3c9 )
by Asmir
17:55 queued 14:19
created

SerializerBuilder::setDeserializationVisitor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
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\CachedReader;
22
use Doctrine\Common\Cache\FilesystemCache;
23
use JMS\Serializer\Builder\DefaultDriverFactory;
24
use JMS\Serializer\Builder\DriverFactoryInterface;
25
use JMS\Serializer\Handler\PhpCollectionHandler;
26
use JMS\Serializer\Handler\PropelCollectionHandler;
27
use JMS\Serializer\Exception\RuntimeException;
28
use Metadata\Driver\DriverInterface;
29
use Metadata\MetadataFactory;
30
use JMS\Serializer\Metadata\Driver\AnnotationDriver;
31
use JMS\Serializer\Handler\HandlerRegistry;
32
use JMS\Serializer\Construction\UnserializeObjectConstructor;
33
use PhpCollection\Map;
34
use JMS\Serializer\EventDispatcher\EventDispatcher;
35
use Metadata\Driver\DriverChain;
36
use JMS\Serializer\Metadata\Driver\YamlDriver;
37
use JMS\Serializer\Metadata\Driver\XmlDriver;
38
use Metadata\Driver\FileLocator;
39
use JMS\Serializer\Handler\DateHandler;
40
use JMS\Serializer\Handler\ArrayCollectionHandler;
41
use JMS\Serializer\Construction\ObjectConstructorInterface;
42
use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
43
use JMS\Serializer\Naming\CamelCaseNamingStrategy;
44
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
45
use Doctrine\Common\Annotations\Reader;
46
use Doctrine\Common\Annotations\AnnotationReader;
47
use Metadata\Cache\FileCache;
48
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy;
49
use JMS\Serializer\Exception\InvalidArgumentException;
50
51
/**
52
 * Builder for serializer instances.
53
 *
54
 * This object makes serializer construction a breeze for projects that do not use
55
 * any special dependency injection container.
56
 *
57
 * @author Johannes M. Schmitt <[email protected]>
58
 */
59
class SerializerBuilder
60
{
61
    private $metadataDirs = array();
62
    private $handlerRegistry;
63
    private $handlersConfigured = false;
64
    private $eventDispatcher;
65
    private $listenersConfigured = false;
66
    private $objectConstructor;
67
    private $serializationVisitors;
68
    private $deserializationVisitors;
69
    private $visitorsAdded = false;
70
    private $propertyNamingStrategy;
71
    private $debug = false;
72
    private $cacheDir;
73
    private $annotationReader;
74
    private $includeInterfaceMetadata = false;
75
    private $driverFactory;
76
77 13
    public static function create()
78
    {
79 13
        return new static();
80
    }
81
82 13
    public function __construct()
83
    {
84 13
        $this->handlerRegistry = new HandlerRegistry();
85 13
        $this->eventDispatcher = new EventDispatcher();
86 13
        $this->driverFactory = new DefaultDriverFactory();
87 13
        $this->serializationVisitors = new Map();
88 13
        $this->deserializationVisitors = new Map();
89 13
    }
90
91
    public function setAnnotationReader(Reader $reader)
92
    {
93
        $this->annotationReader = $reader;
94
95
        return $this;
96
    }
97
98
    public function setDebug($bool)
99
    {
100
        $this->debug = (boolean) $bool;
101
102
        return $this;
103
    }
104
105 1
    public function setCacheDir($dir)
106
    {
107 1
        if ( ! is_dir($dir)) {
108 1
            $this->createDir($dir);
109 1
        }
110 1
        if ( ! is_writable($dir)) {
111
            throw new InvalidArgumentException(sprintf('The cache directory "%s" is not writable.', $dir));
112
        }
113
114 1
        $this->cacheDir = $dir;
115
116 1
        return $this;
117
    }
118
119 12
    public function addDefaultHandlers()
120
    {
121 12
        $this->handlersConfigured = true;
122 12
        $this->handlerRegistry->registerSubscribingHandler(new DateHandler());
123 12
        $this->handlerRegistry->registerSubscribingHandler(new PhpCollectionHandler());
124 12
        $this->handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler());
125 12
        $this->handlerRegistry->registerSubscribingHandler(new PropelCollectionHandler());
126
127 12
        return $this;
128
    }
129
130 1
    public function configureHandlers(\Closure $closure)
131
    {
132 1
        $this->handlersConfigured = true;
133 1
        $closure($this->handlerRegistry);
134
135 1
        return $this;
136
    }
137
138 12
    public function addDefaultListeners()
139
    {
140 12
        $this->listenersConfigured = true;
141 12
        $this->eventDispatcher->addSubscriber(new DoctrineProxySubscriber());
142
143 12
        return $this;
144
    }
145
146 1
    public function configureListeners(\Closure $closure)
147
    {
148 1
        $this->listenersConfigured = true;
149 1
        $closure($this->eventDispatcher);
150
151 1
        return $this;
152
    }
153
154
    public function setObjectConstructor(ObjectConstructorInterface $constructor)
155
    {
156
        $this->objectConstructor = $constructor;
157
158
        return $this;
159
    }
160
161
    public function setPropertyNamingStrategy(PropertyNamingStrategyInterface $propertyNamingStrategy)
162
    {
163
        $this->propertyNamingStrategy = $propertyNamingStrategy;
164
165
        return $this;
166
    }
167
168 1
    public function setSerializationVisitor($format, VisitorInterface $visitor)
169
    {
170 1
        $this->visitorsAdded = true;
171 1
        $this->serializationVisitors->set($format, $visitor);
172
173 1
        return $this;
174
    }
175
176
    public function setDeserializationVisitor($format, VisitorInterface $visitor)
177
    {
178
        $this->visitorsAdded = true;
179
        $this->deserializationVisitors->set($format, $visitor);
180
181
        return $this;
182
    }
183
184 12
    public function addDefaultSerializationVisitors()
185
    {
186 12
        $this->initializePropertyNamingStrategy();
187
188 12
        $this->visitorsAdded = true;
189 12
        $this->serializationVisitors->setAll(array(
190 12
            'xml' => new XmlSerializationVisitor($this->propertyNamingStrategy),
191 12
            'yml' => new YamlSerializationVisitor($this->propertyNamingStrategy),
192 12
            'json' => new JsonSerializationVisitor($this->propertyNamingStrategy),
193 12
        ));
194
195 12
        return $this;
196
    }
197
198 12
    public function addDefaultDeserializationVisitors()
199
    {
200 12
        $this->initializePropertyNamingStrategy();
201
202 12
        $this->visitorsAdded = true;
203 12
        $this->deserializationVisitors->setAll(array(
204 12
            'xml' => new XmlDeserializationVisitor($this->propertyNamingStrategy),
205 12
            'json' => new JsonDeserializationVisitor($this->propertyNamingStrategy),
206 12
        ));
207
208 12
        return $this;
209
    }
210
211
    /**
212
     * @param Boolean $include Whether to include the metadata from the interfaces
213
     *
214
     * @return SerializerBuilder
215
     */
216 1
    public function includeInterfaceMetadata($include)
217
    {
218 1
        $this->includeInterfaceMetadata = (Boolean) $include;
219
220 1
        return $this;
221
    }
222
223
    /**
224
     * Sets a map of namespace prefixes to directories.
225
     *
226
     * This method overrides any previously defined directories.
227
     *
228
     * @param array<string,string> $namespacePrefixToDirMap
229
     *
230
     * @return SerializerBuilder
231
     *
232
     * @throws InvalidArgumentException When a directory does not exist
233
     */
234
    public function setMetadataDirs(array $namespacePrefixToDirMap)
235
    {
236
        foreach ($namespacePrefixToDirMap as $dir) {
237
            if ( ! is_dir($dir)) {
238
                throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
239
            }
240
        }
241
242
        $this->metadataDirs = $namespacePrefixToDirMap;
243
244
        return $this;
245
    }
246
247
    /**
248
     * Adds a directory where the serializer will look for class metadata.
249
     *
250
     * The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume
251
     * that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace.
252
     *
253
     * If you use an empty prefix, your metadata files would need to look like:
254
     *
255
     * ``my-dir/MyApplication.Entity.SomeObject.yml``
256
     * ``my-dir/MyApplication.Entity.OtherObject.xml``
257
     *
258
     * If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like:
259
     *
260
     * ``my-dir/SomeObject.yml``
261
     * ``my-dir/OtherObject.yml``
262
     *
263
     * Please keep in mind that you currently may only have one directory per namespace prefix.
264
     *
265
     * @param string $dir The directory where metadata files are located.
266
     * @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory.
267
     *
268
     * @return SerializerBuilder
269
     *
270
     * @throws InvalidArgumentException When a directory does not exist
271
     * @throws InvalidArgumentException When a directory has already been registered
272
     */
273
    public function addMetadataDir($dir, $namespacePrefix = '')
274
    {
275
        if ( ! is_dir($dir)) {
276
            throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
277
        }
278
279
        if (isset($this->metadataDirs[$namespacePrefix])) {
280
            throw new InvalidArgumentException(sprintf('There is already a directory configured for the namespace prefix "%s". Please use replaceMetadataDir() to override directories.', $namespacePrefix));
281
        }
282
283
        $this->metadataDirs[$namespacePrefix] = $dir;
284
285
        return $this;
286
    }
287
288
    /**
289
     * Adds a map of namespace prefixes to directories.
290
     *
291
     * @param array<string,string> $namespacePrefixToDirMap
292
     *
293
     * @return SerializerBuilder
294
     */
295
    public function addMetadataDirs(array $namespacePrefixToDirMap)
296
    {
297
        foreach ($namespacePrefixToDirMap as $prefix => $dir) {
298
            $this->addMetadataDir($dir, $prefix);
299
        }
300
301
        return $this;
302
    }
303
304
    /**
305
     * Similar to addMetadataDir(), but overrides an existing entry.
306
     *
307
     * @param string $dir
308
     * @param string $namespacePrefix
309
     *
310
     * @return SerializerBuilder
311
     *
312
     * @throws InvalidArgumentException When a directory does not exist
313
     * @throws InvalidArgumentException When no directory is configured for the ns prefix
314
     */
315
    public function replaceMetadataDir($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 no directory configured for namespace prefix "%s". Please use addMetadataDir() for adding new directories.', $namespacePrefix));
323
        }
324
325
        $this->metadataDirs[$namespacePrefix] = $dir;
326
327
        return $this;
328
    }
329
330 3
    public function setMetadataDriverFactory(DriverFactoryInterface $driverFactory)
331
    {
332 3
        $this->driverFactory = $driverFactory;
333
334 3
        return $this;
335
    }
336
337 13
    public function build()
338
    {
339 13
        $annotationReader = $this->annotationReader;
340 13
        if (null === $annotationReader) {
341 13
            $annotationReader = new AnnotationReader();
342
343 13
            if (null !== $this->cacheDir) {
344 1
                $this->createDir($this->cacheDir.'/annotations');
345 1
                $annotationsCache = new FilesystemCache($this->cacheDir.'/annotations');
346 1
                $annotationReader = new CachedReader($annotationReader, $annotationsCache, $this->debug);
347 1
            }
348 13
        }
349
350 13
        $metadataDriver = $this->driverFactory->createDriver($this->metadataDirs, $annotationReader);
351 13
        $metadataFactory = new MetadataFactory($metadataDriver, null, $this->debug);
352
353 13
        $metadataFactory->setIncludeInterfaces($this->includeInterfaceMetadata);
354
355 13
        if (null !== $this->cacheDir) {
356 1
            $this->createDir($this->cacheDir.'/metadata');
357 1
            $metadataFactory->setCache(new FileCache($this->cacheDir.'/metadata'));
358 1
        }
359
360 13
        if ( ! $this->handlersConfigured) {
361 11
            $this->addDefaultHandlers();
362 11
        }
363
364 13
        if ( ! $this->listenersConfigured) {
365 12
            $this->addDefaultListeners();
366 12
        }
367
368 13
        if ( ! $this->visitorsAdded) {
369 12
            $this->addDefaultSerializationVisitors();
370 12
            $this->addDefaultDeserializationVisitors();
371 12
        }
372
373 13
        return new Serializer(
374 13
            $metadataFactory,
375 13
            $this->handlerRegistry,
376 13
            $this->objectConstructor ?: new UnserializeObjectConstructor(),
377 13
            $this->serializationVisitors,
378 13
            $this->deserializationVisitors,
379 13
            $this->eventDispatcher
380 13
        );
381
    }
382
383 12
    private function initializePropertyNamingStrategy()
384
    {
385 12
        if (null !== $this->propertyNamingStrategy) {
386 12
            return;
387
        }
388
389 12
        $this->propertyNamingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy());
390 12
    }
391
392 1
    private function createDir($dir)
393
    {
394 1
        if (is_dir($dir)) {
395
            return;
396
        }
397
398 1
        if (false === @mkdir($dir, 0777, true)) {
399
            throw new RuntimeException(sprintf('Could not create directory "%s".', $dir));
400
        }
401 1
    }
402
}
403