1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* Copyright 2013 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 TechDivision\Import\Configuration\Jms\Serializer; |
20
|
|
|
|
21
|
|
|
use Metadata\Cache\FileCache; |
22
|
|
|
use Metadata\MetadataFactory; |
23
|
|
|
use Doctrine\Common\Annotations\AnnotationReader; |
24
|
|
|
use Doctrine\Common\Annotations\FileCacheReader; |
25
|
|
|
use JMS\Serializer\Exception\RuntimeException; |
26
|
|
|
use JMS\Serializer\Construction\UnserializeObjectConstructor; |
27
|
|
|
use JMS\Serializer\Naming\CamelCaseNamingStrategy; |
28
|
|
|
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy; |
29
|
|
|
use JMS\Serializer\Handler\HandlerRegistry; |
30
|
|
|
use JMS\Serializer\EventDispatcher\EventDispatcher; |
31
|
|
|
use JMS\Serializer\Builder\DefaultDriverFactory; |
32
|
|
|
use JMS\Serializer\Builder\DriverFactoryInterface; |
33
|
|
|
use JMS\Serializer\Handler\PhpCollectionHandler; |
34
|
|
|
use JMS\Serializer\Handler\PropelCollectionHandler; |
35
|
|
|
use PhpCollection\Map; |
36
|
|
|
use JMS\Serializer\Handler\DateHandler; |
37
|
|
|
use JMS\Serializer\Handler\ArrayCollectionHandler; |
38
|
|
|
use JMS\Serializer\Construction\ObjectConstructorInterface; |
39
|
|
|
use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber; |
40
|
|
|
use JMS\Serializer\Naming\PropertyNamingStrategyInterface; |
41
|
|
|
use Doctrine\Common\Annotations\Reader; |
42
|
|
|
use JMS\Serializer\Exception\InvalidArgumentException; |
43
|
|
|
use JMS\Serializer\XmlSerializationVisitor; |
44
|
|
|
use JMS\Serializer\YamlSerializationVisitor; |
45
|
|
|
use JMS\Serializer\JsonSerializationVisitor; |
46
|
|
|
use JMS\Serializer\XmlDeserializationVisitor; |
47
|
|
|
use JMS\Serializer\JsonDeserializationVisitor; |
48
|
|
|
use JMS\Serializer\VisitorInterface; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Builder for serializer instances. |
52
|
|
|
* |
53
|
|
|
* This object makes serializer construction a breeze for projects that do not use |
54
|
|
|
* any special dependency injection container. |
55
|
|
|
* |
56
|
|
|
* @author Johannes M. Schmitt <[email protected]> |
57
|
|
|
*/ |
58
|
|
|
class SerializerBuilder |
59
|
|
|
{ |
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
|
|
|
public static function create() |
77
|
|
|
{ |
78
|
|
|
return new static(); |
79
|
|
|
} |
80
|
|
|
public function __construct() |
81
|
|
|
{ |
82
|
|
|
$this->handlerRegistry = new HandlerRegistry(); |
83
|
|
|
$this->eventDispatcher = new EventDispatcher(); |
84
|
|
|
$this->driverFactory = new DefaultDriverFactory(); |
85
|
|
|
$this->serializationVisitors = new Map(); |
86
|
|
|
$this->deserializationVisitors = new Map(); |
87
|
|
|
} |
88
|
|
|
public function setAnnotationReader(Reader $reader) |
89
|
|
|
{ |
90
|
|
|
$this->annotationReader = $reader; |
91
|
|
|
return $this; |
92
|
|
|
} |
93
|
|
|
public function setDebug($bool) |
94
|
|
|
{ |
95
|
|
|
$this->debug = (boolean) $bool; |
96
|
|
|
return $this; |
97
|
|
|
} |
98
|
|
|
public function setCacheDir($dir) |
99
|
|
|
{ |
100
|
|
|
if ( ! is_dir($dir)) { |
101
|
|
|
$this->createDir($dir); |
102
|
|
|
} |
103
|
|
|
if ( ! is_writable($dir)) { |
104
|
|
|
throw new InvalidArgumentException(sprintf('The cache directory "%s" is not writable.', $dir)); |
105
|
|
|
} |
106
|
|
|
$this->cacheDir = $dir; |
107
|
|
|
return $this; |
108
|
|
|
} |
109
|
|
|
public function addDefaultHandlers() |
110
|
|
|
{ |
111
|
|
|
$this->handlersConfigured = true; |
112
|
|
|
$this->handlerRegistry->registerSubscribingHandler(new DateHandler()); |
113
|
|
|
$this->handlerRegistry->registerSubscribingHandler(new PhpCollectionHandler()); |
114
|
|
|
$this->handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler()); |
115
|
|
|
$this->handlerRegistry->registerSubscribingHandler(new PropelCollectionHandler()); |
116
|
|
|
return $this; |
117
|
|
|
} |
118
|
|
|
public function configureHandlers(\Closure $closure) |
119
|
|
|
{ |
120
|
|
|
$this->handlersConfigured = true; |
121
|
|
|
$closure($this->handlerRegistry); |
122
|
|
|
return $this; |
123
|
|
|
} |
124
|
|
|
public function addDefaultListeners() |
125
|
|
|
{ |
126
|
|
|
$this->listenersConfigured = true; |
127
|
|
|
$this->eventDispatcher->addSubscriber(new DoctrineProxySubscriber()); |
128
|
|
|
return $this; |
129
|
|
|
} |
130
|
|
|
public function configureListeners(\Closure $closure) |
131
|
|
|
{ |
132
|
|
|
$this->listenersConfigured = true; |
133
|
|
|
$closure($this->eventDispatcher); |
134
|
|
|
return $this; |
135
|
|
|
} |
136
|
|
|
public function setObjectConstructor(ObjectConstructorInterface $constructor) |
137
|
|
|
{ |
138
|
|
|
$this->objectConstructor = $constructor; |
139
|
|
|
return $this; |
140
|
|
|
} |
141
|
|
|
public function setPropertyNamingStrategy(PropertyNamingStrategyInterface $propertyNamingStrategy) |
142
|
|
|
{ |
143
|
|
|
$this->propertyNamingStrategy = $propertyNamingStrategy; |
144
|
|
|
return $this; |
145
|
|
|
} |
146
|
|
|
public function setSerializationVisitor($format, VisitorInterface $visitor) |
147
|
|
|
{ |
148
|
|
|
$this->visitorsAdded = true; |
149
|
|
|
$this->serializationVisitors->set($format, $visitor); |
150
|
|
|
return $this; |
151
|
|
|
} |
152
|
|
|
public function setDeserializationVisitor($format, VisitorInterface $visitor) |
153
|
|
|
{ |
154
|
|
|
$this->visitorsAdded = true; |
155
|
|
|
$this->deserializationVisitors->set($format, $visitor); |
156
|
|
|
return $this; |
157
|
|
|
} |
158
|
|
|
public function addDefaultSerializationVisitors() |
159
|
|
|
{ |
160
|
|
|
$this->initializePropertyNamingStrategy(); |
161
|
|
|
$this->visitorsAdded = true; |
162
|
|
|
$this->serializationVisitors->setAll(array( |
163
|
|
|
'xml' => new XmlSerializationVisitor($this->propertyNamingStrategy), |
164
|
|
|
'yml' => new YamlSerializationVisitor($this->propertyNamingStrategy), |
165
|
|
|
'json' => new JsonSerializationVisitor($this->propertyNamingStrategy), |
166
|
|
|
)); |
167
|
|
|
return $this; |
168
|
|
|
} |
169
|
|
|
public function addDefaultDeserializationVisitors() |
170
|
|
|
{ |
171
|
|
|
$this->initializePropertyNamingStrategy(); |
172
|
|
|
$this->visitorsAdded = true; |
173
|
|
|
$this->deserializationVisitors->setAll(array( |
174
|
|
|
'xml' => new XmlDeserializationVisitor($this->propertyNamingStrategy), |
175
|
|
|
'json' => new JsonDeserializationVisitor($this->propertyNamingStrategy), |
176
|
|
|
)); |
177
|
|
|
return $this; |
178
|
|
|
} |
179
|
|
|
/** |
180
|
|
|
* @param Boolean $include Whether to include the metadata from the interfaces |
181
|
|
|
* |
182
|
|
|
* @return SerializerBuilder |
183
|
|
|
*/ |
184
|
|
|
public function includeInterfaceMetadata($include) |
185
|
|
|
{ |
186
|
|
|
$this->includeInterfaceMetadata = (Boolean) $include; |
187
|
|
|
return $this; |
188
|
|
|
} |
189
|
|
|
/** |
190
|
|
|
* Sets a map of namespace prefixes to directories. |
191
|
|
|
* |
192
|
|
|
* This method overrides any previously defined directories. |
193
|
|
|
* |
194
|
|
|
* @param array<string,string> $namespacePrefixToDirMap |
195
|
|
|
* |
196
|
|
|
* @return SerializerBuilder |
197
|
|
|
* |
198
|
|
|
* @throws InvalidArgumentException When a directory does not exist |
199
|
|
|
*/ |
200
|
|
|
public function setMetadataDirs(array $namespacePrefixToDirMap) |
201
|
|
|
{ |
202
|
|
|
foreach ($namespacePrefixToDirMap as $dir) { |
203
|
|
|
if ( ! is_dir($dir)) { |
204
|
|
|
throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir)); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
$this->metadataDirs = $namespacePrefixToDirMap; |
208
|
|
|
return $this; |
209
|
|
|
} |
210
|
|
|
/** |
211
|
|
|
* Adds a directory where the serializer will look for class metadata. |
212
|
|
|
* |
213
|
|
|
* The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume |
214
|
|
|
* that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace. |
215
|
|
|
* |
216
|
|
|
* If you use an empty prefix, your metadata files would need to look like: |
217
|
|
|
* |
218
|
|
|
* ``my-dir/MyApplication.Entity.SomeObject.yml`` |
219
|
|
|
* ``my-dir/MyApplication.Entity.OtherObject.xml`` |
220
|
|
|
* |
221
|
|
|
* If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like: |
222
|
|
|
* |
223
|
|
|
* ``my-dir/SomeObject.yml`` |
224
|
|
|
* ``my-dir/OtherObject.yml`` |
225
|
|
|
* |
226
|
|
|
* Please keep in mind that you currently may only have one directory per namespace prefix. |
227
|
|
|
* |
228
|
|
|
* @param string $dir The directory where metadata files are located. |
229
|
|
|
* @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory. |
230
|
|
|
* |
231
|
|
|
* @return SerializerBuilder |
232
|
|
|
* |
233
|
|
|
* @throws InvalidArgumentException When a directory does not exist |
234
|
|
|
* @throws InvalidArgumentException When a directory has already been registered |
235
|
|
|
*/ |
236
|
|
View Code Duplication |
public function addMetadataDir($dir, $namespacePrefix = '') |
|
|
|
|
237
|
|
|
{ |
238
|
|
|
if ( ! is_dir($dir)) { |
239
|
|
|
throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir)); |
240
|
|
|
} |
241
|
|
|
if (isset($this->metadataDirs[$namespacePrefix])) { |
242
|
|
|
throw new InvalidArgumentException(sprintf('There is already a directory configured for the namespace prefix "%s". Please use replaceMetadataDir() to override directories.', $namespacePrefix)); |
243
|
|
|
} |
244
|
|
|
$this->metadataDirs[$namespacePrefix] = $dir; |
245
|
|
|
return $this; |
246
|
|
|
} |
247
|
|
|
/** |
248
|
|
|
* Adds a map of namespace prefixes to directories. |
249
|
|
|
* |
250
|
|
|
* @param array<string,string> $namespacePrefixToDirMap |
251
|
|
|
* |
252
|
|
|
* @return SerializerBuilder |
253
|
|
|
*/ |
254
|
|
|
public function addMetadataDirs(array $namespacePrefixToDirMap) |
255
|
|
|
{ |
256
|
|
|
foreach ($namespacePrefixToDirMap as $prefix => $dir) { |
257
|
|
|
$this->addMetadataDir($dir, $prefix); |
258
|
|
|
} |
259
|
|
|
return $this; |
260
|
|
|
} |
261
|
|
|
/** |
262
|
|
|
* Similar to addMetadataDir(), but overrides an existing entry. |
263
|
|
|
* |
264
|
|
|
* @param string $dir |
265
|
|
|
* @param string $namespacePrefix |
266
|
|
|
* |
267
|
|
|
* @return SerializerBuilder |
268
|
|
|
* |
269
|
|
|
* @throws InvalidArgumentException When a directory does not exist |
270
|
|
|
* @throws InvalidArgumentException When no directory is configured for the ns prefix |
271
|
|
|
*/ |
272
|
|
View Code Duplication |
public function replaceMetadataDir($dir, $namespacePrefix = '') |
|
|
|
|
273
|
|
|
{ |
274
|
|
|
if ( ! is_dir($dir)) { |
275
|
|
|
throw new InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir)); |
276
|
|
|
} |
277
|
|
|
if ( ! isset($this->metadataDirs[$namespacePrefix])) { |
278
|
|
|
throw new InvalidArgumentException(sprintf('There is no directory configured for namespace prefix "%s". Please use addMetadataDir() for adding new directories.', $namespacePrefix)); |
279
|
|
|
} |
280
|
|
|
$this->metadataDirs[$namespacePrefix] = $dir; |
281
|
|
|
return $this; |
282
|
|
|
} |
283
|
|
|
public function setMetadataDriverFactory(DriverFactoryInterface $driverFactory) |
284
|
|
|
{ |
285
|
|
|
$this->driverFactory = $driverFactory; |
286
|
|
|
} |
287
|
|
|
public function build() |
288
|
|
|
{ |
289
|
|
|
$annotationReader = $this->annotationReader; |
290
|
|
|
if (null === $annotationReader) { |
291
|
|
|
$annotationReader = new AnnotationReader(); |
292
|
|
|
if (null !== $this->cacheDir) { |
293
|
|
|
$this->createDir($this->cacheDir.'/annotations'); |
294
|
|
|
$annotationReader = new FileCacheReader($annotationReader, $this->cacheDir.'/annotations', $this->debug); |
|
|
|
|
295
|
|
|
} |
296
|
|
|
} |
297
|
|
|
$metadataDriver = $this->driverFactory->createDriver($this->metadataDirs, $annotationReader); |
298
|
|
|
$metadataFactory = new MetadataFactory($metadataDriver, null, $this->debug); |
299
|
|
|
$metadataFactory->setIncludeInterfaces($this->includeInterfaceMetadata); |
300
|
|
|
if (null !== $this->cacheDir) { |
301
|
|
|
$this->createDir($this->cacheDir.'/metadata'); |
302
|
|
|
$metadataFactory->setCache(new FileCache($this->cacheDir.'/metadata')); |
303
|
|
|
} |
304
|
|
|
if ( ! $this->handlersConfigured) { |
305
|
|
|
$this->addDefaultHandlers(); |
306
|
|
|
} |
307
|
|
|
if ( ! $this->listenersConfigured) { |
308
|
|
|
$this->addDefaultListeners(); |
309
|
|
|
} |
310
|
|
|
if ( ! $this->visitorsAdded) { |
311
|
|
|
$this->addDefaultSerializationVisitors(); |
312
|
|
|
$this->addDefaultDeserializationVisitors(); |
313
|
|
|
} |
314
|
|
|
return new Serializer( |
315
|
|
|
$metadataFactory, |
316
|
|
|
$this->handlerRegistry, |
317
|
|
|
$this->objectConstructor ?: new UnserializeObjectConstructor(), |
318
|
|
|
$this->serializationVisitors, |
319
|
|
|
$this->deserializationVisitors, |
320
|
|
|
$this->eventDispatcher |
321
|
|
|
); |
322
|
|
|
} |
323
|
|
|
private function initializePropertyNamingStrategy() |
324
|
|
|
{ |
325
|
|
|
if (null !== $this->propertyNamingStrategy) { |
326
|
|
|
return; |
327
|
|
|
} |
328
|
|
|
$this->propertyNamingStrategy = new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy()); |
329
|
|
|
} |
330
|
|
|
private function createDir($dir) |
331
|
|
|
{ |
332
|
|
|
if (is_dir($dir)) { |
333
|
|
|
return; |
334
|
|
|
} |
335
|
|
|
if (false === @mkdir($dir, 0777, true)) { |
336
|
|
|
throw new RuntimeException(sprintf('Could not create directory "%s".', $dir)); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.