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\Metadata\Driver; |
20
|
|
|
|
21
|
|
|
use JMS\Serializer\Accessor\Updater\ClassAccessorUpdater; |
22
|
|
|
use JMS\Serializer\Annotation\ExclusionPolicy; |
23
|
|
|
use JMS\Serializer\Exception\RuntimeException; |
24
|
|
|
use JMS\Serializer\GraphNavigator; |
25
|
|
|
use JMS\Serializer\Metadata\ClassMetadata; |
26
|
|
|
use JMS\Serializer\Metadata\ExpressionPropertyMetadata; |
27
|
|
|
use JMS\Serializer\Metadata\PropertyMetadata; |
28
|
|
|
use JMS\Serializer\Metadata\ClassMetadataUpdaterInterface; |
29
|
|
|
use JMS\Serializer\Metadata\VirtualPropertyMetadata; |
30
|
|
|
use Metadata\Driver\AbstractFileDriver; |
31
|
|
|
use Metadata\Driver\FileLocatorInterface; |
32
|
|
|
use Metadata\MethodMetadata; |
33
|
|
|
use Symfony\Component\Yaml\Yaml; |
34
|
|
|
|
35
|
|
|
class YamlDriver extends AbstractFileDriver |
36
|
|
|
{ |
37
|
|
|
/** |
38
|
|
|
* @var ClassMetadataUpdaterInterface |
39
|
|
|
*/ |
40
|
|
|
private $propertyUpdater; |
41
|
|
|
|
42
|
29 |
|
public function __construct(FileLocatorInterface $locator, ClassMetadataUpdaterInterface $classMetadataUpdater = null) |
43
|
|
|
{ |
44
|
29 |
|
parent::__construct($locator); |
45
|
29 |
|
$this->propertyUpdater = $classMetadataUpdater ?: new ClassAccessorUpdater(); |
46
|
29 |
|
} |
47
|
|
|
|
48
|
|
|
|
49
|
29 |
|
protected function loadMetadataFromFile(\ReflectionClass $class, $file) |
50
|
|
|
{ |
51
|
29 |
|
$config = Yaml::parse(file_get_contents($file)); |
52
|
|
|
|
53
|
29 |
|
if (!isset($config[$name = $class->name])) { |
54
|
|
|
throw new RuntimeException(sprintf('Expected metadata for class %s to be defined in %s.', $class->name, $file)); |
55
|
|
|
} |
56
|
|
|
|
57
|
29 |
|
$config = $config[$name]; |
58
|
29 |
|
$metadata = new ClassMetadata($name); |
59
|
29 |
|
$metadata->fileResources[] = $file; |
60
|
29 |
|
$metadata->fileResources[] = $class->getFileName(); |
61
|
29 |
|
$exclusionPolicy = isset($config['exclusion_policy']) ? strtoupper($config['exclusion_policy']) : 'NONE'; |
62
|
29 |
|
$excludeAll = isset($config['exclude']) ? (Boolean)$config['exclude'] : false; |
63
|
29 |
|
$metadata->accessType = isset($config['access_type']) ? $config['access_type'] : null; |
64
|
29 |
|
$metadata->accessTypeNaming = isset($config['access_type_naming']) ? $config['access_type_naming'] : null; |
65
|
29 |
|
$readOnlyClass = isset($config['read_only']) ? (Boolean)$config['read_only'] : false; |
66
|
29 |
|
$this->addClassProperties($metadata, $config); |
67
|
|
|
|
68
|
29 |
|
$propertiesMetadata = array(); |
69
|
29 |
|
if (array_key_exists('virtual_properties', $config)) { |
70
|
4 |
|
foreach ($config['virtual_properties'] as $methodName => $propertySettings) { |
71
|
4 |
|
if (isset($propertySettings['exp'])) { |
72
|
2 |
|
$virtualPropertyMetadata = new ExpressionPropertyMetadata($name, $methodName, $propertySettings['exp']); |
73
|
2 |
|
unset($propertySettings['exp']); |
74
|
|
|
|
75
|
|
|
} else { |
76
|
|
|
|
77
|
3 |
|
if (!$class->hasMethod($methodName)) { |
78
|
|
|
throw new RuntimeException('The method ' . $methodName . ' not found in class ' . $class->name); |
79
|
|
|
} |
80
|
3 |
|
$virtualPropertyMetadata = new VirtualPropertyMetadata($name, $methodName); |
81
|
|
|
} |
82
|
4 |
|
$propertiesMetadata[$methodName] = $virtualPropertyMetadata; |
83
|
4 |
|
$config['properties'][$methodName] = $propertySettings; |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
29 |
|
if (!$excludeAll) { |
88
|
29 |
|
foreach ($class->getProperties() as $property) { |
89
|
25 |
|
if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) { |
|
|
|
|
90
|
2 |
|
continue; |
91
|
|
|
} |
92
|
|
|
|
93
|
24 |
|
$pName = $property->getName(); |
94
|
24 |
|
$propertiesMetadata[$pName] = new PropertyMetadata($name, $pName); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** @var PropertyMetadata $pMetadata */ |
98
|
29 |
|
foreach ($propertiesMetadata as $pName => $pMetadata) { |
99
|
26 |
|
$isExclude = false; |
100
|
26 |
|
$isExpose = $pMetadata instanceof VirtualPropertyMetadata |
101
|
25 |
|
|| $pMetadata instanceof ExpressionPropertyMetadata |
102
|
26 |
|
|| (isset($config['properties']) && array_key_exists($pName, $config['properties'])); |
103
|
|
|
|
104
|
26 |
|
if (isset($config['properties'][$pName])) { |
105
|
23 |
|
$pConfig = $config['properties'][$pName]; |
106
|
|
|
|
107
|
23 |
|
if (isset($pConfig['exclude'])) { |
108
|
3 |
|
$isExclude = (Boolean)$pConfig['exclude']; |
109
|
|
|
} |
110
|
|
|
|
111
|
23 |
|
if ($isExclude) { |
112
|
3 |
|
continue; |
113
|
|
|
} |
114
|
|
|
|
115
|
22 |
|
if (isset($pConfig['expose'])) { |
116
|
3 |
|
$isExpose = (Boolean)$pConfig['expose']; |
117
|
|
|
} |
118
|
|
|
|
119
|
22 |
|
if (isset($pConfig['skip_when_empty'])) { |
120
|
1 |
|
$pMetadata->skipWhenEmpty = (Boolean)$pConfig['skip_when_empty']; |
121
|
|
|
} |
122
|
|
|
|
123
|
22 |
|
if (isset($pConfig['since_version'])) { |
124
|
|
|
$pMetadata->sinceVersion = (string)$pConfig['since_version']; |
125
|
|
|
} |
126
|
|
|
|
127
|
22 |
|
if (isset($pConfig['until_version'])) { |
128
|
|
|
$pMetadata->untilVersion = (string)$pConfig['until_version']; |
129
|
|
|
} |
130
|
|
|
|
131
|
22 |
|
if (isset($pConfig['exclude_if'])) { |
132
|
2 |
|
$pMetadata->excludeIf = (string)$pConfig['exclude_if']; |
133
|
|
|
} |
134
|
|
|
|
135
|
22 |
|
if (isset($pConfig['expose_if'])) { |
136
|
2 |
|
$pMetadata->excludeIf = "!(" . $pConfig['expose_if'] . ")"; |
137
|
|
|
} |
138
|
|
|
|
139
|
22 |
|
if (isset($pConfig['serialized_name'])) { |
140
|
3 |
|
$pMetadata->serializedName = (string)$pConfig['serialized_name']; |
141
|
|
|
} |
142
|
|
|
|
143
|
22 |
|
if (isset($pConfig['type'])) { |
144
|
16 |
|
$pMetadata->setType((string)$pConfig['type']); |
145
|
|
|
} |
146
|
|
|
|
147
|
22 |
|
if (isset($pConfig['groups'])) { |
148
|
1 |
|
$pMetadata->groups = $pConfig['groups']; |
149
|
|
|
} |
150
|
|
|
|
151
|
22 |
|
if (isset($pConfig['xml_list'])) { |
152
|
2 |
|
$pMetadata->xmlCollection = true; |
153
|
|
|
|
154
|
2 |
|
$colConfig = $pConfig['xml_list']; |
155
|
2 |
|
if (isset($colConfig['inline'])) { |
156
|
2 |
|
$pMetadata->xmlCollectionInline = (Boolean)$colConfig['inline']; |
157
|
|
|
} |
158
|
|
|
|
159
|
2 |
|
if (isset($colConfig['entry_name'])) { |
160
|
1 |
|
$pMetadata->xmlEntryName = (string)$colConfig['entry_name']; |
161
|
|
|
} |
162
|
|
|
|
163
|
2 |
|
if (isset($colConfig['skip_when_empty'])) { |
164
|
1 |
|
$pMetadata->xmlCollectionSkipWhenEmpty = (Boolean)$colConfig['skip_when_empty']; |
165
|
|
|
} else { |
166
|
2 |
|
$pMetadata->xmlCollectionSkipWhenEmpty = true; |
167
|
|
|
} |
168
|
|
|
|
169
|
2 |
|
if (isset($colConfig['namespace'])) { |
170
|
|
|
$pMetadata->xmlEntryNamespace = (string)$colConfig['namespace']; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
22 |
|
if (isset($pConfig['xml_map'])) { |
175
|
|
|
$pMetadata->xmlCollection = true; |
176
|
|
|
|
177
|
|
|
$colConfig = $pConfig['xml_map']; |
178
|
|
|
if (isset($colConfig['inline'])) { |
179
|
|
|
$pMetadata->xmlCollectionInline = (Boolean)$colConfig['inline']; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
if (isset($colConfig['entry_name'])) { |
183
|
|
|
$pMetadata->xmlEntryName = (string)$colConfig['entry_name']; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
if (isset($colConfig['namespace'])) { |
187
|
|
|
$pMetadata->xmlEntryNamespace = (string)$colConfig['namespace']; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
if (isset($colConfig['key_attribute_name'])) { |
191
|
|
|
$pMetadata->xmlKeyAttribute = $colConfig['key_attribute_name']; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
} |
195
|
|
|
|
196
|
22 |
|
if (isset($pConfig['xml_element'])) { |
197
|
4 |
|
$colConfig = $pConfig['xml_element']; |
198
|
4 |
|
if (isset($colConfig['cdata'])) { |
199
|
2 |
|
$pMetadata->xmlElementCData = (Boolean)$colConfig['cdata']; |
200
|
|
|
} |
201
|
|
|
|
202
|
4 |
|
if (isset($colConfig['namespace'])) { |
203
|
3 |
|
$pMetadata->xmlNamespace = (string)$colConfig['namespace']; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
22 |
|
if (isset($pConfig['xml_attribute'])) { |
208
|
4 |
|
$pMetadata->xmlAttribute = (Boolean)$pConfig['xml_attribute']; |
209
|
|
|
} |
210
|
|
|
|
211
|
22 |
|
if (isset($pConfig['xml_attribute_map'])) { |
212
|
|
|
$pMetadata->xmlAttributeMap = (Boolean)$pConfig['xml_attribute_map']; |
213
|
|
|
} |
214
|
|
|
|
215
|
22 |
|
if (isset($pConfig['xml_value'])) { |
216
|
2 |
|
$pMetadata->xmlValue = (Boolean)$pConfig['xml_value']; |
217
|
|
|
} |
218
|
|
|
|
219
|
22 |
|
if (isset($pConfig['xml_key_value_pairs'])) { |
220
|
1 |
|
$pMetadata->xmlKeyValuePairs = (Boolean)$pConfig['xml_key_value_pairs']; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
//we need read_only before setter and getter set, because that method depends on flag being set |
224
|
22 |
|
if (isset($pConfig['read_only'])) { |
225
|
3 |
|
$pMetadata->readOnly = (Boolean)$pConfig['read_only']; |
226
|
|
|
} else { |
227
|
20 |
|
$pMetadata->readOnly = $pMetadata->readOnly || $readOnlyClass; |
228
|
|
|
} |
229
|
|
|
|
230
|
22 |
|
$pMetadata->setAccessor( |
231
|
22 |
|
isset($pConfig['access_type']) ? $pConfig['access_type'] : null, |
232
|
22 |
|
isset($pConfig['accessor']['getter']) ? $pConfig['accessor']['getter'] : null, |
233
|
22 |
|
isset($pConfig['accessor']['setter']) ? $pConfig['accessor']['setter'] : null, |
234
|
22 |
|
isset($pConfig['access_type_naming']) ? $pConfig['access_type_naming'] : null |
235
|
|
|
); |
236
|
|
|
|
237
|
22 |
|
if (isset($pConfig['inline'])) { |
238
|
|
|
$pMetadata->inline = (Boolean)$pConfig['inline']; |
239
|
|
|
} |
240
|
|
|
|
241
|
22 |
|
if (isset($pConfig['max_depth'])) { |
242
|
1 |
|
$pMetadata->maxDepth = (int)$pConfig['max_depth']; |
243
|
|
|
} |
244
|
|
|
} |
245
|
26 |
|
if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude) |
246
|
26 |
|
|| (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose) |
247
|
|
|
) { |
248
|
26 |
|
$metadata->addPropertyMetadata($pMetadata); |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
|
253
|
29 |
|
if (isset($config['handler_callbacks'])) { |
254
|
1 |
|
foreach ($config['handler_callbacks'] as $directionName => $formats) { |
255
|
1 |
|
$direction = GraphNavigator::parseDirection($directionName); |
256
|
1 |
|
foreach ($formats as $format => $methodName) { |
257
|
1 |
|
$metadata->addHandlerCallback($direction, $format, $methodName); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
262
|
29 |
|
if (isset($config['callback_methods'])) { |
263
|
|
|
$cConfig = $config['callback_methods']; |
264
|
|
|
|
265
|
|
|
if (isset($cConfig['pre_serialize'])) { |
266
|
|
|
$metadata->preSerializeMethods = $this->getCallbackMetadata($class, $cConfig['pre_serialize']); |
267
|
|
|
} |
268
|
|
|
if (isset($cConfig['post_serialize'])) { |
269
|
|
|
$metadata->postSerializeMethods = $this->getCallbackMetadata($class, $cConfig['post_serialize']); |
270
|
|
|
} |
271
|
|
|
if (isset($cConfig['post_deserialize'])) { |
272
|
|
|
$metadata->postDeserializeMethods = $this->getCallbackMetadata($class, $cConfig['post_deserialize']); |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
29 |
|
$this->propertyUpdater->update($metadata); |
277
|
|
|
|
278
|
28 |
|
return $metadata; |
279
|
|
|
} |
280
|
|
|
|
281
|
29 |
|
protected function getExtension() |
282
|
|
|
{ |
283
|
29 |
|
return 'yml'; |
284
|
|
|
} |
285
|
|
|
|
286
|
29 |
|
private function addClassProperties(ClassMetadata $metadata, array $config) |
287
|
|
|
{ |
288
|
29 |
|
if (isset($config['custom_accessor_order']) && !isset($config['accessor_order'])) { |
289
|
1 |
|
$config['accessor_order'] = 'custom'; |
290
|
|
|
} |
291
|
|
|
|
292
|
29 |
|
if (isset($config['accessor_order'])) { |
293
|
1 |
|
$metadata->setAccessorOrder($config['accessor_order'], isset($config['custom_accessor_order']) ? $config['custom_accessor_order'] : array()); |
294
|
|
|
} |
295
|
|
|
|
296
|
29 |
|
if (isset($config['xml_root_name'])) { |
297
|
9 |
|
$metadata->xmlRootName = (string)$config['xml_root_name']; |
298
|
|
|
} |
299
|
|
|
|
300
|
29 |
|
if (isset($config['xml_root_namespace'])) { |
301
|
1 |
|
$metadata->xmlRootNamespace = (string)$config['xml_root_namespace']; |
302
|
|
|
} |
303
|
|
|
|
304
|
29 |
|
if (array_key_exists('xml_namespaces', $config)) { |
305
|
|
|
|
306
|
3 |
|
foreach ($config['xml_namespaces'] as $prefix => $uri) { |
307
|
3 |
|
$metadata->registerNamespace($uri, $prefix); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
} |
311
|
|
|
|
312
|
29 |
|
if (isset($config['discriminator'])) { |
313
|
4 |
|
if (isset($config['discriminator']['disabled']) && true === $config['discriminator']['disabled']) { |
314
|
|
|
$metadata->discriminatorDisabled = true; |
315
|
|
|
} else { |
316
|
4 |
|
if (!isset($config['discriminator']['field_name'])) { |
317
|
|
|
throw new RuntimeException('The "field_name" attribute must be set for discriminators.'); |
318
|
|
|
} |
319
|
|
|
|
320
|
4 |
|
if (!isset($config['discriminator']['map']) || !\is_array($config['discriminator']['map'])) { |
321
|
|
|
throw new RuntimeException('The "map" attribute must be set, and be an array for discriminators.'); |
322
|
|
|
} |
323
|
4 |
|
$groups = isset($config['discriminator']['groups']) ? $config['discriminator']['groups'] : array(); |
324
|
4 |
|
$metadata->setDiscriminator($config['discriminator']['field_name'], $config['discriminator']['map'], $groups); |
325
|
|
|
|
326
|
4 |
|
if (isset($config['discriminator']['xml_attribute'])) { |
327
|
1 |
|
$metadata->xmlDiscriminatorAttribute = (bool)$config['discriminator']['xml_attribute']; |
328
|
|
|
} |
329
|
4 |
|
if (isset($config['discriminator']['xml_element'])) { |
330
|
2 |
|
if (isset($config['discriminator']['xml_element']['cdata'])) { |
331
|
1 |
|
$metadata->xmlDiscriminatorCData = (bool)$config['discriminator']['xml_element']['cdata']; |
332
|
|
|
} |
333
|
2 |
|
if (isset($config['discriminator']['xml_element']['namespace'])) { |
334
|
1 |
|
$metadata->xmlDiscriminatorNamespace = (string)$config['discriminator']['xml_element']['namespace']; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
} |
339
|
|
|
} |
340
|
29 |
|
} |
341
|
|
|
|
342
|
|
|
private function getCallbackMetadata(\ReflectionClass $class, $config) |
343
|
|
|
{ |
344
|
|
|
if (\is_string($config)) { |
345
|
|
|
$config = array($config); |
346
|
|
|
} elseif (!\is_array($config)) { |
347
|
|
|
throw new RuntimeException(sprintf('callback methods expects a string, or an array of strings that represent method names, but got %s.', json_encode($config['pre_serialize']))); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
$methods = array(); |
351
|
|
|
foreach ($config as $name) { |
352
|
|
|
if (!$class->hasMethod($name)) { |
353
|
|
|
throw new RuntimeException(sprintf('The method %s does not exist in class %s.', $name, $class->name)); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
$methods[] = new MethodMetadata($class->name, $name); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return $methods; |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.