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 Iterator Laravel Package

halilim/xml-iterator

halilim/xml-iterator offers a simple, memory-efficient way to iterate over XML in PHP. Built on XMLReader, it lets you loop through nodes with foreach and parse large XML files without loading them fully into memory—ideal for big feeds, exports, and streaming XML data.

Deep Wiki
Context7

Getting Started

halilim/xml-iterator is a thin, streaming wrapper around PHP's XMLReader. You give it an XML source and the name of the repeating element you care about; it yields each occurrence as an array (or object), parsing lazily so a 500 MB supplier feed doesn't land in memory. It is not a Laravel package — no service provider, no facade, no config file — just a class you new up. That's a feature: zero bootstrapping, trivial to drop into a command or job.

composer require halilim/xml-iterator

Minimal first use case — an artisan command that reads a product feed:

use Halilim\XmlIterator\XmlIterator;

class ImportProducts extends Command
{
    protected $signature = 'import:products {file}';

    public function handle(): int
    {
        // Second argument = the repeating element you want to iterate over
        $items = new XmlIterator($this->argument('file'), 'product');

        foreach ($items as $product) {
            // $product is an associative array for this single <product> node
            $this->line($product['sku'] ?? '?');
        }

        return self::SUCCESS;
    }
}

Where to look first: open src/XmlIterator.php and read the constructor and the option handling — the third constructor argument is an options array and the class exposes constants/flags you'll want to know about before writing anything non-trivial. Then read the tests; with a package this small the tests are the documentation for edge cases (attributes, nested tags, CDATA, empty elements). There is no config/xml-iterator.php, so don't go hunting for one.

The realistic first use case is exactly the above: a scheduled import of a partner/vendor XML feed into Eloquent. Reach for this package whenever simplexml_load_file() would make your queue worker exceed memory_limit, but you don't want to hand-roll XMLReader::read() state-machine code.

Implementation Patterns

Wrap it behind your own reader class. Because it's a 19-star dependency, treat it as an implementation detail. Define a small contract in your app and let the package be one adapter:

interface FeedReader
{
    /** @return iterable<array<string, mixed>> */
    public function items(string $path, string $element): iterable;
}

class XmlIteratorFeedReader implements FeedReader
{
    public function items(string $path, string $element): iterable
    {
        return new XmlIterator($path, $element);
    }
}

If the package goes stale, you swap the adapter for a raw XMLReader loop and nothing else changes.

Download to a real file path, then iterate. XMLReader wants a URI/path, so stream large feeds to disk rather than buffering them into a string:

$tmp = Storage::path('imports/'.Str::uuid().'.xml');
Http::withOptions(['sink' => $tmp])->get($feedUrl)->throw();

foreach ($reader->items($tmp, 'product') as $row) {
    // ...
}

If you use S3 or another non-local disk, copy to a local temp path first — remote stream wrappers are slow and re-read unpredictably.

Normalise into a DTO, not an array, at the boundary. The array shape is whatever the XML says: missing tags simply won't be keys, single vs. repeated children change structure, and everything is a string. Cast immediately:

$product = ProductData::fromArray($row); // trim, (int)/(float), bool from 'true'/'1'

Batch the writes. Iterating is cheap; 40k individual INSERTs are not. Buffer ~500 rows and upsert() inside a transaction, or dispatch a chunked job. Because the iterator is a generator underneath, memory stays flat as long as you flush the buffer.

Use it inside a queued job with a bounded runtime. One job per file, $timeout set generously, and $this->fail() on parse exceptions so the queue doesn't retry a corrupt feed forever. Combine with Laravel's LazyCollection if you want map/filter/chunk ergonomics over the iterator.

Attributes and namespaces. Attribute access and default-namespace handling are the usual friction points. Check how your version surfaces attributes (they typically appear as keys on the node array) and test with a real-world feed early — feeds from ERP systems love xsi: prefixed attributes and default namespaces that will otherwise silently produce empty arrays.

Test with fixture files, not inline strings. Keep two or three trimmed-but-realistic XML files in tests/fixtures/ — one with attributes, one with a single repeated child, one deliberately malformed — and assert on the yielded array shape. That's your regression net against package upgrades.

Gotchas and Tips

Never iterator_to_array() it. That defeats the entire point and will OOM on the files you bought this package for. Keep the foreach and flush as you go. Same for collect($iterator) on a large feed.

Path vs. XML string ambiguity. If the constructor accepts both raw XML and a filename, a short string that happens to be a valid path can be misinterpreted. Pass unambiguous absolute paths, or check the underlying behaviour in the source and be explicit in your wrapper.

Iterators may not rewind. Streaming iterators generally can't be traversed twice; a second foreach over the same instance may yield nothing or throw. Construct a new instance per pass, and don't stash it in a singleton.

Memory can still creep from a single huge text node (a giant <description> CDATA block) — the reader has to materialise that one node. If your feed has megabyte-sized fields, you'll need a lower-level approach for those.

Malformed XML fails mid-stream. You'll have already written half the rows. Make imports idempotent (upsert on a natural key) and either wrap in a transaction (bad for very large imports) or track a "last successful run" marker so a retry doesn't duplicate. This is the number-one operational pitfall with streaming imports.

Silent empty results. If the element name doesn't match — wrong case, wrong namespace prefix, nested one level deeper than you assumed — you get zero iterations and no error. Always log/assert the count and fail loudly on 0 when the feed should have data.

Everything is a string. "0", "false", and empty elements all need explicit handling; (bool) "false" is true. Cast deliberately in your DTO factory.

No validation, no XPath, no schema. This package iterates one element name at the top of the tree you point it at. It won't filter by predicate, won't validate against XSD, and won't give you random access. If you need "only products where stock > 0", that's a continue inside your loop.

Extension points are limited to subclassing (override the element-handling methods if exposed) or decorators around the iterator. Don't fight it; if you need fine control over XMLReader flags, entity substitution, encoding, or error suppression, drop to raw XMLReader — it's ~30 lines and you own it.

Pin the version and watch the bus factor. One maintainer, modest star count, last release date notwithstanding. Lock a specific minor version in composer.json, keep the adapter wrapper, and add the fixture tests so an upgrade can't quietly change array shapes on you. Also verify the PHP version constraint against your Laravel runtime before adopting.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel