vyuldashev/xml-to-array
A lightweight PHP package that converts XML strings into clean, recursively nested arrays. It parses elements, attributes, and text nodes into a predictable structure, making XML easy to work with alongside json_encode, config files, or Laravel collections without dependencies.
Install via Composer — there is no ServiceProvider or facade to register, so it works in any PHP project, Laravel or otherwise:
composer require vyuldashev/xml-to-array
The entire public surface is a single static entry point:
use Vyuldashev\XmlToArray\XmlToArray;
$array = XmlToArray::convert($xmlString);
Look first at the package's src/XmlToArray.php — it is a very thin wrapper around simplexml_load_string() plus a JSON round-trip, which means everything you know about SimpleXML's quirks applies here. The readme is short; the source is shorter.
First real use case: you are consuming a third-party SOAP/REST/feed endpoint that returns XML and you want to hand the payload to Laravel's data_get(), validation, or an Eloquent create() without writing an XPath layer:
$response = Http::get('https://example.com/feed.xml');
$data = XmlToArray::convert($response->body());
$title = data_get($data, 'book.title');
$id = data_get($data, '[email protected]');
Wrap it, don't scatter it. Because the package is a one-method static, the natural pattern is a small adapter of your own so the rest of the codebase does not depend on the package directly:
final class XmlFeed
{
public static function parse(string $xml): array
{
return XmlToArray::convert($xml);
}
}
Feed ingestion into Eloquent. Map the array to models in a queued job, keeping the raw XML on disk or in a payload column for replay:
foreach (data_get($data, 'channel.item', []) as $item) {
Post::updateOrCreate(
['guid' => $item['guid']],
['title' => $item['title'], 'link' => $item['link']],
);
}
Attributes are a first-class citizen. Attribute values land under @attributes on the node they belong to, so you can pull both content and metadata from a single element without a second pass:
// <price currency="USD">19.99</price>
$node = $data['price'];
$currency = $node['@attributes']['currency']; // "USD"
$amount = $node['@content'] ?? null; // text value, see Gotchas
Repeated siblings are not always arrays. SimpleXML collapses a single occurrence into a scalar and only builds a list when there are two or more. Normalise explicitly with a helper before iterating:
$items = Arr::wrap(data_get($data, 'channel.item'));
Chaining with HTTP client and caching. Since conversion is pure string-in/array-out, cache the parsed array rather than the XML when feeds are large — Cache::remember("feed:{$id}", now()->addMinutes(10), fn () => XmlFeed::parse($xml)).
Pipeline stage. For multi-source imports, implement it as a Laravel pipeline stage (XmlToArray::convert → normaliser → validator → persister) so the conversion step stays replaceable if you later outgrow the package.
Text content is not where you expect it. Elements that carry both attributes and text get an @content (or similarly-prefixed) key instead of a plain scalar. Always check the raw dd(XmlToArray::convert($xml)) output for your specific payload before writing accessors — do not assume $data['node'] is a string.
A single child is a string, two children are an array. This is SimpleXML's behaviour leaking through. Never iterate without Arr::wrap() or a is_array() guard.
Namespaces and prefixes. SimpleXML drops or mangles namespace declarations; soap:Envelope style documents will often flatten unexpectedly. Strip or rewrite prefixes with str_replace before converting if the payload uses them consistently.
CDATA. The parser loads with LIBXML_NOCDATA, so CDATA sections should be merged into text — verify this against your actual payload, as HTML fragments inside CDATA frequently contain markup that breaks the naive round-trip.
Silent failures. There is no exception on malformed XML by default. Wrap your adapter with libxml error handling so you find out:
libxml_use_internal_errors(true);
$array = XmlToArray::convert($xml);
if ($array === [] && libxml_get_errors()) { /* log and fail */ }
libxml_clear_errors();
Encoding and BOM. A UTF-8 BOM or a <?xml encoding="ISO-8859-1"?> declaration can produce empty results or mojibake. Normalise with mb_convert_encoding() and strip BOMs before conversion.
Numeric keys. Element names that are numeric (or repeated numeric children) can be re-indexed by json_decode, turning what looks like a name into an integer key. Access those with (string) casts or data_get($data, '0.1') carefully.
Memory. The JSON round-trip doubles memory and is not stream-safe. For files above a few MB, use XMLReader with readOuterXml() instead of this package.
No validation, no schema, no XPath. This is a conversion utility, not a parser framework. If you need XSD validation or XPath queries, keep SimpleXMLElement/DOMDocument in your own layer and use this package only for the final array shape.
Extension points. XmlToArray is final/static in practice — do not subclass it. Your extension surface is a decorator class plus a normaliser that walks the array (flattening @attributes into sibling keys, casting numbers, coercing empty elements to null), which is the right place to absorb every quirk above.
Pin the version. With 36 stars and infrequent maintenance, add it to composer.json with a caret constraint and rely on your adapter tests to catch behaviour drift on update.
How can I help you explore Laravel packages today?