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

Laravel Xml Laravel Package

fetchleo/laravel-xml

fetchleo/laravel-xml brings simple XML handling to Laravel: parse XML strings or files into arrays and collections, and generate clean XML from models or arrays with an expressive API. Handy for feeds, sitemaps, SOAP APIs, and third-party integrations, with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin conversion layer, not a full XML toolkit. Install it, confirm auto-discovery, and verify the exact facade namespace in vendor/fetchleo/laravel-xml/src — the public surface is small (one converter class, a facade, and likely a helper/macro).

composer require fetchleo/laravel-xml

Look first at:

  1. vendor/fetchleo/laravel-xml/src/ — the converter class and service provider (this tells you the real method names).
  2. config/xml.php if present — root element name, encoding, version, pretty-print defaults.
  3. The README's examples — they are usually copy-pasteable and match the version you installed.

First use case: returning XML from a controller instead of JSON for a legacy consumer.

use FetchLeo\LaravelXml\Facades\Xml;

Route::get('/users', function () {
    $xml = Xml::convert(User::with('posts')->get(), 'users');

    return response($xml, 200)
        ->header('Content-Type', 'application/xml');
});

If vendor:publish exposes a config tag, publish it early — the root element default is the single most common thing you'll want to change.

php artisan vendor:publish --tag=xml-config

Implementation Patterns

Arrays are the canonical input. Everything else (models, collections, resources) is normalized to an array first. Design your XML shape as an array, then feed the converter.

$payload = [
    'user' => [
        '@id'    => $user->id,          // attribute
        'name'   => $user->name,
        'email'  => $user->email,
        'roles'  => [
            'role' => $user->roles->pluck('name')->all(),
        ],
    ],
];

return response(Xml::convert($payload), 200)
    ->header('Content-Type', 'application/xml');

Wrap collections explicitly. An indexed array of scalars becomes repeated sibling elements, but you almost always want a named container. ['item' => $items] is the pattern to reach for; don't rely on the package to infer a plural name.

Treat models as data, not as XML. Rather than calling $model->toXml() directly (if a macro exists), funnel through toArray() so $hidden, $visible, and casts apply consistently, and so relation recursion stays predictable.

$xml = Xml::convert(['user' => $user->toArray()], 'user');

Use a dedicated resource for anything non-trivial. Mirror Laravel's JsonResource with a small toXml() method so controllers stay one-liners and the XML contract lives in one place.

class UserXmlResource
{
    public function __construct(protected User $user) {}

    public function toXml(): string
    {
        return Xml::convert([
            'user' => [
                '@id'   => $this->user->id,
                'name'  => $this->user->name,
                'email' => $this->user->email,
            ],
        ]);
    }
}

Set the response header yourself. Laravel's response() will not infer application/xml. Add a small macro or response helper if you ship more than one endpoint.

Response::macro('xml', fn (string $content, int $status = 200) =>
    response($content, $status)->header('Content-Type', 'application/xml'));

Test with XML-aware assertions. String comparison is brittle once whitespace or attribute order shifts.

$this->assertXmlStringEqualsXmlString(
    '<user id="1"><name>Ada</name></user>',
    $response->getContent()
);

Queue/third-party integrations. For SOAP-ish or file-drop integrations, generate the XML in a job, store it, and hand the file to the consumer. Keep the converter out of the HTTP layer in that case.

Gotchas and Tips

One-way only. The package converts values to XML. Parsing inbound XML is on you — use SimpleXMLElement or a dedicated parser. Don't assume symmetry.

toArray() filters silently. Because models normalize through toArray(), $hidden / $visible / $appends all affect output. If a field is mysteriously missing from the XML, check the model, not the converter.

N+1 and recursion. Eager load relations before conversion, and guard against circular relations (user -> posts -> user) — toArray() on a circular graph will blow the stack or produce duplicate subtrees. Prefer explicit only() / pluck() shapes for nested data.

Attribute vs. element mixing. The @ prefix convention for attributes and _value for text content (when a node has both attributes and children) comes from spatie/array-to-xml. If you get "Array to string conversion" or empty nodes, you've mixed them incorrectly.

Nulls and empty strings behave differently. Decide whether null means "omit the element" or "emit an empty element" and normalize before conversion — otherwise consumers see inconsistent documents across releases.

Key names are not sanitized. Column names with spaces, colons, or leading digits produce invalid XML. Sanitize keys at the array-shaping step.

Dates depend on casts. A datetime cast renders as Y-m-d H:i:s; format explicitly in the resource if the consumer expects ISO 8601.

Large datasets: the converter builds a string in memory. For hundreds of thousands of rows, stream with XMLWriter instead of pushing a full collection through the converter.

Pin the version and wrap the call. 10 stars and a 0.4 score means low bus factor. Pin in composer.json and keep every Xml::convert() call behind your own adapter class — swapping to spatie/array-to-xml directly later should be a one-file change.

Config cache. If you publish and then edit config/xml.php, run php artisan config:clear in local or you'll debug a phantom root-element name.

Extension points: a facade accessor swap in a service provider, a Collection/Response macro, or a custom converter bound to the container — all viable. Prefer the wrapper-class approach over a macro; macros are invisible to static analysis and IDE completion.

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