PHP/SimpleXML - Arrays generated differently for single child and multiple children

SimpleXML is quirky like this. I used it recently trying to make configuration files "easier" to write up and found out in the process that SimpleXML doesn't always act consistent. In this case I think you will benefit from simply detecting if a <property> is the only one in a set, and if so, then wrap it in an array by itself and then send it to your loop.

NOTE: ['root'] is there because I needed to wrap a '<root></root>' element around your XML to make my test work.

//Rebuild the properties listings
$rebuild = array();
foreach($xml_array['root']['branch'] as $key => $branch) {
    $branchName = $branch['@attributes']['name'];
    //Check to see if 'properties' is only one, if it
    //is then wrap it in an array of its own.
    if(is_array($branch['properties']['property']) && !isset($branch['properties']['property'][0])) {
        //Only one propery found, wrap it in an array
        $rebuild[$branchName] = array($branch['properties']['property']);
    } else {
        //Multiple properties found
        $rebuild[$branchName] = $branch['properties']['property'];
    }
}

That takes care of rebuilding your properties. It feels a little hackish. But basically you are detecting for the lack of a multi-dimensional array here:

if(is_array($branch['properties']['property']) && !isset($branch['properties']['property'][0]))

If you don't find a multi-dimensional array then you explicitly make one of the single <property>. Then to test that everything was rebuilt correctly you can use this code:

//Now do your operation...whatever it is.
foreach($rebuild as $branch => $properties) {
    print("Listings for $branch:\n");
    foreach($properties as $property) {
        print("Reference of " . $property['reference'] . " sells at $" . $property['price'] . " for " . $property['bedrooms'] . " bedrooms.\n");
    }
    print("\n");
}

This produces the following output:

Listings for Trustee Realtors:
Reference of 1 sells at $275000 for 3 bedrooms.
Reference of 2 sells at $350000 for 4 bedrooms.
Reference of 3 sells at $128500 for 4 bedrooms.

Listings for Quick-E-Realty Inc:
Reference of 4 sells at $180995 for 3 bedrooms.

And a dump of the rebuild will produce:

Array
(
    [Trustee Realtors] => Array
        (
            [0] => Array
                (
                    [reference] => 1
                    [price] => 275000
                    [bedrooms] => 3
                )

            [1] => Array
                (
                    [reference] => 2
                    [price] => 350000
                    [bedrooms] => 4
                )

            [2] => Array
                (
                    [reference] => 3
                    [price] => 128500
                    [bedrooms] => 4
                )

        )

    [Quick-E-Realty Inc] => Array
        (
            [0] => Array
                (
                    [reference] => 4
                    [price] => 180995
                    [bedrooms] => 3
                )

        )

)

I hope that helps you out getting closer to a solution to your problem.


The big massive "think outside the box" question to ask yourself here is: why are you converting the SimpleXML object to an array in the first place?

SimpleXML is not just a library for parsing XML and then using something else to manipulate it, it's designed for exactly the kind of thing you're about to do with that array.

In fact, this problem of sometimes having single elements and sometimes multiple is one of the big advantages it has over a plain array representation: for nodes that you know will be single, you can leave off the [0]; but for nodes you know might be multiple, you can use [0], or a foreach loop, and that will work too.

Here are some examples of why SimpleXML lives up to its name with your XML:

$sxml = simplexml_load_string($xml);

// Looping over multiple nodes with the same name
// We could also use $sxml->children() to loop regardless of name
//   or even the shorthand foreach ( $sxml as $children )
foreach ( $sxml->branch as $branch ) {

    // Access an attribute using array index notation
    //   the (string) is optional here, but good habit to avoid
    //   passing around SimpleXML objects by mistake
    echo 'The branch name is: ' . (string)$branch['name'] . "\n";

    // We know there is only one <properties> node, so we can take a shortcut:
    //   $branch->properties means the same as $branch->properties[0]
    // We don't know if there are 1 or many <property> nodes, but it
    //   doesn't matter: we're asking to loop over them, so SimpleXML 
    //   knows what we mean
    foreach ( $branch->properties->property as $property ) {
        echo 'The property reference is ' . (string)$property->reference . "\n";
    }
}

Basically, whenever I see that ugly json_decode(json_encode( trick, I cringe a little, because 99 times out of 100 the code that follows is much uglier than just using SimpleXML.


One possibility is reading the XML with DOM+XPath. XML can not just be converted to JSON, but building a specific JSON for a specific XML is easy:

$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);

$result = [];
foreach ($xpath->evaluate('//branch') as $branchNode) {
  $properties = [];
  foreach ($xpath->evaluate('properties/property', $branchNode) as $propertyNode) {
    $properties[] = [
      'reference' => $xpath->evaluate('string(reference)', $propertyNode),
      'price' => (int)$xpath->evaluate('string(price)', $propertyNode),
      'bedrooms' => (int)$xpath->evaluate('string(bedrooms)', $propertyNode)
    ];
  }
  $result[] = [
    'name' => $xpath->evaluate('string(@name)', $branchNode),
    'properties' => $properties
  ];
}

echo json_encode($result, JSON_PRETTY_PRINT);

Output: https://eval.in/154352

[
    {
        "name": "Trustee Realtors",
        "properties": [
            {
                "reference": "1",
                "price": 275000,
                "bedrooms": 3
            },
            {
                "reference": "2",
                "price": 350000,
                "bedrooms": 4
            },
            {
                "reference": "3",
                "price": 128500,
                "bedrooms": 4
            }
        ]
    },
    {
        "name": "Quick-E-Realty Inc",
        "properties": [
            {
                "reference": "4",
                "price": 180995,
                "bedrooms": 3
            }
        ]
    }

Tags:

Php

Xml

Simplexml