skip to content

In PHP, how do you read an element's text and an attribute with SimpleXML, and why do you cast the results to string?

level: juniorimportance: must knowfreq 50%

answer

  1. children as properties, attributes as array keys
  2. foreach over repeated elements
  3. every value is a SimpleXMLElement
  4. === against a string is always false
  5. missing child: use isset()

basics

~20 s

simplexml_load_string() returns a SimpleXMLElement: child elements are read as properties ($p->name) and attributes with array syntax ($p['sku']). Both return SimpleXMLElement objects, so cast with (string), (int) or (float) before comparing, storing or passing them on.

solid answer

~40 s

`simplexml_load_string($xml)` or `simplexml_load_file($path)` returns a `SimpleXMLElement` for the root, or `false` on a parse failure. Child elements are reached as properties, `$feed->product->name`, which gives the first match, and `foreach ($feed->product as $p)` walks all of them. Attributes use array access, `$p['sku']`. Every one of those results is itself a `SimpleXMLElement`, not a string, which is why the manual tells you to cast with `(string)` before comparing: `$p->name === 'Desk lamp'` is always false, the object cannot be an array key, and under `declare(strict_types=1)` passing it to a `string` parameter throws a `TypeError`. Cast numbers with `(int)` or `(float)`. A missing child does not return `null`, so test it with `isset($p->discount)`.

code

php · 26 lines
php
<?php
declare(strict_types=1);

$xml = <<<XML
<feed>
  <product sku="A-100"><name>Desk lamp</name><price currency="EUR">24.90</price></product>
  <product sku="B-200"><name>Chair</name><price currency="EUR">89.00</price></product>
</feed>
XML;

$feed = simplexml_load_string($xml);
if ($feed === false) {
    throw new RuntimeException('Feed is not well-formed XML');
}

$bySku = [];
foreach ($feed->product as $product) {
    $sku = (string) $product['sku'];
    $bySku[$sku] = [
        'name'     => (string) $product->name,
        'price'    => (float) $product->price,
        'currency' => (string) $product->price['currency'],
    ];
}
var_dump($feed->product->name === 'Desk lamp');          // bool(false) - object vs string
var_dump((string) $feed->product->name === 'Desk lamp'); // bool(true)

go deeper

for a junior

Recall the syntax: properties for child elements, array access for attributes, foreach for repeated elements, and cast to string before use.

for a middle

Explain why every value is a SimpleXMLElement, which operations break without a cast, and how isset() handles missing children.

for a senior

Map external XML into typed arrays or DTOs at the boundary, validating required fields instead of letting empty casts pass through.

for a principal

Decide where XML parsing lives in an integration and how feed schema changes are detected before they corrupt imported data.

## Loading a document SimpleXML is PHP's convenience API for XML. Two functions load a document: - `simplexml_load_string(string $data, ...)` parses a string; - `simplexml_load_file(string $filename, ...)` parses a file or URL. Both return `SimpleXMLElement|false`: the root element on success, `false` when the XML is not well-formed (with warnings unless libxml internal errors are enabled). `new SimpleXMLElement($xml)` does the same but throws an `Exception` on failure. ## Navigating: properties for elements, array syntax for attributes Given a supplier feed such as ```xml <feed> <product sku="A-100"> <name>Desk lamp</name> <price currency="EUR">24.90</price> </product> </feed> ``` the root is `<feed>`, so: | Expression | Returns | |---|---| | `$feed->product` | the first `<product>` (iterable over all of them) | | `$feed->product->name` | the first `<name>` inside it | | `$feed->product['sku']` | the `sku` attribute | | `$feed->product->price['currency']` | the `currency` attribute of `<price>` | | `$feed->product->{'list-price'}` | a child whose name is not a valid PHP identifier | | `count($feed->product)` | how many `<product>` siblings there are | `foreach ($feed->product as $product)` iterates every `<product>` child, which is the normal way to process repeated records. ## Why the cast matters Every expression in that table returns a **`SimpleXMLElement` object**, even when it represents plain text. The object converts itself to its text content when PHP asks for a string, but many operations never ask: - **strict comparison**: `$product->name === 'Desk lamp'` compares an object with a string, so it is always `false`; - **array keys**: `$bySku[$product['sku']] = $product;` fails, because an object is not a valid key; - **strict types**: with `declare(strict_types=1)`, `strlen($product->name)` throws a `TypeError`, since strict mode does not convert objects to `string`; - **numbers**: arithmetic on `$product->price` needs a number, so write `(float) $product->price`; - **storage**: putting the object into a session, cache or JSON payload stores the object's structure, not the text. The manual's own guidance is to cast with `(string)` whenever an element or attribute is compared with a string or passed to a function that needs one. In practice, cast once when you map the element into your own array or DTO: ```php $row = [ 'sku' => (string) $product['sku'], 'name' => (string) $product->name, 'price' => (float) $product->price, ]; ``` ## Missing elements and attributes Asking for a child that does not exist does not return `null`; it returns an empty `SimpleXMLElement`, and chaining further properties on it does not fail. Use `isset()`: - `isset($product->discount)` is `false` when there is no `<discount>` element; - `isset($product['sku'])` checks an attribute. Casting a missing element with `(string)` gives `''`, which can silently turn a required field into an empty value, so validate required fields explicitly. ## Where SimpleXML stops SimpleXML is ideal for reading and light edits (`addChild()`, `addAttribute()`, `asXML()`). It is not the tool for moving nodes, handling mixed content in detail, or parsing a multi-gigabyte file, since it loads the whole document into memory. ## Iterating and counting correctly A few idioms cover almost every read: - `foreach ($feed->product as $product)` visits every `<product>` child of `<feed>`; - `foreach ($product->children() as $child)` visits all child elements, whatever their names, and `$child->getName()` returns each name; - `foreach ($product->attributes() as $name => $value)` lists attributes; - `$feed->product[1]` is the second `<product>`, since numeric offsets pick among same-named siblings. ## A mapping checklist 1. Check the loader result for `false` before navigating. 2. Cast every value once, at the point where it leaves SimpleXML. 3. Use `isset()` for optional elements and attributes; fail on missing required ones. 4. Never pass `SimpleXMLElement` objects deeper into the application.

  • How do you tell a missing <discount> element from an empty one in SimpleXML?
    `isset($product->discount)` is `false` when the element is absent and `true` when `<discount/>` exists, even if empty. `(string) $product->discount` returns `''` in both cases, so checking the cast value alone cannot separate them.
  • What does simplexml_load_string() return for malformed XML?
    It returns `false` and, unless `libxml_use_internal_errors(true)` is active, emits warnings describing the parse errors. The constructor `new SimpleXMLElement($xml)` instead throws an `Exception`. Check for `false` before navigating.

saying these in an interview costs you the question

  • Believing $product->name returns a plain PHP string
  • Reading attributes as properties, like $product->sku
  • Expecting a missing child element to be null
  • Using an uncast SimpleXMLElement as an array key
  • Comparing SimpleXML values with === without casting