217

I want to convert below XML to PHP array. Any suggestions on how I can do this?

<aaaa Version="1.0">
   <bbb>
     <cccc>
       <dddd Id="id:pass" />
       <eeee name="hearaman" age="24" />
     </cccc>
   </bbb>
</aaaa>
Syscall
  • 19,327
  • 10
  • 37
  • 52
Hearaman
  • 8,466
  • 13
  • 41
  • 58
  • 3
    also, how is that question different form your other question? http://stackoverflow.com/questions/6578084/how-to-convert-this-xml-request-into-array-in-php – Gordon Jul 05 '11 at 07:04
  • 3
    Few things are as obnoxious as an OP accepting the wrong answer to their own question. – John Oct 20 '17 at 09:42

12 Answers12

522

easy!

$xml = simplexml_load_string($xmlstring, "SimpleXMLElement", LIBXML_NOCDATA);
$json = json_encode($xml);
$array = json_decode($json,TRUE);
MartyIX
  • 27,828
  • 29
  • 136
  • 207
user1398287
  • 5,245
  • 5
  • 21
  • 25
  • 1
    Thanks! After looking through a bunch of functions everywhere, nothing structured the parsed XML in a sane way... this is simple and works beautifully! – Brad Jan 28 '14 at 22:19
  • 36
    You might run into trouble at CDATA sections (returning always null). As a solution try $xml = simplexml_load_string( $xmlstring , null , LIBXML_NOCDATA ); $json = json_encode($xml); $array = json_decode($json,TRUE); (see http://stackoverflow.com/a/2970701/413531) //e damn.. is there a way to add new lines in a comment? – Hirnhamster May 26 '14 at 09:57
  • 1
    @Hirnhamster: If you use that flag, you most often can just cast to array: `$array = (array) simplexml_load_string($xmlstring, null , LIBXML_NOCDATA);` - no need for decode a JSON encode. In case you need more options: http://stackoverflow.com/a/25095553/367456 – hakre Aug 02 '14 at 13:48
  • 4
    We do the exact same thing but with simplexml_load_file and it work fine. Thanks – Thermech Sep 03 '14 at 13:46
  • 2
    What the second parameter(TRUE) is for? – Mansour Fahad Sep 19 '14 at 18:50
  • 4
    @MansourFahad In [json_decode](http://us3.php.net/manual/en/function.json-decode.php) you can use the optional second parameter as `TRUE` (normally defaults to `FALSE`) to convert the JSON input to an associative array. – Jake Bathman Sep 26 '14 at 19:20
  • 17
    @Ismael Miguel too much code? Just because you put all those functions on one line doesn't mean you're using less code. It might look more compact but it comes at the expense of readability. – Jage Mar 05 '15 at 21:44
  • @Jage Don't forget the performance win. You don't have to be storing it into another variable. PHP passes it eficiently to the function. – Ismael Miguel Mar 05 '15 at 23:05
  • @IsmaelMiguel The performance gain by doing it that way is negligible. You're talking micro-optimization at the expense of readability and maintainability. I'd sooner have other devs be able to read my code than save them a small handful of clock cycles. – maiorano84 Oct 29 '15 at 13:59
  • @maiorano84 You are right, it is a micro-optimization. One that can avoid a ton of copies remaining in memory due to assigning the data to other functions. You keey a XML object, a JSON representation and then an array, when you could only have a XML object and the array. – Ismael Miguel Oct 29 '15 at 15:09
  • 1
    @IsmaelMiguel You seem to be suggesting that PHP's internal garbage collection is insufficient in handling such a trivial task like this. Do you have any benchmarks to back up your claim that there would be a "ton" of copies remaining in memory just from creating variables to maintain readability, and produce a significant enough overhead that memory becomes an issue? Because if not, I'm not sure what you're trying to argue here except that you somehow believe that less lines = performant, which is laughably false. – maiorano84 Oct 29 '15 at 15:55
  • @maiorano84 Yes, it is as false as it can get. I am talking about a huge string that will be left behind. I'm not saying that the GB won't be enough. I'm saying that you will have 2 copies of the same string, because you assigned the original to a variable and you pass the copy to the function. – Ismael Miguel Oct 30 '15 at 21:28
  • 1
    If your xml has namespaces, do `simplexml_load_string($xmlstring, "SimpleXMLElement", LIBXML_NOCDATA, "ns3", true);` ns3 is an example of a perfix. if it's a prefix, the next argument needs to be true. If the fourth argument is not a namespace, but a URI, the 5th parameter is not needed, since it's defaulted to false. If any of these are improper, the result will be an empty string. – ahnbizcad Mar 24 '17 at 16:44
  • 2
    simplexml_load_string drops attributes on elements with only text node - so this doesn't work for me. – But those new buttons though.. Sep 21 '17 at 16:54
  • This will work for some sets of XML (the one OP posted is okay). However, it will also lose attributes and whole nodes in some situations. For example, if you have an "array" of elements (a collection of elements with the same name) then any attributes they have will not be in the final array. I'm finding this even on PHP 7.2; it's a problem that is probably here to stay. – Jason Mar 22 '18 at 11:47
  • Don't try to convert into an array, by the use of json_encode. It does not work with namespaces! – Sven Jun 26 '21 at 18:17
  • LIBXML_NOWARNING flag for third param might help somebody too – Tonoslav Oct 20 '22 at 14:12
140

Another option is the SimpleXML extension (I believe it comes standard with most php installs.)

http://php.net/manual/en/book.simplexml.php

The syntax looks something like this for your example

$xml = new SimpleXMLElement($xmlString);
echo $xml->bbb->cccc->dddd['Id'];
echo $xml->bbb->cccc->eeee['name'];
// or...........
foreach ($xml->bbb->cccc as $element) {
  foreach($element as $key => $val) {
   echo "{$key}: {$val}";
  }
}
Hearaman
  • 8,466
  • 13
  • 41
  • 58
Sam Dufel
  • 17,560
  • 3
  • 48
  • 51
  • 93
    To be fair, this does not exactly answer the question how to get an array. – sieppl Nov 05 '13 at 08:03
  • SimpleXML suck when parsing this xml : https://www.amazon.in/rss/bestsellers/shoes?tag=dealslama-21 Even print_r don't tell object contains actually. – Ravi Soni Jan 24 '15 at 07:39
  • use var_dump, you'll see the xml structure as keys inside the object. – Magus Oct 03 '16 at 16:49
  • 3
    I have some `[CDATA[TEXT]]` inside some elements and they are not parsing with this. It parses it as a `SimpleXMLElement Object`. Any workaround for that? – masterFly Oct 26 '16 at 04:20
  • 1
    This doesn’t answer the question – Bilaal Rashid Apr 14 '19 at 16:11
  • I believe when I put this answer in 8 years ago, I was attempting to highlight that you can access the contents of a parsed XML document using SimpleXML in the same manner as you would if it was in a plain array. – Sam Dufel Apr 18 '19 at 23:35
54

Converting an XML string ($buffer) into a simplified array ignoring attributes and grouping (non-empty) child-elements with the same names:

function XML2Array(SimpleXMLElement $parent)
{
    $array = array();

    foreach ($parent as $name => $element) {
        ($node = & $array[$name])
            && (is_array($node) || ($node = array($node)))
            && $node = & $node[];

        $node = $element->count() ? XML2Array($element) : trim($element);
    }

    return $array;
}

$xml   = simplexml_load_string($buffer);
$array = XML2Array($xml);
$array = array($xml->getName() => $array);

Result (print_r($array)):

Array
(
    [aaaa] => Array
        (
            [bbb] => Array
                (
                    [cccc] => Array
                        (
                            [dddd] => 
                            [eeee] => 
                        )

                )

        )

)

If you also want to have the attributes, they are available via JSON encoding/decoding of SimpleXMLElement. This is often the most easy quick'n'dirty solution:

$xml   = simplexml_load_string($buffer);
$array = json_decode(json_encode((array) $xml), true);
$array = array($xml->getName() => $array);

Result:

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

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

                                )

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

                                )

                        )

                )

        )

)

