phpxmlrecursionmultidimensional-array

XML with attributes to array in PHP


I am trying to convert an XML string into multi-dimensioned PHP array. The difficulties are that XML comes with attributes and has nested values. My code works at parent level data but I am not sure how to deal with sub-levels recursively.

A sample XML is as follows:

<root>
    <item id="1" name="ItemOne">Item Value 1</item>
    <item id="2" name="ItemTwo">
        <subb>Sub Value 1</subb>
        <subb>Sub Value 2</subb>
    </item>
    <item id="3" name="ItemThree">Value 3</item>
    <something>something value</something>
</root>

This is my current function to achieve this:

$xmlString = '<root><item id="1" name="ItemOne">Item Value 1</item><item id="2" name="ItemTwo"><subb>Sub Value 1</subb><subb>Sub Value 2</subb></item><item id="3" name="ItemThree">Value 3</item><something>something value</something></root>';

function xmlToArray($xmlObject) {
    $array = [];
    foreach ($xmlObject as $item) {
        // Convert attributes to an array
        $attributes = (array)$item->attributes();
        // Add the text value to the attributes array if it exists
        $attributes['@value'] = trim((string)$item);
        $key = (string)$item->getName();
        if (isset($make_sub_array) && in_array($key, $make_sub_array)) {
            $array[$key][] = $attributes;
        }
        elseif (isset($array[$key])) {
            $make_sub_array[] = $key;
            $tmp = $array[$key];
            unset($array[$key]);
            $array[$key][] = $tmp; //existing data
            $array[$key][] = $attributes; //this data
        }
        else $array[$key] = $attributes;
    }
    return $array;
}
// Load the XML string into a SimpleXMLElement object
$xmlObject = simplexml_load_string($xmlString);
$array = xmlToArray($xmlObject);
exit('<pre>'.print_r($array,1).'</pre>');

The resulting array structure is below, and I require your help about how I can process the array under second item. I would like it to be processed the same way as the parent one: if the item name is repeated then it will be included as [] so I get number as its parent, otherwise [itemname]. Thank you

Array
(
    [item] => Array
        (
            [0] => Array
                (
                    [@attributes] => Array
                        (
                            [id] => 1
                            [name] => ItemOne
                        )
                    [@value] => Item Value 1
                )
            [1] => Array
                (
                    [@attributes] => Array
                        (
                            [id] => 2
                            [name] => ItemTwo
                        )
                    [@value] => 
                )
            [2] => Array
                (
                    [@attributes] => Array
                        (
                            [id] => 3
                            [name] => ItemThree
                        )

                    [@value] => Value 3
                )
        )
    [something] => Array
        (
            [@value] => something value
        )
)

Solution

  • If you replace your:

    $attributes['@value'] = trim((string)$item);
    

    by:

    $attributes['@value'] = count($item->children()) ? xmlToArray($item) : trim((string)$item);
    

    you'll get what you want:

    <pre>Array
    (
        [item] => Array
            (
                [0] => Array
                    (
                        [@attributes] => Array
                            (
                                [id] => 1
                                [name] => ItemOne
                            )
    
                        [@value] => Item Value 1
                    )
    
                [1] => Array
                    (
                        [@attributes] => Array
                            (
                                [id] => 2
                                [name] => ItemTwo
                            )
    
                        [@value] => Array
                            (
                                [subb] => Array
                                    (
                                        [0] => Array
                                            (
                                                [@value] => Sub Value 1
                                            )
    
                                        [1] => Array
                                            (
                                                [@value] => Sub Value 2
                                            )
    
                                    )
    
                            )
    
                    )
    
                [2] => Array
                    (
                        [@attributes] => Array
                            (
                                [id] => 3
                                [name] => ItemThree
                            )
    
                        [@value] => Value 3
                    )
    
            )
    
        [something] => Array
            (
                [@value] => something value
            )
    
    )
    </pre>
    

    Note that hasChildren() will consistently return false, thus the test with count($item->children()).