Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 693f9ab

Browse filesBrowse files
connorhunicolas-grekas
authored andcommitted
[Serializer] make XmlEncoder stateless thus reentrant
1 parent c59a5fc commit 693f9ab
Copy full SHA for 693f9ab

File tree

6 files changed

+186
-45
lines changed
Filter options

6 files changed

+186
-45
lines changed

‎src/Symfony/Component/Serializer/Encoder/XmlEncoder.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
+35-45Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
use Symfony\Component\Serializer\SerializerAwareTrait;
1818

1919
/**
20-
* Encodes XML data.
21-
*
2220
* @author Jordi Boggiano <j.boggiano@seld.be>
2321
* @author John Wards <jwards@whiteoctober.co.uk>
2422
* @author Fabian Vogler <fabian@equivalence.ch>
@@ -68,13 +66,6 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
6866
self::TYPE_CAST_ATTRIBUTES => true,
6967
];
7068

71-
/**
72-
* @var \DOMDocument
73-
*/
74-
private $dom;
75-
private $format;
76-
private $context;
77-
7869
/**
7970
* @param array $defaultContext
8071
*/
@@ -107,19 +98,17 @@ public function encode($data, $format, array $context = [])
10798

10899
$xmlRootNodeName = $context[self::ROOT_NODE_NAME] ?? $this->defaultContext[self::ROOT_NODE_NAME];
109100

110-
$this->dom = $this->createDomDocument($context);
111-
$this->format = $format;
112-
$this->context = $context;
101+
$dom = $this->createDomDocument($context);
113102

114103
if (null !== $data && !is_scalar($data)) {
115-
$root = $this->dom->createElement($xmlRootNodeName);
116-
$this->dom->appendChild($root);
117-
$this->buildXml($root, $data, $xmlRootNodeName);
104+
$root = $dom->createElement($xmlRootNodeName);
105+
$dom->appendChild($root);
106+
$this->buildXml($root, $data, $format, $context, $xmlRootNodeName);
118107
} else {
119-
$this->appendNode($this->dom, $data, $xmlRootNodeName);
108+
$this->appendNode($dom, $data, $format, $context, $xmlRootNodeName);
120109
}
121110

122-
return $this->dom->saveXML($ignorePiNode ? $this->dom->documentElement : null);
111+
return $dom->saveXML($ignorePiNode ? $dom->documentElement : null);
123112
}
124113

125114
/**
@@ -242,7 +231,7 @@ public function getRootNodeName()
242231
final protected function appendXMLString(\DOMNode $node, string $val): bool
243232
{
244233
if ('' !== $val) {
245-
$frag = $this->dom->createDocumentFragment();
234+
$frag = $node->ownerDocument->createDocumentFragment();
246235
$frag->appendXML($val);
247236
$node->appendChild($frag);
248237

@@ -254,15 +243,15 @@ final protected function appendXMLString(\DOMNode $node, string $val): bool
254243

255244
final protected function appendText(\DOMNode $node, string $val): bool
256245
{
257-
$nodeText = $this->dom->createTextNode($val);
246+
$nodeText = $node->ownerDocument->createTextNode($val);
258247
$node->appendChild($nodeText);
259248

260249
return true;
261250
}
262251

263252
final protected function appendCData(\DOMNode $node, string $val): bool
264253
{
265-
$nodeText = $this->dom->createCDATASection($val);
254+
$nodeText = $node->ownerDocument->createCDATASection($val);
266255
$node->appendChild($nodeText);
267256

268257
return true;
@@ -284,7 +273,7 @@ final protected function appendDocumentFragment(\DOMNode $node, $fragment): bool
284273

285274
final protected function appendComment(\DOMNode $node, string $data): bool
286275
{
287-
$node->appendChild($this->dom->createComment($data));
276+
$node->appendChild($node->ownerDocument->createComment($data));
288277

289278
return true;
290279
}
@@ -412,22 +401,22 @@ private function parseXmlValue(\DOMNode $node, array $context = [])
412401
*
413402
* @throws NotEncodableValueException
414403
*/
415-
private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = null): bool
404+
private function buildXml(\DOMNode $parentNode, $data, string $format, array $context, string $xmlRootNodeName = null): bool
416405
{
417406
$append = true;
418-
$removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
419-
$encoderIgnoredNodeTypes = $this->context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
407+
$removeEmptyTags = $context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
408+
$encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
420409

421-
if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data, $this->format)))) {
410+
if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data, $format)))) {
422411
foreach ($data as $key => $data) {
423412
//Ah this is the magic @ attribute types.
424413
if (str_starts_with($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) {
425414
if (!is_scalar($data)) {
426-
$data = $this->serializer->normalize($data, $this->format, $this->context);
415+
$data = $this->serializer->normalize($data, $format, $context);
427416
}
428417
$parentNode->setAttribute($attributeName, $data);
429418
} elseif ('#' === $key) {
430-
$append = $this->selectNodeType($parentNode, $data);
419+
$append = $this->selectNodeType($parentNode, $data, $format, $context);
431420
} elseif ('#comment' === $key) {
432421
if (!\in_array(\XML_COMMENT_NODE, $encoderIgnoredNodeTypes, true)) {
433422
$append = $this->appendComment($parentNode, $data);
@@ -441,15 +430,15 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName =
441430
* From ["item" => [0,1]];.
442431
*/
443432
foreach ($data as $subData) {
444-
$append = $this->appendNode($parentNode, $subData, $key);
433+
$append = $this->appendNode($parentNode, $subData, $format, $context, $key);
445434
}
446435
} else {
447-
$append = $this->appendNode($parentNode, $data, $key);
436+
$append = $this->appendNode($parentNode, $data, $format, $context, $key);
448437
}
449438
} elseif (is_numeric($key) || !$this->isElementNameValid($key)) {
450-
$append = $this->appendNode($parentNode, $data, 'item', $key);
439+
$append = $this->appendNode($parentNode, $data, $format, $context, 'item', $key);
451440
} elseif (null !== $data || !$removeEmptyTags) {
452-
$append = $this->appendNode($parentNode, $data, $key);
441+
$append = $this->appendNode($parentNode, $data, $format, $context, $key);
453442
}
454443
}
455444

@@ -461,20 +450,20 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName =
461450
throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__));
462451
}
463452