The two revivers presented in this answer are brief and may easily fall short.

Take note that all these methods only work in the default namespace of the XML document. See existing Q&A Resolve namespaces with SimpleXML regardless of structure or namespace for both the discussion of XML namespace handling and various other details of converting SimpleXMLElement to array.

For additional information for SimpleXMLElement and json_encode(), a series of blog posts is available on my blog as well (Part I, II and III).

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Related: [Json Encode or Serialize an XML](http://stackoverflow.com/a/18553535/367456) – hakre Sep 06 '13 at 10:20
  • In PHP 7 I had to add this: `&& (is_countable($node) && 1 === count($node) ? $node = array($node) : 1)`, however I got an error in the next line: `[] operator not supported for strings`. – andreshg112 Dec 16 '19 at 21:48
  • @andreshg112: I'm unable to reproduce (works stable PHP 5.3.0 - 7.4.0), the behavior has not changed since ages, please compare against hundreds of different PHP versions: https://3v4l.org/l4nQN – hakre Dec 16 '19 at 23:58
  • maybe it is because of my KML file (it's an XML). I cannot share it. I already imported it but I had to do it another way. – andreshg112 Dec 26 '19 at 13:12
  • probably you are concerned about XML namespaces. The example is only for the parts w/o a namespace (or the default one, I sometimes mix this). – hakre Dec 28 '19 at 18:38
  • @hakre: you can reproduce what andreshg112 mentioned if you have duplicate node names in the xml. – Mihai MATEI Jul 05 '23 at 14:53
  • thanks for pointing that out @MihaiMATEI. the code in the answer does check for that code, but probably it is flawed. Can you share an example on 3v4l.org that breaks this, then I can take a look. (or anywhere else, just ping me here.) – hakre Jul 06 '23 at 01:17
  • 1
    @hakre, here is a sample xml just pasted in your previously linked 3v4l: https://3v4l.org/Oqr5c . (btw: this code and xml content is commonly used in the Porto theme for Magento1) – Mihai MATEI Jul 07 '23 at 10:14
  • 1
    @MihaiMATEI: Please find an update here: https://3v4l.org/WuQeL What I could identify is merely updating for PHP forward compatibility. So it is now PHP 5.3+ up to 8.2 and git.master_jit, git.master, rfc.literals - so I guess this should cover upcoming PHP 8.3 as well. I'll update the answer accordingly. – hakre Jul 09 '23 at 19:52
45
$array = json_decode(json_encode((array)simplexml_load_string($xml)),true);
ntzm
  • 4,663
  • 2
  • 31
  • 35
Fawad Ghafoor
  • 6,039
  • 7
  • 41
  • 53
  • 2
    if you cast to array, you dont need `json_encode` and `json_decode`. – Ismael Miguel Jan 28 '14 at 23:20
  • 13
    @Ismael in theory, casting to array should be enough. In practice we need to cast all leaf nodes too, which are also objects. A naive cast leaves the leaves as SimpleXML objects. json_encode casts recursively saving a lot of legwork. – Peter Mellett May 01 '14 at 11:00
  • 2
    If you do not have text values into your `$array` variable, it is perhaps because of CDATA. To solve it, load your XML with: `new SimpleXMLElement($xml, LIBXML_NOCDATA)`. – Jonathan Petitcolas Jul 25 '14 at 09:24
  • 1
    ps. $xml = str_replace(array('<![CDATA[',']]>'),'',$xml); – user956584 Jan 15 '15 at 11:51
  • 1
    It doesn't work like that. That code wouldn't convert even a simple XML like this one `Hello!`. Run the code and you'll see that the **** `c` attribute is lost! Please check the full code here if you don't want any bad surprises https://github.com/gaarf/XML-string-to-PHP-array/blob/master/xmlstr_to_array.php or see my answer below http://stackoverflow.com/a/30234924/828366 – Francesco Casula May 14 '15 at 10:21
18

The method used in the accepted answer drop attributes when encountering child elements with only a text node. For example:

$xml = '<container><element attribute="123">abcd</element></container>';
print_r(json_decode(json_encode(simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA)),1));

Array
(
    [element] => abcd
)

My solution (and I wish I could give credit here because I'm sure I adapted this from something):

function XMLtoArray($xml) {
    $previous_value = libxml_use_internal_errors(true);
    $dom = new DOMDocument('1.0', 'UTF-8');
    $dom->preserveWhiteSpace = false; 
    $dom->loadXml($xml);
    libxml_use_internal_errors($previous_value);
    if (libxml_get_errors()) {
        return [];
    }
    return DOMtoArray($dom);
}

function DOMtoArray($root) {
    $result = array();

    if ($root->hasAttributes()) {
        $attrs = $root->attributes;
        foreach ($attrs as $attr) {
            $result['@attributes'][$attr->name] = $attr->value;
        }
    }

    if ($root->hasChildNodes()) {
        $children = $root->childNodes;
        if ($children->length == 1) {
            $child = $children->item(0);
            if (in_array($child->nodeType,[XML_TEXT_NODE,XML_CDATA_SECTION_NODE])) {
                $result['_value'] = $child->nodeValue;
                return count($result) == 1
                    ? $result['_value']
                    : $result;
            }

        }
        $groups = array();
        foreach ($children as $child) {
            if (!isset($result[$child->nodeName])) {
                $result[$child->nodeName] = DOMtoArray($child);
            } else {
                if (!isset($groups[$child->nodeName])) {
                    $result[$child->nodeName] = array($result[$child->nodeName]);
                    $groups[$child->nodeName] = 1;
                }
                $result[$child->nodeName][] = DOMtoArray($child);
            }
        }
    }
    return $result;
}

$xml = '
    <aaaa Version="1.0">
       <bbb>
         <cccc>
           <dddd id="123" />
           <eeee name="john" age="24" />
           <ffff type="employee">Supervisor</ffff>
         </cccc>
       </bbb>
    </aaaa>
';
print_r(XMLtoArray($xml));

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

            [bbb] => Array
                (
                    [cccc] => Array
                        (
                            [dddd] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [id] => 123
                                        )

                                )

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

                                )

                            [ffff] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [type] => employee
                                        )

                                    [_value] => Supervisor
                                )

                        )

                )

        )

)
  • 3
    This is the only answer I've found which handled node attributes as well as arrays. Very easy to understand too. – Brainware Aug 19 '20 at 02:36
  • Wonderful. You saved me at least 18 yerars of debugging! – Mad Marvin Jan 28 '21 at 20:15
  • This answer also fails to account for nodes that that contain text as well as XML children. I have spent hours looking now. I am starting to believe there is no XML solution anywhere that can properly parse all XML, and keep everything! – InterLinked Nov 25 '21 at 01:43
