schmittjoh /
serializer
| 1 | <?php |
||||||
| 2 | |||||||
| 3 | declare(strict_types=1); |
||||||
| 4 | |||||||
| 5 | namespace JMS\Serializer; |
||||||
| 6 | |||||||
| 7 | use JMS\Serializer\Exception\InvalidArgumentException; |
||||||
| 8 | use JMS\Serializer\Exception\LogicException; |
||||||
| 9 | use JMS\Serializer\Exception\NotAcceptableException; |
||||||
| 10 | use JMS\Serializer\Exception\RuntimeException; |
||||||
| 11 | use JMS\Serializer\Exception\XmlErrorException; |
||||||
| 12 | use JMS\Serializer\Metadata\ClassMetadata; |
||||||
| 13 | use JMS\Serializer\Metadata\PropertyMetadata; |
||||||
| 14 | use JMS\Serializer\Visitor\DeserializationVisitorInterface; |
||||||
| 15 | |||||||
| 16 | final class XmlDeserializationVisitor extends AbstractVisitor implements NullAwareVisitorInterface, DeserializationVisitorInterface |
||||||
| 17 | { |
||||||
| 18 | /** |
||||||
| 19 | * @var \SplStack |
||||||
| 20 | */ |
||||||
| 21 | private $objectStack; |
||||||
| 22 | |||||||
| 23 | /** |
||||||
| 24 | * @var \SplStack |
||||||
| 25 | */ |
||||||
| 26 | 72 | private $metadataStack; |
|||||
| 27 | |||||||
| 28 | /** |
||||||
| 29 | 72 | * @var \SplStack |
|||||
| 30 | 72 | */ |
|||||
| 31 | 72 | private $objectMetadataStack; |
|||||
| 32 | 72 | ||||||
| 33 | 72 | /** |
|||||
| 34 | 72 | * @var object|null |
|||||
| 35 | */ |
||||||
| 36 | 71 | private $currentObject; |
|||||
| 37 | |||||||
| 38 | 71 | /** |
|||||
| 39 | * @var ClassMetadata|PropertyMetadata|null |
||||||
| 40 | 71 | */ |
|||||
| 41 | 71 | private $currentMetadata; |
|||||
| 42 | |||||||
| 43 | 71 | /** |
|||||
| 44 | * @var bool |
||||||
| 45 | 71 | */ |
|||||
| 46 | 2 | private $disableExternalEntities; |
|||||
| 47 | 2 | ||||||
| 48 | 2 | /** |
|||||
| 49 | 2 | * @var string[] |
|||||
| 50 | 2 | */ |
|||||
| 51 | private $doctypeAllowList; |
||||||
| 52 | /** |
||||||
| 53 | * @var int |
||||||
| 54 | */ |
||||||
| 55 | 69 | private $options; |
|||||
| 56 | |||||||
| 57 | 69 | public function __construct( |
|||||
| 58 | 69 | bool $disableExternalEntities = true, |
|||||
| 59 | array $doctypeAllowList = [], |
||||||
| 60 | 69 | int $options = 0 |
|||||
| 61 | 1 | ) { |
|||||
| 62 | $this->objectStack = new \SplStack(); |
||||||
| 63 | $this->metadataStack = new \SplStack(); |
||||||
| 64 | 68 | $this->objectMetadataStack = new \SplStack(); |
|||||
| 65 | $this->disableExternalEntities = $disableExternalEntities; |
||||||
| 66 | $this->doctypeAllowList = $doctypeAllowList; |
||||||
| 67 | 71 | $this->options = $options; |
|||||
| 68 | } |
||||||
| 69 | 71 | ||||||
| 70 | /** |
||||||
| 71 | * {@inheritdoc} |
||||||
| 72 | 13 | */ |
|||||
| 73 | public function prepare($data) |
||||||
| 74 | { |
||||||
| 75 | 13 | $data = $this->emptyStringToSpaceCharacter($data); |
|||||
| 76 | |||||||
| 77 | 25 | $previous = libxml_use_internal_errors(true); |
|||||
| 78 | libxml_clear_errors(); |
||||||
| 79 | 25 | ||||||
| 80 | $previousEntityLoaderState = null; |
||||||
| 81 | if (\LIBXML_VERSION < 20900) { |
||||||
| 82 | 8 | // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated |
|||||
| 83 | $previousEntityLoaderState = libxml_disable_entity_loader($this->disableExternalEntities); |
||||||
| 84 | 8 | } |
|||||
| 85 | |||||||
| 86 | 8 | if (false !== stripos($data, '<!doctype')) { |
|||||
| 87 | 4 | $internalSubset = $this->getDomDocumentTypeEntitySubset($data); |
|||||
| 88 | 5 | if (!in_array($internalSubset, $this->doctypeAllowList, true)) { |
|||||
| 89 | 5 | throw new InvalidArgumentException(sprintf( |
|||||
| 90 | 'The document type "%s" is not allowed. If it is safe, you may add it to the allowlist configuration.', |
||||||
| 91 | $internalSubset, |
||||||
| 92 | )); |
||||||
| 93 | } |
||||||
| 94 | } |
||||||
| 95 | 8 | ||||||
| 96 | $doc = simplexml_load_string($data, 'SimpleXMLElement', $this->options); |
||||||
| 97 | 8 | ||||||
| 98 | libxml_use_internal_errors($previous); |
||||||
| 99 | |||||||
| 100 | 10 | if (\LIBXML_VERSION < 20900) { |
|||||
| 101 | // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated |
||||||
| 102 | 10 | libxml_disable_entity_loader($previousEntityLoaderState); |
|||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
| 103 | } |
||||||
| 104 | |||||||
| 105 | 18 | if (false === $doc) { |
|||||
| 106 | throw new XmlErrorException(libxml_get_last_error()); |
||||||
| 107 | } |
||||||
| 108 | 18 | ||||||
| 109 | 2 | return $doc; |
|||||
| 110 | } |
||||||
| 111 | |||||||
| 112 | 2 | /** |
|||||
| 113 | * @param mixed $data |
||||||
| 114 | 2 | */ |
|||||
| 115 | private function emptyStringToSpaceCharacter($data): string |
||||||
| 116 | 2 | { |
|||||
| 117 | 2 | return '' === $data ? ' ' : (string) $data; |
|||||
| 118 | 2 | } |
|||||
| 119 | 2 | ||||||
| 120 | /** |
||||||
| 121 | * {@inheritdoc} |
||||||
| 122 | 2 | */ |
|||||
| 123 | public function visitNull($data, array $type) |
||||||
| 124 | { |
||||||
| 125 | 18 | return null; |
|||||
| 126 | 18 | } |
|||||
| 127 | |||||||
| 128 | 18 | /** |
|||||
| 129 | 13 | * {@inheritdoc} |
|||||
| 130 | 13 | */ |
|||||
| 131 | 13 | public function visitString($data, array $type): string |
|||||
| 132 | 10 | { |
|||||
| 133 | 10 | $this->assertValueCanBeCastToString($data); |
|||||
| 134 | 1 | ||||||
| 135 | return (string) $data; |
||||||
| 136 | } |
||||||
| 137 | |||||||
| 138 | /** |
||||||
| 139 | 18 | * {@inheritdoc} |
|||||
| 140 | 5 | */ |
|||||
| 141 | 5 | public function visitBoolean($data, array $type): bool |
|||||
| 142 | 5 | { |
|||||
| 143 | $this->assertValueCanBeCastToString($data); |
||||||
| 144 | 14 | ||||||
| 145 | $data = (string) $data; |
||||||
| 146 | |||||||
| 147 | 18 | if ('true' === $data || '1' === $data) { |
|||||
| 148 | 4 | return true; |
|||||
| 149 | } elseif ('false' === $data || '0' === $data) { |
||||||
| 150 | return false; |
||||||
| 151 | 18 | } else { |
|||||
| 152 | 18 | throw new RuntimeException(sprintf('Could not convert data to boolean. Expected "true", "false", "1" or "0", but got %s.', json_encode($data))); |
|||||
| 153 | } |
||||||
| 154 | } |
||||||
| 155 | 18 | ||||||
| 156 | 18 | /** |
|||||
| 157 | * {@inheritdoc} |
||||||
| 158 | 18 | */ |
|||||
| 159 | 18 | public function visitInteger($data, array $type): int |
|||||
| 160 | { |
||||||
| 161 | $this->assertValueCanBeCastToInt($data); |
||||||
| 162 | 18 | ||||||
| 163 | return (int) $data; |
||||||
| 164 | 4 | } |
|||||
| 165 | 4 | ||||||
| 166 | /** |
||||||
| 167 | * {@inheritdoc} |
||||||
| 168 | */ |
||||||
| 169 | 4 | public function visitDouble($data, array $type): float |
|||||
| 170 | 4 | { |
|||||
| 171 | $this->assertValueCanCastToFloat($data); |
||||||
| 172 | 4 | ||||||
| 173 | 4 | return (float) $data; |
|||||
| 174 | 4 | } |
|||||
| 175 | 4 | ||||||
| 176 | /** |
||||||
| 177 | * {@inheritdoc} |
||||||
| 178 | */ |
||||||
| 179 | 4 | public function visitArray($data, array $type): array |
|||||
| 180 | 4 | { |
|||||
| 181 | // handle key-value-pairs |
||||||
| 182 | if (null !== $this->currentMetadata && $this->currentMetadata->xmlKeyValuePairs) { |
||||||
|
0 ignored issues
–
show
|
|||||||
| 183 | 4 | if (2 !== count($type['params'])) { |
|||||
| 184 | throw new RuntimeException('The array type must be specified as "array<K,V>" for Key-Value-Pairs.'); |
||||||
| 185 | } |
||||||
| 186 | |||||||
| 187 | $this->revertCurrentMetadata(); |
||||||
| 188 | |||||||
| 189 | [$keyType, $entryType] = $type['params']; |
||||||
| 190 | 8 | ||||||
| 191 | $result = []; |
||||||
| 192 | foreach ($data as $key => $v) { |
||||||
| 193 | $k = $this->navigator->accept($key, $keyType); |
||||||
|
0 ignored issues
–
show
The method
accept() does not exist on null.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. Loading history...
|
|||||||
| 194 | 8 | $result[$k] = $this->navigator->accept($v, $entryType); |
|||||
| 195 | 1 | } |
|||||
| 196 | |||||||
| 197 | return $result; |
||||||
| 198 | 7 | } |
|||||
| 199 | 1 | ||||||
| 200 | $entryName = null !== $this->currentMetadata && $this->currentMetadata->xmlEntryName ? $this->currentMetadata->xmlEntryName : 'entry'; |
||||||
|
0 ignored issues
–
show
|
|||||||
| 201 | $namespace = null !== $this->currentMetadata && $this->currentMetadata->xmlEntryNamespace ? $this->currentMetadata->xmlEntryNamespace : null; |
||||||
|
0 ignored issues
–
show
|
|||||||
| 202 | 6 | ||||||
| 203 | 1 | if (null === $namespace && $this->objectMetadataStack->count()) { |
|||||
| 204 | $classMetadata = $this->objectMetadataStack->top(); |
||||||
| 205 | 5 | $namespace = $classMetadata->xmlNamespaces[''] ?? $namespace; |
|||||
| 206 | 4 | if (null === $namespace) { |
|||||
| 207 | $namespaces = $data->getDocNamespaces(); |
||||||
| 208 | if (isset($namespaces[''])) { |
||||||
| 209 | 1 | $namespace = $namespaces['']; |
|||||
| 210 | 1 | } |
|||||
| 211 | 1 | } |
|||||
| 212 | 1 | } |
|||||
| 213 | |||||||
| 214 | if (null !== $namespace) { |
||||||
| 215 | $prefix = uniqid('ns-'); |
||||||
| 216 | $data->registerXPathNamespace($prefix, $namespace); |
||||||
| 217 | 34 | $nodes = $data->xpath(sprintf('%s:%s', $prefix, $entryName)); |
|||||
| 218 | } else { |
||||||
| 219 | 34 | $nodes = $data->xpath($entryName); |
|||||
| 220 | 34 | } |
|||||
| 221 | 34 | ||||||
| 222 | if (null === $nodes || !\count($nodes)) { |
||||||
| 223 | 30 | return []; |
|||||
| 224 | } |
||||||
| 225 | 30 | ||||||
| 226 | switch (\count($type['params'])) { |
||||||
| 227 | 30 | case 0: |
|||||
| 228 | throw new RuntimeException(sprintf('The array type must be specified either as "array<T>", or "array<K,V>".')); |
||||||
| 229 | |||||||
| 230 | case 1: |
||||||
| 231 | 30 | $result = []; |
|||||
| 232 | |||||||
| 233 | 6 | foreach ($nodes as $v) { |
|||||
| 234 | 6 | $result[] = $this->navigator->accept($v, $type['params'][0]); |
|||||
| 235 | 6 | } |
|||||
| 236 | |||||||
| 237 | return $result; |
||||||
| 238 | |||||||
| 239 | case 2: |
||||||
| 240 | if (null === $this->currentMetadata) { |
||||||
| 241 | 30 | throw new RuntimeException('Maps are not supported on top-level without metadata.'); |
|||||
| 242 | 7 | } |
|||||
| 243 | |||||||
| 244 | [$keyType, $entryType] = $type['params']; |
||||||
| 245 | 28 | $result = []; |
|||||
| 246 | 7 | ||||||
| 247 | 7 | $nodes = $data->children($namespace)->$entryName; |
|||||
| 248 | 6 | foreach ($nodes as $v) { |
|||||
| 249 | $attrs = $v->attributes(); |
||||||
| 250 | if (!isset($attrs[$this->currentMetadata->xmlKeyAttribute])) { |
||||||
|
0 ignored issues
–
show
|
|||||||
| 251 | 7 | throw new RuntimeException(sprintf('The key attribute "%s" must be set for each entry of the map.', $this->currentMetadata->xmlKeyAttribute)); |
|||||
| 252 | 7 | } |
|||||
| 253 | 7 | ||||||
| 254 | 7 | $k = $this->navigator->accept($attrs[$this->currentMetadata->xmlKeyAttribute], $keyType); |
|||||
| 255 | $result[$k] = $this->navigator->accept($v, $entryType); |
||||||
| 256 | } |
||||||
| 257 | 27 | ||||||
| 258 | 4 | return $result; |
|||||
| 259 | 4 | ||||||
| 260 | 4 | default: |
|||||
| 261 | throw new LogicException(sprintf('The array type does not support more than 2 parameters, but got %s.', json_encode($type['params']))); |
||||||
| 262 | } |
||||||
| 263 | } |
||||||
| 264 | 24 | ||||||
| 265 | /** |
||||||
| 266 | 24 | * {@inheritdoc} |
|||||
| 267 | 2 | */ |
|||||
| 268 | 2 | public function visitDiscriminatorMapProperty($data, ClassMetadata $metadata): string |
|||||
| 269 | 2 | { |
|||||
| 270 | switch (true) { |
||||||
| 271 | 22 | // Check XML attribute without namespace for discriminatorFieldName |
|||||
| 272 | case $metadata->xmlDiscriminatorAttribute && null === $metadata->xmlDiscriminatorNamespace && isset($data->attributes()->{$metadata->discriminatorFieldName}): |
||||||
| 273 | 24 | return (string) $data->attributes()->{$metadata->discriminatorFieldName}; |
|||||
| 274 | 2 | ||||||
| 275 | // Check XML attribute with namespace for discriminatorFieldName |
||||||
| 276 | 24 | case $metadata->xmlDiscriminatorAttribute && null !== $metadata->xmlDiscriminatorNamespace && isset($data->attributes($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}): |
|||||
| 277 | return (string) $data->attributes($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}; |
||||||
| 278 | |||||||
| 279 | 26 | // Check XML element with namespace for discriminatorFieldName |
|||||
| 280 | 2 | case !$metadata->xmlDiscriminatorAttribute && null !== $metadata->xmlDiscriminatorNamespace && isset($data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}): |
|||||
| 281 | return (string) $data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}; |
||||||
| 282 | |||||||
| 283 | 26 | // Check XML element for discriminatorFieldName |
|||||
| 284 | case isset($data->{$metadata->discriminatorFieldName}): |
||||||
| 285 | return (string) $data->{$metadata->discriminatorFieldName}; |
||||||
| 286 | |||||||
| 287 | default: |
||||||
| 288 | throw new LogicException(sprintf( |
||||||
| 289 | 'The discriminator field name "%s" for base-class "%s" was not found in input data.', |
||||||
| 290 | $metadata->discriminatorFieldName, |
||||||
| 291 | $metadata->name, |
||||||
| 292 | 34 | )); |
|||||
| 293 | } |
||||||
| 294 | 34 | } |
|||||
| 295 | 34 | ||||||
| 296 | 34 | public function startVisitingObject(ClassMetadata $metadata, object $object, array $type): void |
|||||
| 297 | { |
||||||
| 298 | 34 | $this->setCurrentObject($object); |
|||||
| 299 | $this->objectMetadataStack->push($metadata); |
||||||
| 300 | } |
||||||
| 301 | 34 | ||||||
| 302 | /** |
||||||
| 303 | 34 | * {@inheritdoc} |
|||||
| 304 | 34 | */ |
|||||
| 305 | 34 | public function visitProperty(PropertyMetadata $metadata, $data) |
|||||
| 306 | { |
||||||
| 307 | $name = $metadata->serializedName; |
||||||
| 308 | |||||||
| 309 | if (true === $metadata->inline) { |
||||||
| 310 | if (!$metadata->type) { |
||||||
| 311 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
||||||
| 312 | 34 | } |
|||||
| 313 | |||||||
| 314 | 34 | return $this->navigator->accept($data, $metadata->type); |
|||||
|
0 ignored issues
–
show
$metadata->type of type JMS\Serializer\Metadata\TypeArray is incompatible with the type array|null expected by parameter $type of JMS\Serializer\GraphNavigatorInterface::accept().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 315 | } |
||||||
| 316 | |||||||
| 317 | 9 | if ($metadata->xmlAttribute) { |
|||||
| 318 | $attributes = $data->attributes($metadata->xmlNamespace); |
||||||
| 319 | 9 | if (isset($attributes[$name])) { |
|||||
| 320 | 9 | if (!$metadata->type) { |
|||||
| 321 | 9 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
|||||
| 322 | } |
||||||
| 323 | |||||||
| 324 | return $this->navigator->accept($attributes[$name], $metadata->type); |
||||||
| 325 | } |
||||||
| 326 | |||||||
| 327 | throw new NotAcceptableException(); |
||||||
| 328 | 9 | } |
|||||
| 329 | |||||||
| 330 | 9 | if (0 === strpos($name, '@')) { |
|||||
|
0 ignored issues
–
show
It seems like
$name can also be of type null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 331 | $attributeName = substr($name, 1); |
||||||
|
0 ignored issues
–
show
It seems like
$name can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 332 | $attributes = $data->attributes($metadata->xmlNamespace); |
||||||
| 333 | 67 | ||||||
| 334 | if (isset($attributes[$attributeName])) { |
||||||
| 335 | 67 | if (!$metadata->type) { |
|||||
| 336 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
||||||
| 337 | } |
||||||
| 338 | |||||||
| 339 | return $this->navigator->accept($attributes[$attributeName], $metadata->type); |
||||||
| 340 | } |
||||||
| 341 | |||||||
| 342 | throw new NotAcceptableException(sprintf('Attribute "%s" (derived from serializedName "%s") in namespace "%s" not found for property %s::$%s. XML: %s', $attributeName, $name, $metadata->xmlNamespace ?? '[none]', $metadata->class, $metadata->name, $data->asXML())); |
||||||
| 343 | } |
||||||
| 344 | 2 | ||||||
| 345 | if (false !== strpos($name, '/@')) { |
||||||
| 346 | 2 | [$elementName, $attributeName] = explode('/@', $name, 2); |
|||||
|
0 ignored issues
–
show
It seems like
$name can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 347 | 2 | ||||||
| 348 | $childDataNode = null; |
||||||
| 349 | 2 | if ('' === $metadata->xmlNamespace) { |
|||||
| 350 | 2 | // Element explicitly in NO namespace |
|||||
| 351 | 2 | $xpathQuery = "./*[local-name()='" . $elementName . "' and (namespace-uri()='' or not(namespace-uri()))]"; |
|||||
| 352 | $matchingNodes = $data->xpath($xpathQuery); |
||||||
| 353 | 2 | if (!empty($matchingNodes)) { |
|||||
| 354 | 2 | $childDataNode = $matchingNodes[0]; |
|||||
| 355 | } |
||||||
| 356 | 2 | } elseif ($metadata->xmlNamespace) { |
|||||
| 357 | // Element in a specific namespace URI |
||||||
| 358 | 2 | $childrenInNs = $data->children($metadata->xmlNamespace); |
|||||
| 359 | 2 | if (isset($childrenInNs->$elementName)) { |
|||||
| 360 | 2 | $childDataNode = $childrenInNs->$elementName; |
|||||
| 361 | 2 | } |
|||||
| 362 | } else { |
||||||
| 363 | 2 | // xmlNamespace is null: element in default namespace (or no namespace if no default is active) |
|||||
| 364 | $childrenInDefaultOrNoNs = $data->children(null); |
||||||
| 365 | if (isset($childrenInDefaultOrNoNs->$elementName)) { |
||||||
| 366 | $childDataNode = $childrenInDefaultOrNoNs->$elementName; |
||||||
| 367 | } |
||||||
| 368 | } |
||||||
| 369 | |||||||
| 370 | if (!$childDataNode || !$childDataNode->getName()) { |
||||||
| 371 | 69 | if (null === $metadata->xmlNamespace) { |
|||||
| 372 | $ns = '[default/none]'; |
||||||
| 373 | 69 | } else { |
|||||
| 374 | $ns = '' === $metadata->xmlNamespace ? '[none]' : $metadata->xmlNamespace; |
||||||
| 375 | } |
||||||
| 376 | |||||||
| 377 | 69 | throw new NotAcceptableException(sprintf('Child element "%s" for attribute access not found (element namespace: %s). Property %s::$%s. XML: %s', $elementName, $ns, $metadata->class, $metadata->name, $data->asXML())); |
|||||
| 378 | } |
||||||
| 379 | 2 | ||||||
| 380 | $attributeTargetNs = $metadata->xmlNamespace && '' !== $metadata->xmlNamespace ? $metadata->xmlNamespace : null; |
||||||
| 381 | $attributes = $childDataNode->attributes($attributeTargetNs); |
||||||
| 382 | 69 | ||||||
| 383 | 69 | if (isset($attributes[$attributeName])) { |
|||||
| 384 | 69 | if (!$metadata->type) { |
|||||
| 385 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
||||||
| 386 | 14 | } |
|||||
| 387 | |||||||
| 388 | return $this->navigator->accept($attributes[$attributeName], $metadata->type); |
||||||
| 389 | } |
||||||
| 390 | 57 | ||||||
| 391 | throw new NotAcceptableException(sprintf('Attribute "%s" on element "%s" not found (attribute namespace: %s). Property %s::$%s. XML: %s', $attributeName, $elementName, $attributeTargetNs ?? '[none]', $metadata->class, $metadata->name, $data->asXML())); |
||||||
| 392 | } |
||||||
| 393 | |||||||
| 394 | if ($metadata->xmlValue) { |
||||||
| 395 | if (!$metadata->type) { |
||||||
| 396 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
||||||
| 397 | } |
||||||
| 398 | |||||||
| 399 | return $this->navigator->accept($data, $metadata->type); |
||||||
| 400 | } |
||||||
| 401 | |||||||
| 402 | if ($metadata->xmlCollection) { |
||||||
| 403 | $enclosingElem = $data; |
||||||
| 404 | if (!$metadata->xmlCollectionInline) { |
||||||
| 405 | $enclosingElem = $data->children($metadata->xmlNamespace)->$name; |
||||||
| 406 | } |
||||||
| 407 | |||||||
| 408 | $this->setCurrentMetadata($metadata); |
||||||
| 409 | if (!$metadata->type) { |
||||||
| 410 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
||||||
| 411 | } |
||||||
| 412 | |||||||
| 413 | $v = $this->navigator->accept($enclosingElem, $metadata->type); |
||||||
| 414 | $this->revertCurrentMetadata(); |
||||||
| 415 | |||||||
| 416 | return $v; |
||||||
| 417 | } |
||||||
| 418 | |||||||
| 419 | if ($metadata->xmlNamespace) { |
||||||
| 420 | $node = $data->children($metadata->xmlNamespace)->$name; |
||||||
| 421 | if (!$node->count()) { |
||||||
| 422 | throw new NotAcceptableException(); |
||||||
| 423 | } |
||||||
| 424 | } elseif ('' === $metadata->xmlNamespace) { |
||||||
| 425 | // See #1087 - element must be like: <element xmlns="" /> - https://www.w3.org/TR/REC-xml-names/#iri-use |
||||||
| 426 | // Use of an empty string in a namespace declaration turns it into an "undeclaration". |
||||||
| 427 | $nodes = $data->xpath('./' . $name); |
||||||
| 428 | if (empty($nodes)) { |
||||||
| 429 | throw new NotAcceptableException(); |
||||||
| 430 | } |
||||||
| 431 | |||||||
| 432 | $node = reset($nodes); |
||||||
| 433 | } else { |
||||||
| 434 | $namespaces = $data->getDocNamespaces(); |
||||||
| 435 | if (isset($namespaces[''])) { |
||||||
| 436 | $prefix = uniqid('ns-'); |
||||||
| 437 | $data->registerXPathNamespace($prefix, $namespaces['']); |
||||||
| 438 | $nodes = $data->xpath('./' . $prefix . ':' . $name); |
||||||
| 439 | } else { |
||||||
| 440 | $nodes = $data->xpath('./' . $name); |
||||||
| 441 | } |
||||||
| 442 | |||||||
| 443 | if (empty($nodes)) { |
||||||
| 444 | throw new NotAcceptableException(); |
||||||
| 445 | } |
||||||
| 446 | |||||||
| 447 | $node = reset($nodes); |
||||||
| 448 | } |
||||||
| 449 | |||||||
| 450 | if ($metadata->xmlKeyValuePairs) { |
||||||
| 451 | $this->setCurrentMetadata($metadata); |
||||||
| 452 | } |
||||||
| 453 | |||||||
| 454 | if (!$metadata->type) { |
||||||
| 455 | throw RuntimeException::noMetadataForProperty($metadata->class, $metadata->name); |
||||||
| 456 | } |
||||||
| 457 | |||||||
| 458 | return $this->navigator->accept($node, $metadata->type); |
||||||
| 459 | } |
||||||
| 460 | |||||||
| 461 | /** |
||||||
| 462 | * {@inheritdoc} |
||||||
| 463 | */ |
||||||
| 464 | public function endVisitingObject(ClassMetadata $metadata, $data, array $type): object |
||||||
| 465 | { |
||||||
| 466 | $rs = $this->currentObject; |
||||||
| 467 | $this->objectMetadataStack->pop(); |
||||||
| 468 | $this->revertCurrentObject(); |
||||||
| 469 | |||||||
| 470 | return $rs; |
||||||
|
0 ignored issues
–
show
|
|||||||
| 471 | } |
||||||
| 472 | |||||||
| 473 | public function setCurrentObject(object $object): void |
||||||
| 474 | { |
||||||
| 475 | $this->objectStack->push($this->currentObject); |
||||||
| 476 | $this->currentObject = $object; |
||||||
| 477 | } |
||||||
| 478 | |||||||
| 479 | public function getCurrentObject(): ?object |
||||||
| 480 | { |
||||||
| 481 | return $this->currentObject; |
||||||
| 482 | } |
||||||
| 483 | |||||||
| 484 | public function revertCurrentObject(): ?object |
||||||
| 485 | { |
||||||
| 486 | return $this->currentObject = $this->objectStack->pop(); |
||||||
| 487 | } |
||||||
| 488 | |||||||
| 489 | public function setCurrentMetadata(PropertyMetadata $metadata): void |
||||||
| 490 | { |
||||||
| 491 | $this->metadataStack->push($this->currentMetadata); |
||||||
| 492 | $this->currentMetadata = $metadata; |
||||||
| 493 | } |
||||||
| 494 | |||||||
| 495 | /** |
||||||
| 496 | * @return ClassMetadata|PropertyMetadata|null |
||||||
| 497 | */ |
||||||
| 498 | public function getCurrentMetadata() |
||||||
| 499 | { |
||||||
| 500 | return $this->currentMetadata; |
||||||
| 501 | } |
||||||
| 502 | |||||||
| 503 | /** |
||||||
| 504 | * @return ClassMetadata|PropertyMetadata|null |
||||||
| 505 | */ |
||||||
| 506 | public function revertCurrentMetadata() |
||||||
| 507 | { |
||||||
| 508 | return $this->currentMetadata = $this->metadataStack->pop(); |
||||||
| 509 | } |
||||||
| 510 | |||||||
| 511 | /** |
||||||
| 512 | * {@inheritdoc} |
||||||
| 513 | */ |
||||||
| 514 | public function getResult($data) |
||||||
| 515 | { |
||||||
| 516 | $this->navigator = null; |
||||||
| 517 | |||||||
| 518 | return $data; |
||||||
| 519 | } |
||||||
| 520 | |||||||
| 521 | /** |
||||||
| 522 | * Retrieves internalSubset even in bugfixed php versions |
||||||
| 523 | */ |
||||||
| 524 | private function getDomDocumentTypeEntitySubset(string $data): string |
||||||
| 525 | { |
||||||
| 526 | $startPos = $endPos = stripos($data, '<!doctype'); |
||||||
| 527 | $braces = 0; |
||||||
| 528 | do { |
||||||
| 529 | $char = $data[$endPos++]; |
||||||
| 530 | if ('<' === $char) { |
||||||
| 531 | ++$braces; |
||||||
| 532 | } |
||||||
| 533 | |||||||
| 534 | if ('>' === $char) { |
||||||
| 535 | --$braces; |
||||||
| 536 | } |
||||||
| 537 | } while ($braces > 0); |
||||||
| 538 | |||||||
| 539 | $internalSubset = substr($data, $startPos, $endPos - $startPos); |
||||||
| 540 | $internalSubset = str_replace(["\n", "\r"], '', $internalSubset); |
||||||
| 541 | $internalSubset = preg_replace('/\s{2,}/', ' ', $internalSubset); |
||||||
| 542 | $internalSubset = str_replace(['[ <!', '> ]>'], ['[<!', '>]>'], $internalSubset); |
||||||
| 543 | |||||||
| 544 | return $internalSubset; |
||||||
| 545 | } |
||||||
| 546 | |||||||
| 547 | /** |
||||||
| 548 | * {@inheritdoc} |
||||||
| 549 | */ |
||||||
| 550 | public function isNull($value): bool |
||||||
| 551 | { |
||||||
| 552 | if ($value instanceof \SimpleXMLElement) { |
||||||
| 553 | // Workaround for https://bugs.php.net/bug.php?id=75168 and https://github.com/schmittjoh/serializer/issues/817 |
||||||
| 554 | // If the "name" is empty means that we are on a nonexistent node and subsequent operations on the object will trigger the warning: |
||||||
| 555 | // "Node no longer exists" |
||||||
| 556 | if ('' === $value->getName()) { |
||||||
| 557 | // @todo should be "true", but for collections needs a default collection value. maybe something for the 2.0 |
||||||
| 558 | return false; |
||||||
| 559 | } |
||||||
| 560 | |||||||
| 561 | $xsiAttributes = $value->attributes('http://www.w3.org/2001/XMLSchema-instance'); |
||||||
| 562 | if ( |
||||||
| 563 | isset($xsiAttributes['nil']) |
||||||
| 564 | && ('true' === (string) $xsiAttributes['nil'] || '1' === (string) $xsiAttributes['nil']) |
||||||
| 565 | ) { |
||||||
| 566 | return true; |
||||||
| 567 | } |
||||||
| 568 | } |
||||||
| 569 | |||||||
| 570 | return null === $value; |
||||||
| 571 | } |
||||||
| 572 | } |
||||||
| 573 |