464-
$data = $this->serializer->normalize($data, $this->format, $this->context);
453+
$data = $this->serializer->normalize($data, $format, $context);
465454
if (null !== $data && !is_scalar($data)) {
466-
return $this->buildXml($parentNode, $data, $xmlRootNodeName);
455+
return $this->buildXml($parentNode, $data, $format, $context, $xmlRootNodeName);
467456
}
468457

469458
// top level data object was normalized into a scalar
470459
if (!$parentNode->parentNode->parentNode) {
471460
$root = $parentNode->parentNode;
472461
$root->removeChild($parentNode);
473462

474-
return $this->appendNode($root, $data, $xmlRootNodeName);
463+
return $this->appendNode($root, $data, $format, $context, $xmlRootNodeName);
475464
}
476465

477-
return $this->appendNode($parentNode, $data, 'data');
466+
return $this->appendNode($parentNode, $data, $format, $context, 'data');
478467
}
479468

480469
throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data))));
@@ -485,13 +474,14 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName =
485474
*
486475
* @param array|object $data
487476
*/
488-
private function appendNode(\DOMNode $parentNode, $data, string $nodeName, string $key = null): bool
477+
private function appendNode(\DOMNode $parentNode, $data, string $format, array $context, string $nodeName, string $key = null): bool
489478
{
490-
$node = $this->dom->createElement($nodeName);
479+
$dom = $parentNode instanceof \DomDocument ? $parentNode : $parentNode->ownerDocument;
480+
$node = $dom->createElement($nodeName);
491481
if (null !== $key) {
492482
$node->setAttribute('key', $key);
493483
}
494-
$appendNode = $this->selectNodeType($node, $data);
484+
$appendNode = $this->selectNodeType($node, $data, $format, $context);
495485
// we may have decided not to append this node, either in error or if its $nodeName is not valid
496486
if ($appendNode) {
497487
$parentNode->appendChild($node);
@@ -505,32 +495,32 @@ private function appendNode(\DOMNode $parentNode, $data, string $nodeName, strin
505495
*/
506496
private function needsCdataWrapping(string $val): bool
507497
{
508-
return 0 < preg_match('/[<>&]/', $val);
498+
return preg_match('/[<>&]/', $val);
509499
}
510500

511501
/**
512502
* Tests the value being passed and decide what sort of element to create.
513503
*
514504
* @throws NotEncodableValueException
515505
*/
516-
private function selectNodeType(\DOMNode $node, $val): bool
506+
private function selectNodeType(\DOMNode $node, $val, string $format, array $context): bool
517507
{
518508
if (\is_array($val)) {
519-
return $this->buildXml($node, $val);
509+
return $this->buildXml($node, $val, $format, $context);
520510
} elseif ($val instanceof \SimpleXMLElement) {
521-
$child = $this->dom->importNode(dom_import_simplexml($val), true);
511+
$child = $node->ownerDocument->importNode(dom_import_simplexml($val), true);
522512
$node->appendChild($child);
523513
} elseif ($val instanceof \Traversable) {
524-
$this->buildXml($node, $val);
514+
$this->buildXml($node, $val, $format, $context);
525515
} elseif ($val instanceof \DOMNode) {
526-
$child = $this->dom->importNode($val, true);
516+
$child = $node->ownerDocument->importNode($val, true);
527517
$node->appendChild($child);
528518
} elseif (\is_object($val)) {
529519
if (null === $this->serializer) {
530520
throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__));
531521
}
532522

533-
return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context));
523+
return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context);
534524
} elseif (is_numeric($val)) {
535525
return $this->appendText($node, (string) $val);
536526
} elseif (\is_string($val) && $this->needsCdataWrapping($val)) {

‎src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
+36Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
2121
use Symfony\Component\Serializer\Serializer;
2222
use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
23+
use Symfony\Component\Serializer\Tests\Fixtures\EnvelopedMessage;
24+
use Symfony\Component\Serializer\Tests\Fixtures\EnvelopedMessageNormalizer;
25+
use Symfony\Component\Serializer\Tests\Fixtures\EnvelopeNormalizer;
26+
use Symfony\Component\Serializer\Tests\Fixtures\EnvelopeObject;
2327
use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
2428
use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy;
2529

@@ -850,6 +854,23 @@ public function testNotEncodableValueExceptionMessageForAResource()
850854
(new XmlEncoder())->encode(tmpfile(), 'xml');
851855
}
852856