12

See https://github.com/gaarf/XML-string-to-PHP-array/blob/master/xmlstr_to_array.php

<?php
/**
  * convert xml string to php array - useful to get a serializable value
  *
  * @param string $xmlstr
  * @return array
  *
  * @author Adrien aka Gaarf & contributors
  * @see http://gaarf.info/2009/08/13/xml-string-to-php-array/
*/
function xmlstr_to_array($xmlstr) {
  $doc = new DOMDocument();
  $doc->loadXML($xmlstr);
  $root = $doc->documentElement;
  $output = domnode_to_array($root);
  $output['@root'] = $root->tagName;
  return $output;
}
function domnode_to_array($node) {
  $output = array();
  switch ($node->nodeType) {
    case XML_CDATA_SECTION_NODE:
    case XML_TEXT_NODE:
      $output = trim($node->textContent);
    break;
    case XML_ELEMENT_NODE:
      for ($i=0, $m=$node->childNodes->length; $i<$m; $i++) {
        $child = $node->childNodes->item($i);
        $v = domnode_to_array($child);
        if(isset($child->tagName)) {
          $t = $child->tagName;
          if(!isset($output[$t])) {
            $output[$t] = array();
          }
          $output[$t][] = $v;
        }
        elseif($v || $v === '0') {
          $output = (string) $v;
        }
      }
      if($node->attributes->length && !is_array($output)) { //Has attributes but isn't an array
        $output = array('@content'=>$output); //Change output into an array.
      }
      if(is_array($output)) {
        if($node->attributes->length) {
          $a = array();
          foreach($node->attributes as $attrName => $attrNode) {
            $a[$attrName] = (string) $attrNode->value;
          }
          $output['@attributes'] = $a;
        }
        foreach ($output as $t => $v) {
          if(is_array($v) && count($v)==1 && $t!='@attributes') {
            $output[$t] = $v[0];
          }
        }
      }
    break;
  }
  return $output;
}
Francesco Casula
  • 26,184
  • 15
  • 132
  • 131
