Hướng dẫn php xml to array

I know I'm 1 billion years late, but I had the same problem as you and needed a more complex solution, so here is a function (xml_decode()) I made to convert SimpleXMLElements into PHP arrays without losing attributes and with arguments for a more customizable use.

The val() function is meant for you to customize how element values shall be treated - in case you want to transform true into true instead of "true" for example.

Disclaimer: I know it is easier to just use the PHP SimpleXML extension, but I needed to transform lots of XML files into JSON files for a big change in a project of mine. Also, the question is about how to transform XML to PHP arrays, not how to use XML in PHP.


 * 
 * If true, xml_decode() will output
 * array("attributes" => array("foo" => "true", "bar" => "false"))
 * 
 * If false, xml_decode() will output
 * array("foo" => "true", "bar" => "false")
 * 
 * @param bool $reduce If unecessary keys created due to XML structure should be eliminated.
 * 
 * Example: applebanana
 * 
 * If true, xml_decode() will output the element as
 * array("fruits" => array(0 => "apple", 1 => "banana"))
 * 
 * If false, xml_decode() will output the element as
 * array("fruits" => array("fruit" => array(0 => "apple", 1 => "banana")))
 * 
 * @param array $always_array List of which childs should be treated aways as an array.
 * 
 * Example: apple
 * 
 * If array("fruit") is passed as $aways_array, xml_decode() will output the element as
 * array("fruits" => array("fruit" => array(0 => "apple")))
 * 
 * If not, xml_decode() will output the element as
 * array("fruits" => array("fruit" => "apple"))
 * 
 * @param array $value_keys List of custom element's value names. This argument is only
 * used when values need to passed as elements because of attributes or other reasons.
 * 
 * The default value key name is "value".
 * 
 * Example: apple
 * 
 * If array("fruit" => "name) is passed as $value_keys, xml_decode() will output the element as
 * array("fruits" => array("fruit" => array("attributes" => array("id" => "123"), "name" => "apple")))
 * 
 * If not, xml_decode() will output the element as
 * array("fruits" => array("fruit" => array("attributes" => array("id" => "123"), "value" => "apple")))
 */
function xml_decode(SimpleXMLElement $xml, bool $attributes_key = true, bool $reduce = true,
    array $always_array = array(), array $value_keys = array()): string|array {

    // Inicialize the array.
    $arr = array();

    // XML tag name.
    $xml_name = $xml->getName();

    // Turn attributes into elements.
    foreach ($xml->attributes() as $key => $value) {
        // Use a key for attributes if $attributes_key argument is true.
        if ($attributes_key) {
            $arr['attributes'][val($key)] = val($value);
        } else {
            $arr[val($key)] = val($value);
        }
    }

    // Count children.
    $children_count = $xml->children()->count();

    // No children? Value will be text.
    if ($children_count == 0) {

        // If attributes were found and turned into elements
        // the value shall be an element.
        if (count($arr) > 0) {
            // If attributes were found previosly.
            $key = $value_keys[$xml_name] ?? $value_keys['*'] ?? "value";
            $arr[$key] = val($xml);
        // Else, no need for an array.
        } else {
            $arr = val($xml);
        }

    // Children? Loop continues.
    } else {

        // Defines if there are unecessary array keys - due to the XML structure - to be cut.
        // Example: 
        // could be turned into arr['fruits'][0] and arr['fruits'][1] instead of
        // arr['fruits']['fruit'][0] and arr['fruits']['fruit'][1] for a
        // cleaner organization.
        $children_names = array();
        foreach ($xml->children() as $child) {
            $child_name = $child->getName();
            in_array($child_name, $children_names) or $children_names[] = $child_name;
        }
        $reducible = empty($arr) && count($children_names) === 1;

        foreach ($xml->children() as $child) {

            // Child's name shall be the element key.
            $name = $child->getName();
            
            // Children with the same name will be turned into a list.
            // Example: $arr['repeating-child'][...] = $value;
            if ($xml->$name->count() > 1 || in_array($name, $always_array)) {

                // Reduction, if possible and requested by the $reduce argument.
                if ($reduce && $reducible) {
                    $arr[] = xml_decode($child, $attributes_key, $reduce, $always_array, $value_keys);
                } else {
                    $arr[$name][] = xml_decode($child, $attributes_key, $reduce, $always_array, $value_keys);
                }

            // Normal children will be normally decoded.
            // Example: $arr['no-repeating-child] = $value;
            } else {
                
                $arr[$name] = xml_decode($child, $attributes_key, $reduce, $always_array, $value_keys);

            }
        }
    }

    return $arr;

}

Resuming all the documentation and comments, the function transforms attributes and elements values into simple array elements and uses a loop with itself to process elements which contain children.

The arguments allow you to:

  • Group attributes into separate keys;
  • Cut unecessary keys generated due to the XML structure conversion (Example: fruits->fruit to $arr['fruits']['fruit'][n]);
  • Set elements which should aways be treated as lists (because sometimes it will have only one child element but you still need it to be a list);
  • Set a name for array element keys which will represent an XML element text value - which will be needed when attributes are converted to array elements.

Usage example with your XML elements (I think you already solved it after 11 years, but I'm answering it, so...):

test.xml


    
        
            
                
                
            
        
    

PHP

$xml = simplexml_load_file("test.xml");
$decode = xml_decode($xml);
echo "
" . print_r($decode,true) . "
";

Output

Array
(
    [aaaa] => Array
        (
            [attributes] => Array
                (
                    [Version] => 1.0
                )

            [bbb] => Array
                (
                    [cccc] => Array
                        (
                            [dddd] => Array
                                (
                                    [attributes] => Array
                                        (
                                            [Id] => id:pass
                                        )

                                    [value] => 
                                )

                            [eeee] => Array
                                (
                                    [attributes] => Array
                                        (
                                            [name] => hearaman
                                            [age] => 24
                                        )

                                    [value] => 
                                )

                        )

                )

        )

)