Unable to Serialize A Multi-Dimensional Array From PHP to XML

Viewed 378

I am testing this webservice by sending a SOAP request through SoapUi.

I currently have this PHP array:

array(7) {
  ["name"]=>
  string(9) "John Doe"
  ["date"]=>
  string(23) "2021-11-30 00:00:00.000"
  ["job"]=>
  string(31) "developer"
  ["where_from"]=>
  string(15) "france"
  ["address"]=>
  array(3) {
    ["country"]=>
    string(15) "france"
    ["city"]=>
    string(10) "paris"
    ["vat_number"]=>
    string(1) "123456"
  }
  ["items"]=>
  array(1) {
    [0]=>
    array(2) {
      ["cook"]=>
      string(7) "spoon"
      ["clean"]=>
      string(14) "vacuum"
    }
  }
}

I am trying to convert it to XML with:

function convertToXml($data, $name='root', &$doc=null, &$node=null){
    if ($doc==null){
        $doc = new DOMDocument('1.0','UTF-8');
        $doc->formatOutput = TRUE;
        $node = $doc;
    }

    if (is_array($data)){
        foreach($data as $var=>$val){
            if (is_numeric($var)){
                convertToXml($val, $name, $doc, $node);
            }else{
                if (!isset($child)){
                    $child = $doc->createElement($name);
                    $node->appendChild($child);
                }

                convertToXml($val, $var, $doc, $child);
            }
        }
    }else{
        $child = $doc->createElement($name);
        $node->appendChild($child);
        $textNode = $doc->createTextNode($data);
        $child->appendChild($textNode);
}

    if ($doc==$node) return $doc->saveXML();
}

However, I am getting this response in SOAPUI:

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
   <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
         <faultcode xsi:type="xsd:string">SOAP-ENV:Server</faultcode>
         <faultactor xsi:type="xsd:string"/>
         <faultstring xsi:type="xsd:string">unable to serialize result</faultstring>
         <detail xsi:type="xsd:string"/>
      </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

I have tried with a smaller array and it works, but with this one it is not working as expected.

Can anyone help?

------------------------ UPDATE--------------------------------

Response I expect to get:

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://ex.pt/soap/WebServices">
   <SOAP-ENV:Body>
      <ns1:Person xmlns:ns1="https://ex.pt/webservices">
         
         <data xsi:type="tns:getPersonInfo">
            <name xsi:type="xsd:string">John</name>
            <surname xsi:type="xsd:string">Doe</surname>
            <job xsi:type="xsd:string">developer</job>
            <from xsi:type="xsd:string">france</from>
            <address xsi:type="tns:getAddress">
               <country xsi:type="xsd:string">france</country>
               <city xsi:type="xsd:string">paris</city>
               <post_code xsi:type="xsd:string">12345</post_code>
            </address>
            <items xsi:type="tns:getItems">
               <item xsi:type="xsd:string">
                  <name xsi:type="xsd:string">pillow</name>
                  <material xsi:type="xsd:string">cotton</material>
               </item>
               .... other items
            </items>
         </data>
      </ns1:Person>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

How it is retrieving the response:

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://ex.pt/soap/WebServices">
       <SOAP-ENV:Body>
          <ns1:Person xmlns:ns1="https://ex.pt/webservices">
             
             <data xsi:type="tns:getPersonInfo">
                <name xsi:type="xsd:string">John</name>
                <surname xsi:type="xsd:string">Doe</surname>
                <job xsi:type="xsd:string">developer</job>
                <from xsi:type="xsd:string">france</from>
                <address xsi:type="tns:getAddress">
                   <country xsi:type="xsd:string">france</country>
                   <city xsi:type="xsd:string">paris</city>
                   <post_code xsi:type="xsd:string">12345</post_code>
                </address>
                <items xsi:type="tns:getItems"/>
             </data>
          </ns1:Person>
       </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>

xml schema for "items"

<part name="items" type="tns:getItems"/>


<xsd:complexType name="getItems">
  <xsd:complexContent>
     <xsd:restriction base="SOAP-ENC:Array">
        <xsd:attribute ref="SOAP-ENC:arrayType wsdl:arrayType="tns:ItemInfo[]"/>
     </xsd:restriction>
  </xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="ItemInfo">
   <xsd:all>
     <xsd:element name="name" type="xsd:string"/>
     <xsd:element name="material" type="xsd:string"/>
  </xsd:all>
</xsd:complexType>
    
4 Answers

Not sure this is an elegant way to do but it might help you to get some idea to get a soap response as you mentioned.First you have to build array like below

