Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xml To Array Laravel Package

mtownsend/xml-to-array

Easily convert XML into a clean, traversable PHP array and back again. This lightweight package handles nested elements, attributes, and namespaces, so you can consume XML feeds or APIs in Laravel and plain PHP projects without writing repetitive parsing code.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require mtownsend/xml-to-array

That is the whole setup. The package is framework-agnostic and exposes a single static entry point, so there is nothing to register in config/app.php, no facade to alias, and no config file to publish. In Laravel it is available immediately after composer dump-autoload.

Look here first

  1. vendor/mtownsend/xml-to-array/src/XmlToArray.php — the entire implementation is roughly a hundred lines built on SimpleXMLElement. Read it once; you will understand every behavior and gotcha in about five minutes, which is worth more than any documentation.
  2. vendor/mtownsend/xml-to-array/composer.json — confirms the PHP constraint and that there are no transitive dependencies.
  3. vendor/mtownsend/xml-to-array/tests/ — fixtures showing exactly how attributes, CDATA, and repeated nodes are shaped.

First use case

The canonical job for this package is turning a third-party XML API response into something you can hand to collect() or data_get():

use Mtownsend\XmlToArray\XmlToArray;

$xml = <<<XML
<order id="A-1042">
  <customer>Acme Ltd</customer>
  <item sku="X1"><qty>2</qty></item>
  <item sku="X2"><qty>1</qty></item>
</order>
XML;

$array = XmlToArray::convert($xml);

// [
//   '@attributes' => ['id' => 'A-1042'],
//   'customer'    => 'Acme Ltd',
//   'item'        => [
//       ['@attributes' => ['sku' => 'X1'], 'qty' => '2'],
//       ['@attributes' => ['sku' => 'X2'], 'qty' => '1'],
//   ],
// ]

From there collect($array['item'])->sum('qty') or data_get($array, '[email protected]') gets you to application code in one line. Note the values are strings — '2', not 2.

Implementation Patterns

Wrap it in your own service. Even though the call is one line, do not scatter XmlToArray::convert() through controllers and jobs. Put it behind an interface so you can swap in XMLReader, spatie/array-to-xml's inverse, or a hand-rolled parser when a document outgrows this package:

final class XmlParser
{
    public function parse(string $xml): array
    {
        return XmlToArray::convert($this->stripBom(trim($xml)));
    }
}

Bind it in a service provider and inject it. This also gives you one place to add logging, metrics, and sanitization.

Parsing HTTP responses. The package takes a string, so pair it with Laravel's HTTP client rather than simplexml_load_file on a URL — you get retries, timeouts, and testability for free:

$response = Http::timeout(10)->retry(2, 200)->get($endpoint);
$data = $this->parser->parse($response->body());

Normalize the "one vs many" shape. XML has no concept of an array; a repeated element with a single occurrence comes back as a scalar, and with two or more as a list. Normalize at the boundary so downstream code never branches:

private function listify(array $node, string $key): array
{
    $value = $node[$key] ?? [];
    return array_is_list($value) ? $value : [$value];
}

Map to DTOs immediately. Arrays from XML are stringly-typed and keyed by whatever the vendor chose. Convert once, at the edge, into typed objects:

$order = new OrderDto(
    reference: $array['@attributes']['id'],
    customer:  $array['customer'],
    lines:     collect($this->listify($array, 'item'))
                 ->map(fn ($i) => new LineDto($i['@attributes']['sku'], (int) $i['qty']))
                 ->all(),
);

Attribute-aware traversal. Attributes live under @attributes and mixed content text may live under @value. Write two tiny helpers — attr($node, 'id') and text($node) — instead of repeating the magic keys, and you can change the convention in one place if you ever migrate parsers.

Fixture-driven tests. Because the package has essentially no community surface, your test suite is the contract. Store one representative XML file per endpoint in tests/Fixtures/, snapshot the converted array, and assert on your DTOs. This is what protects you when the vendor changes their schema.

Caching. For slow-changing documents (config feeds, partner catalogues, sitemaps), parse once and cache the resulting array with Cache::remember(). Parsing itself is fast, but the network fetch is not, and the array is trivially serializable.

Pipeline placement. Do the conversion in a queued job or artisan command, not in a request/response cycle. Persist the normalized rows to the database, then serve those. That keeps the raw-XML dependency out of your hot path and lets you re-parse on failure without blocking users.

Gotchas and Tips

Whole-document memory usage. This is a DOM-style parse: the full tree and the full array exist in memory simultaneously. Fine for kilobytes and low megabytes; wrong tool for a 200 MB product feed. Above roughly a few MB per document, switch to XMLReader (streaming) and reserve this package for per-record fragments.

XXE and entity expansion. The conversion path goes through libxml, and you cannot pass LIBXML_NONET or entity flags through the static convert(). For untrusted input, reject documents containing a DOCTYPE before parsing, keep libxml_disable_entity_loader expectations in mind, and never point it at arbitrary user-supplied URLs.

Inconsistent scalar/list shapes. As above, item is a string-or-array landmine. Any code doing foreach ($array['item'] as ...) will silently iterate characters when there is exactly one item. Always normalize.

Everything is a string. '0' is falsy-adjacent, dates are strings, and @attributes values are strings too. Cast explicitly at the DTO boundary; never rely on PHP's loose comparisons of parsed values.

Empty and self-closing elements. <foo/> and <foo></foo> typically become empty strings or empty arrays depending on context — not null, and not missing. Check blank() rather than isset() alone, and decide deliberately whether empty means "absent" in your domain.

Attributes vs. text collision. An element with both attributes and text content (<price currency="EUR">9.99</price>) forces the parser to choose a representation, usually attributes plus @value. Verify with a fixture before shipping — do not assume.

Whitespace and pretty-printing. Indented XML can produce whitespace-only text nodes. trim() inputs, and if you get empty sibling keys in arrays, re-check with minified XML to confirm it is formatting rather than schema.

Namespaces. Prefixed elements (<ns:item>) may end up with the prefix stripped, retained, or dropped entirely depending on the parser. Test with the exact namespace shape your vendor sends; do not rely on the prefix surviving into your array keys.

BOM and encoding. A UTF-8 BOM from a downloaded file will break the parse with a confusing error. ltrim($xml, "\xEF\xBB\xBF") before calling convert(). Non-UTF-8 declarations (ISO-8859-1) are usually handled by libxml, but the values you get back may surprise you.

Error handling. Invalid XML surfaces as an exception from the underlying SimpleXML layer rather than a clean boolean, so wrap the call in try/catch, log the offending payload (redacted) to a dedicated channel, and return a typed failure. A cron job that throws a raw Exception on one malformed feed record is a production incident waiting to happen.

Extension points are thin by design. The class is static and small. If you need custom attribute handling, namespace resolution, or type coercion, the pragmatic move is to copy the source into app/Support/ and own it — the file is short enough that forking costs less than fighting the API.

Dependency risk. With ~116 stars and no dependents, you are the support channel. Pin the exact version in composer.json, add a test that exercises the shapes you depend on, and keep your XmlParser wrapper so a future swap is a one-class change. The upside is that a package this small is very unlikely to break under you.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource