dragon-code/xml-core
A lightweight XML core for Laravel and PHP that provides a simple, fluent API for building, parsing, and validating XML documents. It supports attributes, namespaces, and CDATA, helping you generate structured XML responses and handle common XML tasks without verbose boilerplate.
Before you install anything, look at what this actually is: a low-traffic, single-maintainer, zero-dependency package whose entire public surface is small enough to read in one sitting. That's not a reason to skip it, but it does change the adoption calculus — you're evaluating a snippet library, not a framework component. Clone the repo and read src/ first; you'll almost certainly finish in under ten minutes, and that read will tell you more than any README.
Release 2.4.0 is the useful signal here: it added explicit Laravel 12 support. So the package is maintained, not archived — but "maintained" here means "the maintainer merges a framework-compat PR and a bot bumps the LICENSE year," not "active feature development." Plan accordingly.
Installation is the standard Composer flow:
composer require dragon-code/xml-core
There is no Laravel service provider to register and no config to publish — it's a plain PHP utility, which is the main thing it has going for it. Autoloading is PSR-4 under the DragonCode\XmlCore\ namespace. With 2.4.0 out, the illuminate/* constraints won't block a Laravel 12 upgrade, so you can adopt it without pinning your framework back.
Your first use case should be the smallest one you have: building a short XML document from an array or a small set of objects, or parsing a known-good XML payload from a third-party API into PHP structures. Start with a scratch script, not a feature branch:
use DragonCode\XmlCore\...; // confirm exact class names from src/ — the API is minimal
$xml = /* construct from array/object */;
echo $xml->asXML(); // or the package's equivalent serializer
If the output matches your expectation after an afternoon of poking, you have your answer. If you find yourself writing adapter code to make its shape fit yours, stop — the package is too small to justify a fight, and a 40-line SimpleXMLElement wrapper of your own will be easier to own than a dependency with a bus factor of one.
Treat it as a value-object layer, not an ORM. The useful pattern is: domain objects in, XML string out (or XML in, plain arrays out), with the package confined to a single "serialization" boundary in your app. Something like App\Support\Xml\InvoiceWriter that internally uses xml-core and exposes toString(): string. That keeps the dependency behind one seam you can replace later without touching callers.
Generation pipelines (feeds, sitemaps, invoices, exports). This is where a tiny XML helper earns its keep in Laravel:
// app/Support/Xml/ProductFeed.php
final class ProductFeed
{
public function __construct(private iterable $products) {}
public function render(): string
{
// build via xml-core, return a string
}
}
Then in a controller: return response($feed->render(), 200, ['Content-Type' => 'application/xml; charset=UTF-8']); — or response()->make(...) if you want the same headers through the framework. Do not rely on response()->xml(); it expects a view or array shape and will fight you.
Queue the heavy stuff. XML generation over more than a few hundred records belongs in a job that writes to storage, not in a request. The package won't stream, so the pattern is: job builds the document, writes to Storage::put(), notification or DB row points at it.
Parsing inbound payloads. Wrap parsing in a dedicated class that returns typed DTOs, and validate before you parse — Validator::make() on the raw string's size, or a schema check with DOMDocument::schemaValidate() if the counterparty publishes an XSD. Never hand untrusted XML straight to a helper and hope.
Testing. Keep fixture XML files in tests/Fixtures/xml/ and assert on the parsed structure, not the serialized string — attribute ordering and self-closing tag style are not stable contracts. For generation, a single golden-file test per document type is enough; diffing full strings catches encoding and escaping regressions cheaply. If you do upgrade to a new minor (2.4.x → 2.5.x), run that golden-file suite before touching anything else; it's the cheapest possible regression net for a package with this little coverage.
Integration tip. If you're already deep in the DragonCode ecosystem (Laravel Cache, etc.), the stylistic consistency is a mild plus. If you're not, there's no ecosystem benefit to capture — judge it purely on the code. The Laravel 12 bump means the compatibility box is ticked, but it doesn't change the "small utility, own the seam" calculus.
Maintained, but thinly. 2.4.0 proves the package still accepts framework-compat PRs, which is more than the previous assessment could claim — but the release is a version bump and a license-year commit, not a feature or a fix. Pin the minor ("dragon-code/xml-core": "^2.4") rather than floating, watch the changelog for the next release to gauge whether this is a pattern or a one-off, and keep the isolation seam described above. For a package this small, the realistic long-term move remains to fork or vendor it into app/Support/Xml with a comment pointing at the upstream commit — you lose nothing and gain the ability to fix things.
Laravel 12 support is additive, not a rewrite. Nothing in 2.4.0 changes the public API, so an existing 2.3.x integration should upgrade cleanly. That also means the code you read in src/ during your first evaluation is still the code you'll be depending on — no new abstractions to learn, no new extension points.
XXE is the pitfall that bites. Any XML parsing path must disable external entity loading. Modern libxml (PHP 8+) defaults are safer, but be explicit: pass LIBXML_NONET and avoid LIBXML_NOENT. If the package's parser doesn't expose flags, that's a hard blocker for untrusted input — wrap DOMDocument yourself with libxml_set_external_entity_loader(fn () => null) and $dom->loadXML($xml, LIBXML_NONET).
Memory. SimpleXMLElement/DOMDocument load the whole document. A 50 MB feed will blow memory_limit and there's no streaming mode here. If you need streaming, this is the wrong tool — reach for XMLReader/XMLWriter (or halaxa/json-machine's XML cousin) and don't try to bend xml-core into that role.
Encoding. The XML declaration's encoding="UTF-8" is a claim, not a conversion. Make sure your source strings are actually UTF-8 (mb_check_encoding) before they go in, or you'll ship mojibake that only shows up in one downstream consumer. Also strip control characters (\x00–\x08, \x0B, \x0C, \x0E–\x1F) — XML 1.0 forbids them outright and DOMDocument will produce invalid output without complaining loudly.
Namespaces are awkward everywhere. If your documents use XML namespaces, budget extra time. SimpleXML's namespace handling (children($ns), attributes($ns)) is famously fiddly, and a thin wrapper rarely improves on it. Test with a real namespaced payload early.
CDATA and escaping. Verify how the package escapes text containing <, &, ". If it doesn't offer a CDATA option, pre-sanitize or accept entity escaping — don't hand-concatenate <![CDATA[...]]> around untrusted strings.
Debugging. When output looks wrong, dump ->asXML() (or the package's raw serializer) and inspect. For parse failures, register a libxml error collector:
libxml_use_internal_errors(true);
// ... parse ...
foreach (libxml_get_errors() as $e) { logger()->debug($e->message, ['line' => $e->line]); }
libxml_clear_errors();
Extension points. Given the package size, subclassing is fine but rarely worth it — prefer composition (your writer class uses xml-core) so you can swap the internals. If you find yourself needing to extend the package's behavior in three places, that's your signal to inline it.
The verdict, briefly. 2.4.0 moves this from "archived curiosity" to "thinly maintained utility." That's a real improvement, and if you're on Laravel 12 it's now a friction-free install. For a greenfield app with a handful of XML touchpoints, DOMDocument/SimpleXMLElement directly is still the lower-ownership option — you'll write roughly the same amount of code and own it completely. Adopt xml-core if your first read of src/ shows it already solves a specific annoyance you have (attribute handling, array-to-node mapping) and you're comfortable keeping a single-maintainer dependency behind one seam. It's not a liability if you isolate it; it's a bad idea if it spreads across your codebase.
How can I help you explore Laravel packages today?