9

Surprised no one mentioned xml_parse_into_struct:

$simple = "<para><note>simple note</note></para>";
$p = xml_parser_create();
xml_parse_into_struct($p, $simple, $vals, $index);
xml_parser_free($p);
echo "Index array\n";
print_r($index);
echo "\nVals array\n";
print_r($vals);
eozzy
  • 66,048
  • 104
  • 272
  • 428
  • Sometimes I wonder what the developer who created the PHP XML implementation was thinking when xml_parse_into_struct was designed ... – Anibal Sanchez Jan 08 '20 at 10:28
6

XML To Array

More Details Visit https://github.com/sapankumarmohanty/lamp/blob/master/Crate-XML-2-Array

//Convert XML to array and SOAP XML to array

function xml2array($contents, $get_attributes = 1, $priority = 'tag')
    {
        if (!$contents) return array();
        if (!function_exists('xml_parser_create')) {
            // print "'xml_parser_create()' function not found!";
            return array();
        }
        // Get the XML parser of PHP - PHP must have this module for the parser to work
        $parser = xml_parser_create('');
        xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parse_into_struct($parser, trim($contents) , $xml_values);
        xml_parser_free($parser);
        if (!$xml_values) return; //Hmm...
        // Initializations
        $xml_array = array();
        $parents = array();
        $opened_tags = array();
        $arr = array();
        $current = & $xml_array; //Refference
        // Go through the tags.
        $repeated_tag_index = array(); //Multiple tags with same name will be turned into an array
        foreach($xml_values as $data) {
            unset($attributes, $value); //Remove existing values, or there will be trouble
            // This command will extract these variables into the foreach scope
            // tag(string), type(string), level(int), attributes(array).
            extract($data); //We could use the array by itself, but this cooler.
            $result = array();
            $attributes_data = array();
            if (isset($value)) {
                if ($priority == 'tag') $result = $value;
                else $result['value'] = $value; //Put the value in a assoc array if we are in the 'Attribute' mode
            }
            // Set the attributes too.
            if (isset($attributes) and $get_attributes) {
                foreach($attributes as $attr => $val) {                                   
                                    if ( $attr == 'ResStatus' ) {
                                        $current[$attr][] = $val;
                                    }
                    if ($priority == 'tag') $attributes_data[$attr] = $val;
                    else $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
                }
            }
            // See tag status and do the needed.
                        //echo"<br/> Type:".$type;
            if ($type == "open") { //The starting of the tag '<tag>'
                $parent[$level - 1] = & $current;
                if (!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
                    $current[$tag] = $result;
                    if ($attributes_data) $current[$tag . '_attr'] = $attributes_data;
                                        //print_r($current[$tag . '_attr']);
                    $repeated_tag_index[$tag . '_' . $level] = 1;
                    $current = & $current[$tag];
                }
                else { //There was another element with the same tag name
                    if (isset($current[$tag][0])) { //If there is a 0th element it is already an array
                        $current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
                        $repeated_tag_index[$tag . '_' . $level]++;
                    }
                    else { //This section will make the value an array if multiple tags with the same name appear together
                        $current[$tag] = array(
                            $current[$tag],
                            $result
                        ); //This will combine the existing item and the new item together to make an array
                        $repeated_tag_index[$tag . '_' . $level] = 2;
                        if (isset($current[$tag . '_attr'])) { //The attribute of the last(0th) tag must be moved as well
                            $current[$tag]['0_attr'] = $current[$tag . '_attr'];
                            unset($current[$tag . '_attr']);
                        }
                    }
                    $last_item_index = $repeated_tag_index[$tag . '_' . $level] - 1;
                    $current = & $current[$tag][$last_item_index];
                }
            }
            elseif ($type == "complete") { //Tags that ends in 1 line '<tag />'
                // See if the key is already taken.
                if (!isset($current[$tag])) { //New Key
                    $current[$tag] = $result;
                    $repeated_tag_index[$tag . '_' . $level] = 1;
                    if ($priority == 'tag' and $attributes_data) $current[$tag . '_attr'] = $attributes_data;
                }
                else { //If taken, put all things inside a list(array)
                    if (isset($current[$tag][0]) and is_array($current[$tag])) { //If it is already an array...
                        // ...push the new element into that array.
                        $current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
                        if ($priority == 'tag' and $get_attributes and $attributes_data) {
                            $current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
                        }
                        $repeated_tag_index[$tag . '_' . $level]++;
                    }
                    else { //If it is not an array...
                        $current[$tag] = array(
                            $current[$tag],
                            $result
                        ); //...Make it an array using using the existing value and the new value
                        $repeated_tag_index[$tag . '_' . $level] = 1;
                        if ($priority == 'tag' and $get_attributes) {
                            if (isset($current[$tag . '_attr'])) { //The attribute of the last(0th) tag must be moved as well
                                $current[$tag]['0_attr'] = $current[$tag . '_attr'];
                                unset($current[$tag . '_attr']);
                            }
                            if ($attributes_data) {
                                $current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
                            }
                        }
                        $repeated_tag_index[$tag . '_' . $level]++; //0 and 1 index is already taken
                    }
                }
            }
            elseif ($type == 'close') { //End of tag '</tag>'
                $current = & $parent[$level - 1];
            }
        }
        return ($xml_array);
    }
    
    // Let's call the this above function xml2array
    
    xml2array($xmlContent, $get_attributes = 3, $priority = 'tag'); // it will work 100% if not ping me @skype: sapan.mohannty
    
