Search code examples
symfonyxmlserializerserialization

How do I control tag ordering when using Symfony Serialiser to create XML?


I have the following test case but I'm finding that the XML sections don't come out in the order I require. The SOAP API i'm using issued this message:

<Search Type="Error">
    <ErrorCode>150</ErrorCode>
    <ErrorMessage>Invalid Entry : Invalid content was found starting with element &apos;Telephones&apos;. One of &apos;{Addresses}&apos; is expected. </ErrorMessage>
</Search 

If I edit the XML and submit it with Address first, everyone is happy.

So I need the Address tag to precede the Numbers tag. Would this be something I'd have to control in the Normalizing stage, or the XmlEncoding stage?

Can anyone explain how I do it using the simple example below where I could get the Address tag to precede the Numbers tag in the generated xml?

<?php
/**
* Symfony Serializer experiment  
*/
require $_SERVER['DOCUMENT_ROOT'].'/libraries/symfony/vendor/autoload.php';

use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;

Class Number {
    private $Number;

    // Getters    
    public function getNumber()
    {
        return $this->Number;
    }
    // Setters    
    public function setNumber($number)
    {
        $this->Number = $number;

        return $this;  // for chaining
    }    

}

Class Address {
    private $Premise;
    Private $Postcode;

    // Getters    
    public function getPremise()
    {
        return $this->Premise;
    }
    public function getPostcode()
    {
        return $this->Postcode;
    }    
    // Setters    
    public function setPremise($premise)
    {
        $this->Premise = $premise;

        return $this; // for chaining
    }
    public function setPostcode($postcode)
    {
        $this->Postcode = $postcode;

        return $this; // for chaining
    }              
} 

class Contact
{
    private $Name;
    private $Numbers;
    private $Address;

    // Getters
    public function getName()
    {
        return $this->Name;
    }
    public function getNumbers()
    {
        return $this->Numbers;
    }
    public function getAddress()
    {
        return $this->Address;
    }        
    // Setters
    public function setName($name)
    {
        $this->Name = $name;

        return $this;  // for chaining       
    }
    public function setNumbers($numbers)
    {
        $this->Numbers = $numbers;

        return $this;  // for chaining       
    }
    public function setAddress($address)
    {
        $this->Address = $address;

        return $this;  // for chaining       
    }    
    public function addNumber($number) {
        $this->Numbers['Number'][] = $number;

        return $this;  // for chaining
    }    
}

// Serializer setup

// The default Root tag for the XmlEncode is <response>. Not what we want so let's change it to <Contact>
$xmlEncoder = new XmlEncoder();
$xmlEncoder->setRootNodeName('Contact');                
$encoders = array($xmlEncoder, new JsonEncoder());       
$normalizers = array(new ObjectNormalizer(), new ArrayDenormalizer());
$serializer = new Serializer($normalizers, $encoders);
// The default Root tag for the XmlEncode is <response>. Not what we want so let's change it to <Contact>
$xmlEncoder = new XmlEncoder();
$xmlEncoder->setRootNodeName('Contact');                
$encoders = array($xmlEncoder, new JsonEncoder());
$normalizers = array(new ObjectNormalizer(), new ArrayDenormalizer());
$serializer = new Serializer($normalizers, $encoders);

// First test we can get from php data structure to xml

$address = new Address();
$address->setPremise('11')
->setPostcode('ZZ99 9ZZ');

$contact = new Contact();
$contact->setName('baa')
->addNumber('9876543210')
->addNumber('9876543212')
->setAddress($address);

$xmlContent = $serializer->serialize($contact, 'xml');

echo "<pre lang=xml>";
echo htmlentities($xmlContent);
echo "</pre><br><br>";     

// Next lets test we can get from xml to php

$data = <<<EOF
<Contact>
    <Name>foo</Name>
    <Address>
        <Premise>12</Premise>
        <Postcode>YY88 8YY</Postcode>
    </Address>
    <Numbers>
        <Number>02378415326</Number>
        <Number>07865412354</Number>        
    </Numbers>        
</Contact>
EOF;

//$jsonContent = $serializer->serialize($searchId, 'json');

$contact = $serializer->deserialize($data, Contact::class, 'xml');

$contact->addNumber('01234567890');

$xmlContent = $serializer->serialize($contact, 'xml');

echo "<pre lang=xml>";
echo htmlentities($xmlContent);
echo "</pre>";     
    ?>

Output:

<Contact>
    <Name>baa</Name>
    <Numbers>
        <Number>9876543210</Number>
        <Number>9876543212</Number>
    </Numbers>
    <Address>
        <Premise>11</Premise>
        <Postcode>ZZ99 9ZZ</Postcode>
    </Address>
</Contact>

<?xml version="1.0"?>
<Contact>
    <Name>foo</Name>
    <Numbers>
        <Number>02378415326</Number>
        <Number>07865412354</Number>
        <Number>01234567890</Number>
    </Numbers>
    <Address>
        <Premise>12</Premise>
        <Postcode>YY88 8YY</Postcode>
    </Address>
</Contact>

Solution

  • Quick and dirty:

    All you need to do is change the order of properties and/or getter methods in your Contact class (depending on your property accessor type).

    Now clear your serializer- and/or opcache-cache and enjoy the changed order in the XML output.