$data = [
        'SOAP-ENV:Body' => [
            'ns1:Person' => [
                'info' => [
                    "name" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "John Doe"],
                    "surname" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "Doe"],
                    "job" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "developer"],
                    "from" =>['_attributes' => ['xsi:type' => 'xsd:string'], '_value' =>  "france"],
                    "address" => [
                        "country" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "france"],
                        "city" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "paris"],
                        "post_code" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "123456"],
                        '_attributes' => ['xsi:type' => 'tns:getAddress']
                    ],
                    "items" => [
                        '__custom:item:1' => [
                            "name" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "spoon"],
                            "material" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "vacuum"],
                        ],
                        '_attributes' => ['xsi:type' => 'tns:getItems']
                    ],
                    '_attributes' => ['xsi:type' => 'tns:getPersonInfo',]
                ],
                '_attributes' => ['xmlns:ns1' => 'https://ex.pt/webservices',]
            ],
        ]

    ];

use package array-to-xml ref:https://github.com/spatie/array-to-xml

    $response = ArrayToXml::convert($data, [
        'rootElementName' => 'SOAP-ENV:Envelope',
        '_attributes' => [
            'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/',
            'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
            'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
            'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
            'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
            'xmlns:tns' => 'http://ex.pt/soap/WebServices'
        ],
    ], true, 'UTF-8');

Lets try slightly different approach to your problem. If we assume that you are sending correct xml structure to the service and get correct response from it(copied your example from post). Then we can reverse it to array look like with some extra php.

<?php

$xml =<<<XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://ex.pt/soap/WebServices">
   <SOAP-ENV:Body>
      <ns1:Person xmlns:ns1="https://ex.pt/webservices">
         <data xsi:type="tns:getPersonInfo">
            <name xsi:type="xsd:string">John</name>
            <surname xsi:type="xsd:string">Doe</surname>
            <job xsi:type="xsd:string">developer</job>
            <from xsi:type="xsd:string">france</from>
            <address xsi:type="tns:getAddress">
               <country xsi:type="xsd:string">france</country>
               <city xsi:type="xsd:string">paris</city>
               <post_code xsi:type="xsd:string">12345</post_code>
            </address>
            <items xsi:type="tns:getItems">
               <item xsi:type="tns:ItemInfo">
                  <name xsi:type="xsd:string">pillow_1</name>
                  <material xsi:type="xsd:string">cotton</material>
               </item>
               <item xsi:type="tns:ItemInfo">
                  <name xsi:type="xsd:string">pillow_2</name>
                  <material xsi:type="xsd:string">cotton_2</material>
               </item>
            </items>
         </data>
      </ns1:Person>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;

$response = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", $xml);
$xml = new SimpleXMLElement($response);
$body = $xml->xpath('//SOAP-ENV:Body')[0];
$array = json_decode(json_encode((array)$body), TRUE); 
print_r($array);

Output will be next

Array
(
    [ns1Person] => Array
        (
            [data] => Array
                (
                    [name] => John
                    [surname] => Doe
                    [job] => developer
                    [from] => france
                    [address] => Array
                        (
                            [country] => france
                            [city] => paris
                            [post_code] => 12345
                        )

                    [items] => Array
                        (
                            [item] => Array
                                (
                                    [0] => Array
                                        (
                                            [name] => pillow_1
                                            [material] => cotton
                                        )

                                    [1] => Array
                                        (
                                            [name] => pillow_2
                                            [material] => cotton_2
                                        )

                                )

                        )

                )

        )

)

Then pass this array to your xml generator and check for meaningful response. You can skip ns1Person and do as you did before.

Otherwise refer to previous answer and try to make some kind of envelope with correct body according to your wsdl schema.

One thing worth mentioning is that you are expecting wrong type here <item xsi:type="xsd:string">

<items xsi:type="tns:getItems">
    <item xsi:type="xsd:string">
        <name xsi:type="xsd:string">pillow</name>
        <material xsi:type="xsd:string">cotton</material>
    </item>
    .... other items
</items> 

item itself is an array according to your wsdl schema. Type example is in the code above.

just in case, code for parsing xml was taken from here

Actually it would be great if you could provide an example of your wsdl schema(it can be something close to what you did), so that people who have time can mock Client\Server comms and check if it works correctly.

Hope that something from what was stated before will help)

Updated and simpler answer

From sergey-ligus answer, you need to wrap your items elements in a child element item without changing your function. so your array would be something like:

