* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Serializer\Tests\Encoder; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy; class XmlEncoderTest extends TestCase { /** * @var XmlEncoder */ private $encoder; private $exampleDateTimeString = '2017-02-19T15:16:08+0300'; protected function setUp() { $this->encoder = new XmlEncoder(); $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); $this->encoder->setSerializer($serializer); } public function testEncodeScalar() { $obj = new ScalarDummy(); $obj->xmlFoo = 'foo'; $expected = ''."\n". 'foo'."\n"; $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); } public function testSetRootNodeName() { $obj = new ScalarDummy(); $obj->xmlFoo = 'foo'; $this->encoder->setRootNodeName('test'); $expected = ''."\n". 'foo'."\n"; $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); } public function testDocTypeIsNotAllowed() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $this->expectExceptionMessage('Document types are not allowed.'); $this->encoder->decode('', 'foo'); } public function testAttributes() { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => [ '@id' => 1, '@name' => 'Bar', ], 'Foo' => [ 'Bar' => 'Test', '@Type' => 'test', ], 'föo_bär' => 'a', 'Bar' => [1, 2, 3], 'a' => 'b', ]; $expected = ''."\n". ''. ''. 'Test'. 'a'. '1'. '2'. '3'. 'b'. ''."\n"; $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); } public function testElementNameValid() { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => 'a', 'foo_bar' => 'a', 'föo_bär' => 'a', ]; $expected = ''."\n". ''. 'a'. 'a'. 'a'. ''."\n"; $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); } public function testEncodeSimpleXML() { $xml = simplexml_load_string('Peter'); $array = ['person' => $xml]; $expected = ''."\n". 'Peter'."\n"; $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } public function testEncodeXmlAttributes() { $xml = simplexml_load_string('Peter'); $array = ['person' => $xml]; $expected = ''."\n". 'Peter'."\n"; $context = [ 'xml_version' => '1.1', 'xml_encoding' => 'utf-8', 'xml_standalone' => true, ]; $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); } public function testEncodeRemovingEmptyTags() { $array = ['person' => ['firstname' => 'Peter', 'lastname' => null]]; $expected = ''."\n". 'Peter'."\n"; $context = ['remove_empty_tags' => true]; $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); } public function testEncodeNotRemovingEmptyTags() { $array = ['person' => ['firstname' => 'Peter', 'lastname' => null]]; $expected = ''."\n". 'Peter'."\n"; $this->assertSame($expected, $this->encoder->encode($array, 'xml')); } public function testContext() { $array = ['person' => ['name' => 'George Abitbol']]; $expected = <<<'XML' George Abitbol XML; $context = [ 'xml_format_output' => true, ]; $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); } public function testEncodeScalarRootAttributes() { $array = [ '#' => 'Paul', '@eye-color' => 'brown', ]; $expected = ''."\n". 'Paul'."\n"; $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } public function testEncodeRootAttributes() { $array = [ 'firstname' => 'Paul', '@eye-color' => 'brown', ]; $expected = ''."\n". 'Paul'."\n"; $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } public function testEncodeCdataWrapping() { $array = [ 'firstname' => 'Paul ', ]; $expected = ''."\n". ']]>'."\n"; $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } public function testEncodeScalarWithAttribute() { $array = [ 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], ]; $expected = ''."\n". 'Peter'."\n"; $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } public function testDecodeScalar() { $source = ''."\n". 'foo'."\n"; $this->assertEquals('foo', $this->encoder->decode($source, 'xml')); } public function testDecodeBigDigitAttributes() { $source = << Name XML; $this->assertSame(['@index' => 182077241760011681341821060401202210011000045913000000017100, '#' => 'Name'], $this->encoder->decode($source, 'xml')); } public function testDecodeNegativeIntAttribute() { $source = << Name XML; $this->assertSame(['@index' => -1234, '#' => 'Name'], $this->encoder->decode($source, 'xml')); } public function testDecodeFloatAttribute() { $source = << Name XML; $this->assertSame(['@index' => 12.11, '#' => 'Name'], $this->encoder->decode($source, 'xml')); } public function testDecodeNegativeFloatAttribute() { $source = << Name XML; $this->assertSame(['@index' => -12.11, '#' => 'Name'], $this->encoder->decode($source, 'xml')); } public function testDecodeFloatAttributeWithZeroWholeNumber() { $source = << Name XML; $this->assertSame(['@index' => 0.123, '#' => 'Name'], $this->encoder->decode($source, 'xml')); } public function testNoTypeCastAttribute() { $source = << XML; $data = $this->encoder->decode($source, 'xml', ['xml_type_cast_attributes' => false]); $expected = [ '@a' => '018', '@b' => '-12.11', 'node' => [ '@a' => '018', '@b' => '-12.11', '#' => '', ], ]; $this->assertSame($expected, $data); } public function testDoesNotTypeCastStringsStartingWith0() { $source = << XML; $data = $this->encoder->decode($source, 'xml'); $this->assertSame('018', $data['@a']); } public function testEncode() { $source = $this->getXmlSource(); $obj = $this->getObject(); $this->assertEquals($source, $this->encoder->encode($obj, 'xml')); } public function testEncodeWithNamespace() { $source = $this->getNamespacedXmlSource(); $array = $this->getNamespacedArray(); $this->assertEquals($source, $this->encoder->encode($array, 'xml')); } public function testEncodeSerializerXmlRootNodeNameOption() { $options = ['xml_root_node_name' => 'test']; $this->encoder = new XmlEncoder(); $serializer = new Serializer([], ['xml' => new XmlEncoder()]); $this->encoder->setSerializer($serializer); $array = [ 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], ]; $expected = ''."\n". 'Peter'."\n"; $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options)); } public function testEncodeTraversableWhenNormalizable() { $this->encoder = new XmlEncoder(); $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); $this->encoder->setSerializer($serializer); $expected = <<<'XML' normalizedFoonormalizedBar XML; $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml')); } public function testDecode() { $source = $this->getXmlSource(); $obj = $this->getObject(); $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml')); } public function testDecodeCdataWrapping() { $expected = [ 'firstname' => 'Paul ', ]; $xml = ''."\n". ']]>'."\n"; $this->assertEquals($expected, $this->encoder->decode($xml, 'xml')); } public function testDecodeCdataWrappingAndWhitespace() { $expected = [ 'firstname' => 'Paul ', ]; $xml = ''."\n". ''."\n". ']]>'."\n"; $this->assertEquals($expected, $this->encoder->decode($xml, 'xml')); } public function testDecodeWithNamespace() { $source = $this->getNamespacedXmlSource(); $array = $this->getNamespacedArray(); $this->assertEquals($array, $this->encoder->decode($source, 'xml')); } public function testDecodeScalarWithAttribute() { $source = ''."\n". 'Peter'."\n"; $expected = [ 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], ]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); } public function testDecodeScalarRootAttributes() { $source = ''."\n". 'Peter'."\n"; $expected = [ '#' => 'Peter', '@eye-color' => 'brown', ]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); } public function testDecodeRootAttributes() { $source = ''."\n". 'PeterMac Calloway'."\n"; $expected = [ 'firstname' => 'Peter', 'lastname' => 'Mac Calloway', '@eye-color' => 'brown', ]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); } public function testDecodeArray() { $source = ''."\n". ''. ''. 'BenjaminAlexandre'. 'DamienClay'. ''. ''."\n"; $expected = [ 'people' => ['person' => [ ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], ['firstname' => 'Damien', 'lastname' => 'Clay'], ]], ]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); } public function testDecodeXMLWithProcessInstruction() { $source = <<<'XML' foo a b val val bar title1 title2 Ed 1 ?> XML; $obj = $this->getObject(); $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml')); } public function testDecodeIgnoreWhiteSpace() { $source = <<<'XML' Benjamin Alexandre Damien Clay XML; $expected = ['person' => [ ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); } public function testDecodeWithoutItemHash() { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => [ '@key' => 'value', 'item' => ['@key' => 'key', 'key-val' => 'val'], ], 'Foo' => [ 'Bar' => 'Test', '@Type' => 'test', ], 'föo_bär' => 'a', 'Bar' => [1, 2, 3], 'a' => 'b', ]; $expected = [ 'foo-bar' => [ '@key' => 'value', 'key' => ['@key' => 'key', 'key-val' => 'val'], ], 'Foo' => [ 'Bar' => 'Test', '@Type' => 'test', ], 'föo_bär' => 'a', 'Bar' => [1, 2, 3], 'a' => 'b', ]; $xml = $this->encoder->encode($obj, 'xml'); $this->assertEquals($expected, $this->encoder->decode($xml, 'xml')); } public function testDecodeInvalidXml() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $this->encoder->decode('', 'xml'); } public function testPreventsComplexExternalEntities() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $this->encoder->decode(']>&test;', 'xml'); } public function testDecodeEmptyXml() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $this->expectExceptionMessage('Invalid XML data, it can not be empty.'); $this->encoder->decode(' ', 'xml'); } protected function getXmlSource() { return ''."\n". ''. 'foo'. 'ab'. 'valvalbar'. 'title1title2'. 'Ed'. '1'. ''."\n"; } protected function getNamespacedXmlSource() { return ''."\n". ''. '1'. 'foo'. 'ab'. 'valvalbar'. 'title1title2'. 'Ed'. ''."\n"; } protected function getNamespacedArray() { return [ '@xmlns' => 'http://www.w3.org/2005/Atom', '@xmlns:app' => 'http://www.w3.org/2007/app', '@xmlns:media' => 'http://search.yahoo.com/mrss/', '@xmlns:gd' => 'http://schemas.google.com/g/2005', '@xmlns:yt' => 'http://gdata.youtube.com/schemas/2007', 'qux' => '1', 'app:foo' => 'foo', 'yt:bar' => ['a', 'b'], 'media:baz' => [ 'media:key' => 'val', 'media:key2' => 'val', 'A B' => 'bar', 'item' => [ [ 'title' => 'title1', ], [ 'title' => 'title2', ], ], 'Barry' => [ '@size' => 'large', 'FooBar' => [ 'Baz' => 'Ed', '@gd:id' => 1, ], ], ], ]; } protected function getObject() { $obj = new Dummy(); $obj->foo = 'foo'; $obj->bar = ['a', 'b']; $obj->baz = ['key' => 'val', 'key2' => 'val', 'A B' => 'bar', 'item' => [['title' => 'title1'], ['title' => 'title2']], 'Barry' => ['FooBar' => ['Baz' => 'Ed', '@id' => 1]]]; $obj->qux = '1'; return $obj; } public function testEncodeXmlWithBoolValue() { $expectedXml = <<<'XML' 10 XML; $actualXml = $this->encoder->encode(['foo' => true, 'bar' => false], 'xml'); $this->assertEquals($expectedXml, $actualXml); } public function testEncodeXmlWithDomNodeValue() { $expectedXml = <<<'XML' barfoo & bar XML; $document = new \DOMDocument(); $actualXml = $this->encoder->encode(['foo' => $document->createTextNode('bar'), 'bar' => $document->createTextNode('foo & bar')], 'xml'); $this->assertEquals($expectedXml, $actualXml); } public function testEncodeXmlWithDateTimeObjectValue() { $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer(); $actualXml = $xmlEncoder->encode(['dateTime' => new \DateTime($this->exampleDateTimeString)], 'xml'); $this->assertEquals($this->createXmlWithDateTime(), $actualXml); } public function testEncodeXmlWithDateTimeObjectField() { $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer(); $actualXml = $xmlEncoder->encode(['foo' => ['@dateTime' => new \DateTime($this->exampleDateTimeString)]], 'xml'); $this->assertEquals($this->createXmlWithDateTimeField(), $actualXml); } public function testNotEncodableValueExceptionMessageForAResource() { $this->expectException(NotEncodableValueException::class); $this->expectExceptionMessage('An unexpected value could not be serialized: stream resource'); (new XmlEncoder())->encode(tmpfile(), 'xml'); } /** * @return XmlEncoder */ private function createXmlEncoderWithDateTimeNormalizer() { $encoder = new XmlEncoder(); $serializer = new Serializer([$this->createMockDateTimeNormalizer()], ['xml' => new XmlEncoder()]); $encoder->setSerializer($serializer); return $encoder; } /** * @return MockObject|NormalizerInterface */ private function createMockDateTimeNormalizer() { $mock = $this->getMockBuilder('\Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock(); $mock ->expects($this->once()) ->method('normalize') ->with(new \DateTime($this->exampleDateTimeString), 'xml', []) ->willReturn($this->exampleDateTimeString); $mock ->expects($this->once()) ->method('supportsNormalization') ->with(new \DateTime($this->exampleDateTimeString), 'xml') ->willReturn(true); return $mock; } /** * @return string */ private function createXmlWithDateTime() { return sprintf(' %s ', $this->exampleDateTimeString); } /** * @return string */ private function createXmlWithDateTimeField() { return sprintf(' ', $this->exampleDateTimeString); } }