857+
public function testReentrantXmlEncoder()
858+
{
859+
$envelope = new EnvelopeObject();
860+
$message = new EnvelopedMessage();
861+
$message->text = 'Symfony is great';
862+
$envelope->message = $message;
863+
864+
$encoder = $this->createXmlEncoderWithEnvelopeNormalizer();
865+
$expected = <<<'XML'
866+
<?xml version="1.0"?>
867+
<response><message>PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxyZXNwb25zZT48dGV4dD5TeW1mb255IGlzIGdyZWF0PC90ZXh0PjwvcmVzcG9uc2U+Cg==</message></response>
868+
869+
XML;
870+
871+
$this->assertSame($expected, $encoder->encode($envelope, 'xml'));
872+
}
873+
853874
public function testEncodeComment()
854875
{
855876
$expected = <<<'XML'
@@ -921,6 +942,21 @@ private function doTestEncodeWithoutComment(bool $legacy = false)
921942
$this->assertEquals($expected, $encoder->encode($data, 'xml'));
922943
}
923944

945+
private function createXmlEncoderWithEnvelopeNormalizer(): XmlEncoder
946+
{
947+
$normalizers = [
948+
$envelopeNormalizer = new EnvelopeNormalizer(),
949+
new EnvelopedMessageNormalizer(),
950+
];
951+
952+
$encoder = new XmlEncoder();
953+
$serializer = new Serializer($normalizers, ['xml' => $encoder]);
954+
$encoder->setSerializer($serializer);
955+
$envelopeNormalizer->setSerializer($serializer);
956+
957+
return $encoder;
958+
}
959+
924960
private function createXmlEncoderWithDateTimeNormalizer(): XmlEncoder
925961
{
926962
$encoder = new XmlEncoder();
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
15+
16+
/**
17+
* @author Karoly Gossler <connor@connor.hu>
18+
*/
19+
class EnvelopeNormalizer implements NormalizerInterface
20+
{
21+
private $serializer;
22+
23+
public function normalize($envelope, $format = null, array $context = [])
24+
{
25+
$xmlContent = $this->serializer->serialize($envelope->message, 'xml');
26+
27+
$encodedContent = base64_encode($xmlContent);
28+
29+
return [
30+
'message' => $encodedContent,
31+
];
32+
}
33+
34+
public function supportsNormalization($data, $format = null)
35+
{
36+
return $data instanceof EnvelopeObject;
37+
}
38+
39+
public function setSerializer($serializer)
40+
{
41+
$this->serializer = $serializer;
42+
}
43+
}
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Karoly Gossler <connor@connor.hu>
16+
*/
17+
class EnvelopeObject
18+
{
19+
public $message;
20+
}
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Karoly Gossler <connor@connor.hu>
16+
*/
17+
class EnvelopedMessage
18+
{
19+
public $text;
20+
}
+32Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
15+
16+
/**
17+
* @author Karoly Gossler <connor@connor.hu>
18+
*/
19+
class EnvelopedMessageNormalizer implements NormalizerInterface
20+
{
21+
public function normalize($message, $format = null, array $context = [])
22+
{
23+
return [
24+
'text' => $message->text,
25+
];
26+
}
27+
28+
public function supportsNormalization($data, $format = null)
29+
{
30+
return $data instanceof EnvelopedMessage;
31+
}
32+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.