$test = array(
    'name'  => 'John Doe',
    'date'  => '2021-11-30 00:00:00.000',
    'job'   => 'developer',
    'where_from'    => 'france',
    'address'   => array(
        'country'   => 'france',
        'city'  => 'paris',
        'vat_number'    => '123456',
    ),
    'items' => array(
        'item'  => array(
            array(
                'cook'  => 'spoon',
                'clean' => 'vacuum',
            ),
            array(
                'cook'  => 'spoon2',
                'clean' => 'vacuum2',
            ),
            array(
                'cook'  => 'spoon3',
                'clean' => 'vacuum3',
            ),
        ),
    ),
);

Original answer

in teams of building your `XML` structure, you have two issues that you need to solve: 1. when your element is array, you should create `$child` node to hold array's elements. 2. in your code you are not handling the new node name for elements with numeric keys.

I assume that your original array should be something like this:

$test = array(
    'name'  => 'John Doe',
    'date'  => '2021-11-30 00:00:00.000',
    'job'   => 'developer',
    'where_from'    => 'france',
    'address'   => array(
        'country'   => 'france',
        'city'  => 'paris',
        'vat_number'    => '123456',
    ),
    'items' => array(
        array(
            'nodeName'  => 'item', // added for solution no. 2
            'cook'  => 'spoon',
            'clean' => 'vacuum',
        ),
        array(
            'nodeName'  => 'item', // added for solution no. 2
            'cook'  => 'spoon2',
            'clean' => 'vacuum2',
        ),
        array(
            'nodeName'  => 'item', // added for solution no. 2
            'cook'  => 'spoon3',
            'clean' => 'vacuum3',
        ),
    ),
);
You should fix your creation function like this:
function convertToXml($data, $name='root', &$doc=null, &$node=null){
    if ($doc==null){
        $doc = new DOMDocument('1.0','UTF-8');
        $doc->formatOutput = TRUE;
        $node = $doc;
    }

    if (is_array($data)){
        foreach($data as $var=>$val){
            // EDIT: create your "$child" node when you have an array $data
            if (!isset($child)){
                $child = $doc->createElement($name);
                $node->appendChild($child);
            }

            // EDIT: need solutions for numeric keys
            if (is_numeric($var)){
                // EDIT: first solution: assuming that the parent key contain "s" at the end, so we will remove it for the child node (items => item)
                $namex = substr($name, 0, -1);

                // EDIT: second solution: assuming that you have a 'nodeName' element in your array, so we will get its value and use it as child node name ($val['nodeName'] value) ONLY IN CASE $val IS AN ARRAY. you may need to use is_array($val) and isset($val['nodeName']) for this solution
                $namex = $val['nodeName'];
                unset( $val['nodeName'] );

                // EDIT: using the new node name "$namex" instead parent node name "$name"
                // EDIT: passing "$child" instead of "$node" to the function
                convertToXml($val, $namex, $doc, $child);
            }else{
                convertToXml($val, $var, $doc, $child);
            }
        }
    }else{
        $child = $doc->createElement($name);
        $node->appendChild($child);
        $textNode = $doc->createTextNode($data);
        $child->appendChild($textNode);
    }

    if ($doc==$node) return $doc->saveXML();
}

I used this simple function, credit to Koding Made Simple/Valli


<?php
// function to convert multi-dimensional array to xml
function Array2XML($obj, $array)
{
    foreach ($array as $key => $value)
    {
        if(is_numeric($key))
            $key = 'item' . $key;

        if (is_array($value))
        {
            $node = $obj->addChild($key);
            array2XML($node, $value);
        }
        else
        {
            $obj->addChild($key, htmlspecialchars($value));
        }
    }
}



// Sample Array    
// define php multi-dimensional array


$my_array = array (
    '0' => array (
        'id' => 'XYZ100',
        'personal' => array (
            'name' =>'Ashton Cox',
            'gender' => 'Male',
            'age' => 32,
            'address' => array (
                 'street' => '7 24th Street',
                 'city' => 'New York',
                 'state' => 'NY',
                 'zipcode' => '10038'
             )
        ),
        'profile' => array (
            'position' => 'Team Lead',
            'department' => 'Software'
        )
    ),
    '1' => array (
        'id' => 'XYZ121',
        'personal' => array (
            'name' => 'Rhona Davidson',
            'gender' => 'Female',
            'age' => 40,
            'address' => array (
                 'street' => 'S2 115th Street',
                 'city' => 'New York',
                 'state' => 'NY',
                 'zipcode' => '10100'
             )
        ),
        'profile' => array (
            'position' => 'Integration Specialist',
            'department' => 'Operations'
        )
    )
);


// create new instance of simplexml
$xml = new SimpleXMLElement('<root/>');

// function callback
array2XML($xml, $my_array);

// save as xml file
echo (($xml->asXML('data.xml')) ? 'Your XML file has been generated successfully!' : 'Error generating XML file!');
?>
Related