sabre/xml
sabre/xml is a specialized XML reader and writer for PHP. It makes it easy to parse XML into structured data and generate XML with custom serializers. Supports PHP 7.4+ and PHP 8, with strict type declarations in v3.
Installation:
composer require sabre/xml
Target PHP 8.2+ for full type safety (use 4.x branch) or PHP 7.4+ for legacy support (3.x branch).
First Use Case: Parsing XML Parse a simple XML string into a structured array:
use Sabre\Xml\Reader;
$xml = '<root><item>value</item></root>';
$reader = new Reader();
$result = $reader->parse($xml);
// Result: ['root' => ['item' => 'value']]
First Use Case: Writing XML Generate XML from an array:
use Sabre\Xml\Writer;
$writer = new Writer();
$xml = $writer->write(['root' => ['item' => 'value']]);
// Output: <root><item>value</item></root>
Key Entry Points:
Reader: For parsing XML into PHP arrays/objects (use parse(), parseCurrentElement()).Writer: For generating XML from PHP data (use write(), writeElement()).Service: Combined reader/writer with advanced features (e.g., custom deserializers).Where to Look First:
Pattern: Use Reader for simple cases, Service for complex schemas.
// Basic parsing
$reader = new Reader();
$data = $reader->parse(file_get_contents('data.xml'));
// With custom deserializers (e.g., convert XML to DTOs)
$service = new Service();
$service->addDeserializer('date', fn($value) => new DateTime($value));
$data = $service->parse($xml);
Laravel Integration: Bind to the container for reuse:
$this->app->singleton(Reader::class, fn() => new Reader());
$this->app->singleton(Service::class, fn() => new Service());
Pattern: Use Writer for arrays/collections, Service for objects.
// From a Laravel Collection
$collection = collect(['item1', 'item2']);
$writer = new Writer();
$xml = $writer->write($collection->toArray());
// From a Laravel Model
$service = new Service();
$xml = $service->write($model->toArray());
Dynamic XML with Namespaces:
$writer = new Writer();
$writer->xmlns('ns', 'http://example.com/ns');
$xml = $writer->write(['ns:item' => 'value']);
Pattern: Leverage Service for custom deserializers and namespace handling.
$service = new Service();
$service->addDeserializer('soap:Envelope', fn($node) => [
'header' => $service->parseCurrentElement($node->header),
'body' => $service->parseCurrentElement($node->body),
]);
$soapData = $service->parse($soapXml);
Laravel Example: SOAP API Client
public function handleSoapRequest(string $xml)
{
$service = app(Service::class);
$service->addDeserializer('ns:request', fn($node) => [
'action' => $node->action,
'params' => $service->parseCurrentElement($node->params),
]);
return $service->parse($xml);
}
Pattern: Use Reader/Service exceptions to validate XML.
try {
$reader = new Reader();
$data = $reader->parse($xml);
} catch (\Sabre\Xml\Exception\ParseError $e) {
Log::error("XML validation failed: " . $e->getMessage());
throw new \RuntimeException("Invalid XML received", 0, $e);
}
Laravel Form Request Validation:
public function rules()
{
return [
'xml_data' => ['required', function ($attribute, $value, $fail) {
try {
$reader = new Reader();
$reader->parse($value);
} catch (\Sabre\Xml\Exception\ParseError $e) {
$fail('The XML is invalid: ' . $e->getMessage());
}
}],
];
}
Pattern: Offload XML-heavy tasks to Laravel Queues.
// Job class
public function handle()
{
$service = app(Service::class);
$data = $service->parse(file_get_contents($this->xmlPath));
// Process data...
}
// Dispatch
ParseXmlJob::dispatch($xmlFile)->onQueue('xml-processing');
Parse XML responses from APIs:
$response = Http::get('https://api.example.com/data.xml');
$reader = new Reader();
$data = $reader->parse($response->body());
Hydrate models from XML:
$service = new Service();
$service->addDeserializer('user', fn($node) => [
'name' => $node->name,
'email' => $node->email,
]);
$users = collect($service->parse($xml))->map(fn($u) => User::create($u));
Generate XML dynamically in views:
// Controller
public function showXml()
{
return view('xml.output', ['data' => $this->prepareXmlData()]);
}
// Blade template
@php
$writer = new \Sabre\Xml\Writer();
echo $writer->write($data);
@endphp
Extend Service for domain-specific parsing:
$service = new Service();
$service->addDeserializer('invoice:item', function ($node) {
return [
'sku' => $node->sku,
'quantity' => (int) $node->quantity,
'price' => (float) $node->price,
];
});
Handle namespaced XML:
$service = new Service();
$service->xmlns('ns', 'http://example.com/ns');
$data = $service->parse('<ns:root><ns:item>value</ns:item></ns:root>');
Parse Clark notation (e.g., {namespace}localName):
$service = new Service();
$node = $service->parseClarkNotation('{http://example.com/ns}item', $xml);
Closed Resources
Reader::parse() or Service::parse() throws an exception.file_get_contents() first.
// Bad: $fp = fopen(...); $reader->parse($fp); fclose($fp); // Fails
// Good: $reader->parse(file_get_contents($path));
Empty XML Elements
<item></item> can cause infinite loops in older versions.libxml_use_internal_errors() before parsing.Namespace Handling
Service::xmlns() or use parseClarkNotation().
$service->xmlns('ns', 'http://example.com/ns');
$data = $service->parse('<ns:root><item>value</item></ns:root>');
Type Safety in v3+
Reader/Writer requires adding type declarations (e.g., array|objectHow can I help you explore Laravel packages today?