232

I have something like this:

$url = "http://ws.geonames.org/findNearbyPostalCodes?country=pl&placename=";
$url .= rawurlencode($city[$i]);

$xml = simplexml_load_file($url);
echo $url."\n";
$cityCode[] = array(
    'city' => $city[$i], 
    'lat' => $xml->code[0]->lat, 
    'lng' => $xml->code[0]->lng
);

It's supposed to download XML from geonames. If I do print_r($xml) I get :

SimpleXMLElement Object
(
    [code] => Array
        (
            [0] => SimpleXMLElement Object
                (
                    [postalcode] => 01-935
                    [name] => Warszawa
                    [countryCode] => PL
                    [lat] => 52.25
                    [lng] => 21.0
                    [adminCode1] => SimpleXMLElement Object
                        (
                        )

                    [adminName1] => Mazowieckie
                    [adminCode2] => SimpleXMLElement Object
                        (
                        )

                    [adminName2] => Warszawa
                    [adminCode3] => SimpleXMLElement Object
                        (
                        )

                    [adminName3] => SimpleXMLElement Object
                        (
                        )

                    [distance] => 0.0
                )

I do as you can see $xml->code[0]->lat and it returns an object. How can i get the value?

hakre
  • 193,403
  • 52
  • 435
  • 836
kubas
  • 2,321
  • 2
  • 14
  • 3
  • 1
    possible duplicate of [Forcing a SimpleXML Object to a string, regardless of context](http://stackoverflow.com/questions/416548/forcing-a-simplexml-object-to-a-string-regardless-of-context) – hakre Sep 28 '14 at 10:53
  • 2
    2017 Update: SO no longer displays the best answer at the top. [The best answer is here](https://stackoverflow.com/a/2867601/114558). – rinogo Jun 06 '17 at 21:23
  • 3
    @rinogo You've probably accidentally clicked one of the sorting tabs at the top of the answer block. The answer you linked to has 345 votes, so shows at the top if you have sorting set to "votes". – IMSoP Aug 29 '17 at 13:49
  • 1
    Thanks, @IMSoP! You're right - I must have clicked "active" at some point (useful for old questions with outdated answers, btw) - good to know I need to change it back to "votes"! :) – rinogo Aug 29 '17 at 18:42

12 Answers12

487

You have to cast simpleXML Object to a string.

$value = (string) $xml->code[0]->lat;
Luis Melgratti
  • 11,881
  • 3
  • 30
  • 32
  • 13
    Just noticed if you json_encode the xml object and then json_decode it you get a nested stdObject to deal with, quite handy for when you're being lazy & working with simple structures :D – lsl Sep 20 '13 at 06:59
  • 1
    silly question, but isn't that a bug? see http://php.net/simplexml#95762 why you don't have to cast type on some fields but on others you have to? – gcb Feb 16 '14 at 09:39
  • 6
    i can't believe this is so complicated. why would they make a class called "getName" but not "getValue"? why would they print empty string if you printed it instead of converted it manually to (string). WHY?? – user151496 Oct 17 '16 at 12:31
  • 3
    @user151496 Technically, the string cast isn't giving you the "value", but the "text content". But yes, a specifically-named method would be more discoverable. Once you get used to this, though, it's not actually any harder to *use*. – IMSoP Aug 29 '17 at 14:01
  • i always kill at least half a day by discovering the quirks of simpleXML every time i have to get to it once in a couple of months on some projects – user151496 Sep 19 '17 at 15:57
  • Yep, this is **Simple**XMLElement and no matter how hard you'd try you will always end up with another `SimpleXMLElement` (yes even for `->children()` method), so there's no innerHTML/innerText - **"Returns text content that is directly in this element. Does not return text content that is inside this element's children."** – jave.web Apr 28 '21 at 22:26
106

You can also use the magic method __toString()

$xml->code[0]->lat->__toString()
sglessard
  • 3,097
  • 2
  • 27
  • 31
21

If you know that the value of the XML element is a float number (latitude, longitude, distance), you can use (float)

$value = (float) $xml->code[0]->lat;

Also, (int) for integer number:

$value = (int) $xml->code[0]->distance;
CoursesWeb
  • 4,179
  • 3
  • 21
  • 27
17

if you don't know the value of XML Element, you can use

$value = (string) $xml->code[0]->lat;

if (ctype_digit($value)) {
    // the value is probably an integer because consists only of digits
}

It works when you need to determine if value is a number, because (string) will always return string and is_int($value) returns false

vladkras
  • 16,483
  • 4
  • 45
  • 55
  • Any way to check also for boolean other than: (string)$value == 'true' || (string)$value == 'false'? – Talisin Jun 29 '14 at 07:23
  • Note that this will not always be true. It's only valid for strings with natural (positive) integers. So, they must be in a string. They must not be float nor negative, since decimal separators, minus signs, etc will be evaluated to false. So, **ctype_digit** would be the same as **preg_match('/^\d+$/', $var)** If you want to check whether it's a numeric value, including floats and negatives, use [is_numeric()](https://www.php.net/manual/en/function.is-numeric.php) instead. – luis.ap.uyen Mar 18 '21 at 11:24
16

Quick Solution if you in hurry.

convert a Xml-Object to an array (or object),

function loadXml2Array($file,$array=true){
   $xml = simplexml_load_file($file);   
 
   $json_string = json_encode($xml);    
   return json_decode($json_string, $array);
}
dazzafact
  • 2,570
  • 3
  • 30
  • 49
  • 5
    Converting to JSON and then back in the same context is incredibly inefficient unless you are really in a hurry. Working with objects is not hard, and if you *really* prefer arrays, you ought to convert the object to an array natively. – William Jul 26 '16 at 17:32
  • 1
    I never understand why people do this - you throw away all the features of SimpleXML, just so you can write `$xml['foo'][0]['bar'][0]['@attributes']['baz']` instead of `$xml->foo->bar['baz']`. – IMSoP Aug 29 '17 at 13:51
3

This is the function that has always helped me convert the xml related values to array

function _xml2array ( $xmlObject, $out = array () ){
    foreach ( (array) $xmlObject as $index => $node )
        $out[$index] = ( is_object ( $node ) ) ? _xml2array ( $node ) : $node;

    return $out;
}
pal4life
  • 3,210
  • 5
  • 36
  • 57
  • This will throw away, or break with, among other things: any attributes, anything in a namespace, any CDATA blocks. All just so you can lose the convenience methods which SimpleXML offered you in the first place. – IMSoP Aug 29 '17 at 13:59
3

you can use the '{}' to access you property, and then you can do as you wish. Save it or display the content.

    $varName = $xml->{'key'};

From your example her's the code

        $filePath = __DIR__ . 'Your path ';
        $fileName = 'YourFilename.xml';

        if (file_exists($filePath . $fileName)) {
            $xml = simplexml_load_file($filePath . $fileName);
            $mainNode = $xml->{'code'};

            $cityArray = array();

            foreach ($mainNode as $key => $data)        {
               $cityArray[..] = $mainNode[$key]['cityCode'];
               ....

            }     

        }
  • 4
    There's no need to use `{}` unless there are special characters like hyphens in there. `$xml->{'key'}` is just an uglier way of writing `$xml->key`. – IMSoP Aug 29 '17 at 13:44
  • Exactly :p Its different, if you'd have it in some dynamic way, then you could use $xml->{$dynamicKey} – Grzegorz Pietrzak Mar 16 '22 at 15:40
2

try current($xml->code[0]->lat)

it returns element under current pointer of array, which is 0, so you will get value

Bendeberia
  • 116
  • 7
1
header("Content-Type: text/html; charset=utf8");
$url  = simplexml_load_file("http://URI.com");

 foreach ($url->PRODUCT as $product) {  
    foreach($urun->attributes() as $k => $v) {
        echo $k." : ".$v.' <br />';
    }
    echo '<hr/>';
}
  • 3
    This answer is lacking any explanation, and therefore misses the key insight which is that `echo` forces `$v` to become a string rather than an object, just as `(string)$v` would. – IMSoP Aug 29 '17 at 13:52
1

you can convert array with this function

function xml2array($xml){
$arr = array();

foreach ($xml->children() as $r)
{
    $t = array();
    if(count($r->children()) == 0)
    {
        $arr[$r->getName()] = strval($r);
    }
    else
    {
        $arr[$r->getName()][] = xml2array($r);
    }
}
return $arr;
}
nixis
  • 510
  • 5
  • 10
  • You can, but why would you want to? – IMSoP Aug 29 '17 at 14:00
  • Because if index is a number, it may be a problem. Like this $variable->code->0 ? SimpleXMLElement Object ( [code] => Array ( [0] => SimpleXMLElement Object (... – nixis Nov 30 '17 at 09:21
  • `<0>` is not a valid XML tag, so that would never happen. A few valid XML names are not valid PHP names, like ``, but that can be handled with `->{'foo-bar'}` syntax. Or do you just mean accessing the `0`th element in a list of similar named elements? That's just `$parent->childName[0]`. – IMSoP Nov 30 '17 at 09:54
0
$codeZero = null;
foreach ($xml->code->children() as $child) {
   $codeZero = $child;
}

$lat = null;
foreach ($codeZero->children() as $child) {
   if (isset($child->lat)) {
      $lat = $child->lat;
   }
}
craned
  • 2,991
  • 2
  • 34
  • 38
  • I'm not sure what this code snippet is trying to show, but it looks like it's a very convoluted (and wrong) way of writing `$codeZero = $xml->code[0]` and `$lat = $xml->code[0]->lat` – IMSoP Aug 29 '17 at 14:04
  • @IMSoP, you're voting this down without even trying the code? I wouldn't have posted it if it didn't work. You're suggestion doesn't allow for a foreach, which is cleaner than a simple for loop. I don't remember all the details anymore, but it worked when other solutions didn't. – craned Sep 08 '17 at 16:03
  • I voted down mostly because it has no explanation of what it's doing. If even you don't understand it when you read it back, how is it supposed to be useful to anybody else? As for using a foreach, sure you can: `foreach ( $xml->code as $code ) { $lat = (string)$code->lat; echo $lat; }` But what you're looping over here is "all the children of the first `` element"; which doesn't make much sense to me. To clarify, `$xml->code->children()` is shorthand for `$xml->code[0]->children()`, not "children called `code`". – IMSoP Sep 08 '17 at 16:12
  • @IMSoP, Well, that's a fair point. I should have provided an explanation; it probably seemed so obvious to me at the time that it didn't occur to me. However, in my favor, at least, is the fact that whoever is on this page already knows the context and won't need an explanation because they already know what's happening. This is proven by the fact that one person already up-voted my answer or I would now be in the negative. I will do better at posting an explanation in the future. – craned Sep 08 '17 at 16:21
  • Also, I can almost guarantee I would have tried your suggestion first, and it didn't work so I had to find another. – craned Sep 08 '17 at 16:23
  • Just for fun, I've tried out your code here: https://3v4l.org/7FWgv As expected, it doesn't do anything particularly useful. – IMSoP Sep 08 '17 at 17:11
  • @IMSoP, thank you for proving as a third party that it's not actually wrong. It only does something especially useful when nothing else happens to be working. There is always something unexpected scenario when what should work isn't working. And that's what birthed my solution. And for the record, it seems rather clean to me. I don't know why you would think it to be convoluted. – craned Sep 08 '17 at 17:44
  • It depends what you mean by "wrong". It compiles. With a completely different XML structure, it could even end up with a value other than `null`. It finds the last child of the first `` element, then searches the children of that, and finds the last of *those* children which has a child called ``, and returns that. Which seems a very specific task, but maybe one you encountered. It however has absolutely nothing to do with the question on this page, to which the answer was, as the top-voted answer says, to add `(string)`. So in that sense, yes, this answer is totally wrong. – IMSoP Sep 08 '17 at 22:44
  • I *was* wrong to call it convoluted. If your requirement really was to search a 5-level deep XML document, finding the last element at the 3rd level, and the last matching a particular condition at the 4th level, it's a reasonable algorithm. However, that isn't the structure presented in this question, or in any way related to the problem posed. It's like answering a request for the score to Handel's Messiah with the lyrics to Oasis's Wonderwall - you may or may not think it's a good piece of music, but it's utterly useless for the requested purpose. – IMSoP Sep 08 '17 at 22:49
-2
foreach($xml->code as $vals )
{ 
    unset($geonames);
    $vals=(array)$vals;
    foreach($vals as $key => $value)
      {
        $value=(array)$value;
        $geonames[$key]=$value[0];
      }
}
print_r($geonames);