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

sabre/xml

sabre/xml is a specialized XML reader and writer for PHP, offering a simple, namespace-aware API for parsing and generating XML. It supports PHP 7.4 and 8, with full type declarations in version 3.

View on GitHub
Deep Wiki
Context7

Getting Started

sabre/xml is a standalone PHP library (no Laravel service provider, no config file), so setup is just Composer:

composer require sabre/xml

Check the version constraint that resolves: 4.1+ requires PHP 8.2, 4.0.x supports PHP 7.4–8.1, and 3.x is the older typed line. If you extend Reader, Writer, or Service, v3+ requires you to match its parameter and return type declarations.

Read the four short pages at sabre.io/xml in this order: Introduction → Installation → Reading XML → Writing XML. They are the authoritative reference; the repo README is essentially an index. The single class you should understand first is Sabre\Xml\Service, which is a thin facade over Reader and Writer:

use Sabre\Xml\Service;

$service = new Service();

// Read: returns whatever the root element's deserializer produced
$data = $service->parse($xmlString);

// Write: returns an XML string
$xml = $service->write('order', [
    'id'     => 1234,
    'status' => 'shipped',
]);

Your first real use case will almost always be one of two things: parsing an XML response from a third party (shipping carrier, SOAP endpoint, bank/payment file) into an array or DTO, or generating a fixed XML document for an outbound integration. Start by hand-writing the smallest possible elementMap for the one document you actually need — don't try to model the whole schema up front. Service::parse() on a real fixture, dumped with dd(), tells you more in ten seconds than any amount of schema reading.

Implementation Patterns

Declarative mapping via elementMap and classMap. The idiomatic pattern is to stop hand-walking nodes and instead declare, per element, "how do I turn this into a value?" Both Service and Reader expose elementMap (Clark-notation name → callable) and classMap (Clark-notation name → class implementing XmlDeserializable):

use Sabre\Xml\Service;

$service = new Service();
$service->elementMap = [
    '{urn:example}order' => \App\Xml\Order::class,          // classMap-style
    '{urn:example}line'  => \App\Xml\Line::class,
    '{urn:example}note'  => 'Sabre\Xml\Element\XmlFragment', // keep raw
];

DTOs that round-trip. Implement both interfaces on the same class and the same object can be read and written:

class Order implements XmlSerializable, XmlDeserializable
{
    public int $id;
    public array $lines = [];

    public static function xmlDeserialize(Reader $reader): self
    {
        $self = new self();
        // keyValue() flattens children into an array; attributes land in '@attributes'
        $values = Deserializer\keyValue($reader);
        $self->id = (int) ($values['id'] ?? 0);
        return $self;
    }

    public function xmlSerialize(Writer $writer): void
    {
        $writer->write(['id' => $this->id, 'line' => $this->lines]);
    }
}

Note that xmlDeserialize is static and receives the already-positioned Reader; you must consume the element (the helper functions do this for you) or you'll desynchronise the stream.

Use the bundled deserializers instead of writing loops. Sabre\Xml\Deserializer has keyValue() (children → assoc array, attributes → @attributes), elementList() (repeated children → list), firstValue() (first child's value), getAttribute() (read a specific attribute), and functionCaller() (delegate to a closure — handy for one-off nodes). These compose well: build your map out of these before reaching for custom code.

Namespace handling is Clark notation everywhere. Keys are {http://namespace-uri}localName, never prefix:localName. On read this is automatic. On write, prefixes are resolved through the static Writer::$namespaceMap, so set it once at boot:

\Sabre\Xml\Writer::$namespaceMap = [
    'urn:example' => 'ex',
];

Unmapped namespaces get emitted as a default xmlns="..." declaration, which is correct but can surprise you if the consumer expects a prefix.

Pass unknown content through with XmlFragment. When a document has signed or vendor-specific subtrees you don't care about, map them to Sabre\Xml\Element\XmlFragment (or Base for a generic array) so the data isn't silently dropped and can be re-serialized unchanged. Use Sabre\Xml\Element\Cdata for text that must not be escaped.

In Laravel, register a configured Service as a singleton with your element maps bound at construction, and inject it into small action classes or jobs. Heavy XML work (batch invoice generation, multi-megabyte carrier responses) belongs in a queued job, not the request cycle. Writer::write() returns a string, so it drops straight into Storage::put(), an HTTP client body, or a response()->stream().

Stream large documents with Reader directly. Service::parse() takes a string; for a file on disk, construct a Reader and hand it an already-open XMLReader pointed at the path, wrapping the whole loop in a try/finally so you can close it.

Gotchas and Tips

  • Writer::$namespaceMap is global static state. Mutating it per-request (e.g. in a multi-tenant app) leaks into later writes in the same process, and tests will pass or fail depending on order. Configure it in a bootstrapper and treat it as immutable, or set it in setUp()/tearDown() of your tests.
  • Service::parse() is not a validator. It happily accepts structurally-wrong-but-well-formed XML. For compliance/EDI/invoicing work, run DOMDocument::schemaValidate() (or a Schematron pass) after parsing; don't assume a clean round-trip means the payload was valid.
  • XXE and entity loading. The library disables entity loading, but the behaviour differs by PHP/libxml version and has been patched repeatedly. If you parse untrusted XML, also pass LIBXML_NONET and never enable LIBXML_NOENT.
  • Empty and whitespace-only elements caused an infinite loop in older versions (fixed around 2.1.2/2.1.3, and again for closed resources in 4.0.6). If you inherit an old lockfile, bump it. Text values also may arrive with surrounding whitespace from pretty-printed input — trim() defensively.
  • expect() throws Sabre\Xml\ParseException on a name mismatch or malformed XML. Wrap calls in try/catch and translate to a domain exception; the raw message ("expected {ns}foo, got {ns}bar") is good for logs but not for users.
  • Type declarations bite on upgrade. v3 and later added strict parameter/return types across the public API. Subclassing Writer or overriding xmlDeserialize without matching signatures produces fatal errors, not deprecation notices.
  • Don't call Writer::write() inside xmlSerialize(). You're handed a live $writer; use writeElement(), writeAttributes(), startElement()/endElement(), writeText(). A nested static write starts a second document and corrupts output.
  • Attributes aren't inferred on write. Scalars become text and XmlSerializable objects get serialized, but nothing inspects your array for attribute intent — call writeAttributes([...]) immediately after startElement(), before any content.
  • Missing elementMap entries fail silently. Unmapped elements fall back to the generic Base deserializer (array of children), so a typo in a namespace URI gives you an array instead of your DTO with no error. When debugging, dump $reader->getClark() inside a functionCaller() to confirm exactly which Clark name is being matched.
  • Debug from the Reader. readInnerXml() returns the raw markup of the current element — invaluable for inspecting a subtree that deserialized unexpectedly — but it's only valid while positioned on an element start.
  • Extension points worth knowing: Service::getReader()/getWriter() let you pre-configure a reader or writer (indentation via setIndent(true), startDocument() for an XML declaration, openMemory()/outputMemory() for fragment building) before the facade uses it. Subclass Service when you need environment-specific maps rather than mutating the singleton.
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.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table