//  Enjoy coding
Community
  • 1
  • 1
htngapi
  • 365
  • 3
  • 7
3

Two lines of code (https://www.php.net/manual/en/book.simplexml.php#113485)

$xml = new SimpleXMLElement("<your><xml><string>ok</string></xml></your>");
$array = (array)$xml;
2

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 <elem>true</elem> 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.

<?php

function val($input) {
    return strval($input);
}

/**
 * Transform an SimpleXMLElement into an associative array.
 * 
 * @param SimpleXMLElement $xml The XML element to be decoded.
 * 
 * @param bool $attributes_key If the element attributes should be grouped into a single element.
 * 
 * Example: <elem foo="true" bar="false" />
 * 
 * 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: <fruits><fruit>apple</fruit><fruit>banana</fruit></fruits>
 * 
 * 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: <fruits><fruit>apple</fruit></fruits>
 * 
 * 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: <fruits><fruit id="123">apple</fruit></fruits>
 * 
 * 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: <fruits><fruit /><fruit /><fruits />
        // 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

<test>
    <aaaa Version="1.0">
        <bbb>
            <cccc>
                <dddd Id="id:pass" />
                <eeee name="hearaman" age="24" />
            </cccc>
        </bbb>
    </aaaa>
</test>

PHP

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

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] => 
                                )

                        )

                )

        )

)
Rafael
  • 33
  • 1
  • 8
  • 1
    I like it but just curious, why would you need val() function? I mean, couldn't you use strval right away? – Your Common Sense Jul 09 '22 at 09:38
  • 1
    I did that so the user can customize the information handling. Since all XML values are read by the XML parser as strings because of the `__toString()` method, you might want to use that `val()` function to do stuff like `return $input === "true" ? true : $input;` because JSON accepts other data types apart from strings. I left it returning `strval($input)` as a placeholder. – Rafael Jul 20 '22 at 16:41
1

I liked this question and some answers was helpful to me, but i need to convert the xml to one domination array, so i will post my solution maybe someone need it later:

<?php
$xml = json_decode(json_encode((array)simplexml_load_string($xml)),1);
$finalItem = getChild($xml);
var_dump($finalItem);

function getChild($xml, $finalItem = []){
    foreach($xml as $key=>$value){
        if(!is_array($value)){
            $finalItem[$key] = $value;
        }else{
            $finalItem = getChild($value, $finalItem);
        }
    }
    return $finalItem;
}
?>  
Mohammad Alabed
  • 809
  • 6
  • 17
1

/* Creating an XML file (Optional): Create an XML file which need to convert into the array. test.xml */

<aaaa Version="1.0">
  <bbb>
    <cccc>
      <dddd Id="id:pass" />
      <eeee name="hearaman" age="24" />
    </cccc>
  </bbb>
</aaaa>

<?php
  
// xml file path
$path = "text.xml"; // set your according path for dynamic.
  
// Read entire file into string
$xmlfile = file_get_contents($path);
  
// Convert xml string into an object
$new = simplexml_load_string($xmlfile);
  
// Convert into json
$con = json_encode($new);
  
// Convert into associative array
$newArr = json_decode($con, true);
  
print_r($newArr);
  
?>

Output: Result of XML conversion to PHP Array


[
  'aaaa' => [
    'bbb' => [
      'cccc' => [
        'dddd' => [
          '@value' => '',
          '@attributes' => [
            'Id' => 'id:pass',
          ],
        ],
        'eeee' => [
          '@value' => '',
          '@attributes' => [
            'name' => 'hearaman',
            'age' => '24',
          ],
        ],
      ],
    ],
    '@attributes' => [
      'Version' => '1.0',
    ],
  ],
]