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

Request Xml Laravel Package

mtownsend/request-xml

Adds simple XML response parsing to Laravel's HTTP client. Call xml() on responses to convert XML into arrays, making XML APIs as easy to work with as JSON. Lightweight and Laravel-friendly for legacy or SOAP-style endpoints.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and let Laravel auto-discover the service provider:

composer require mtownsend/request-xml

Version note (2.7.0). The latest release is a compatibility-only bump that adds Laravel 13.x support — no new public API, no deprecations, no changes to the xml() macro signature. If you're on Laravel 11/12 the constraint is unchanged in practice, but check what Composer actually resolved before you write code against it:

composer show mtownsend/request-xml   # confirm 2.7.0 is what you locked

Pin the exact minor in composer.json ("mtownsend/request-xml": "^2.7") rather than floating, because the only thing this project ships between minors is framework-compatibility work — you want those bumps to be deliberate, not silent.

There is no mandatory configuration. If a config file is published (php artisan vendor:publish --provider="Mtownsend\RequestXml\Providers\RequestXmlServiceProvider"), treat it as the place where return-type/casting behavior is decided — pin it early so every controller gets the same shape.

Where to look first

  1. The README’s usage block — pay attention to the exact signature of the xml() macro (path argument, options array, return type) for the version you lock.
  2. src/Providers/RequestXmlServiceProvider.php — confirm the macro is registered as Request::xml and see whether it boots per-request.
  3. The class doing the actual SimpleXML parsing — this tells you whether attributes, CDATA, and namespaces survive the conversion.

First use case: an XML webhook

The natural entry point is a webhook endpoint that receives a raw XML body (payment gateways, supplier feeds, legacy ERP notifications):

// routes/api.php — CSRF doesn't apply to api.php, but see gotchas for web.php
Route::post('/webhooks/supplier', SupplierWebhookController::class);

// app/Http/Controllers/SupplierWebhookController.php
public function __invoke(Request $request)
{
    $payload = $request->xml();          // Illuminate\Support\Collection
    $items   = $request->xml('order.items.item'); // dot-notated subtree

    return response()->noContent();
}

Probe it before wiring anything in. Hit a throwaway route with raw body XML and dd($request->xml()->toArray()) — you need to see how your real payload shape (attributes, namespaces, repeated children) comes out the other side. curl -X POST -H "Content-Type: text/xml" --data-binary @fixture.xml http://localhost:8000/probe is more reliable than Postman’s form modes, which silently send application/x-www-form-urlencoded.

Implementation Patterns

Parse at the edge, pass arrays inward. Keep $request->xml() calls in the controller or a FormRequest and convert to a plain array or DTO immediately. If the package is ever abandoned or you swap parsers, one file changes instead of fifteen.

final class SupplierOrderData
{
    public static function fromCollection(Collection $xml): self
    {
        return new self(
            reference: (string) $xml->get('reference'),
            total:     (float)  $xml->get('totals.grand'),
        );
    }
}

FormRequest wrapper. The macro lives on the underlying request, so $this->xml() works inside a FormRequest — but name your own accessor to avoid confusion and to normalize the shape before validation:

class SupplierOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'reference' => ['required', 'string'],
            'items'     => ['required', 'array', 'min:1'],
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge($this->xmlPayload()->toArray());
    }

    public function xmlPayload(): Collection
    {
        return $this->xml();
    }
}

Subtree extraction for feed-like payloads. When the XML is a list, pull the repeating node path once and map it into rows rather than walking the whole tree:

$rows = $request->xml('feed.products.product')
    ->map(fn ($p) => [
        'sku'   => (string) $p['sku'],   // attribute — verify this works in your version
        'name'  => (string) $p->name,
        'price' => (float)  $p->price,
    ]);

Middleware for content-type-agnostic controllers. If a controller should accept both JSON and XML, normalize early so downstream code never branches:

class NormalizeXmlBody
{
    public function handle(Request $request, Closure $next)
    {
        if (str_contains($request->header('Content-Type', ''), 'xml') && $request->getContent()) {
            $request->merge($request->xml()->toArray());
        }

        return $next($request);
    }
}

Register it after StartSession and before SubstituteBindings so validation and route model binding see the merged data.

Integration service for outbound + inbound. The package only solves the inbound half. Keep outbound XML in a dedicated client so both directions live together:

class SupplierClient
{
    public function push(string $xml): Response
    {
        return Http::withHeaders(['Content-Type' => 'text/xml'])
            ->withBody($xml, 'text/xml')
            ->post(config('services.supplier.url'));
    }

    public function parse(Request $request): Collection
    {
        return $request->xml('Envelope.Body.response');
    }
}

Testing. Laravel’s $this->post() sends form-encoded data, not a raw body. Use the low-level call:

$response = $this->call(
    'POST', '/webhooks/supplier', [], [], [],
    ['CONTENT_TYPE' => 'text/xml', 'HTTP_ACCEPT' => 'application/json'],
    $xmlFixture
);

Store a couple of real XML fixtures in tests/Fixtures — including a “happy path with two items” and a “single item” case, since the single/multiple distinction is where shape bugs hide. When you upgrade Laravel majors, re-run this suite first: a framework-compat release like 2.7.0 touches service-provider boot and macro registration, which is exactly the surface your fixtures pin down.

Own the API surface. Wrap the macro in a small trait or base controller method (xmlPayload()) so your codebase depends on your method, not on mtownsend/request-xml. Given the package’s size and adoption, a 20-line simplexml_load_string fallback is a realistic exit strategy.

Gotchas and Tips

  • Framework compatibility is the whole release cadence. 2.7.0 = Laravel 13.x support, nothing else. That means the package’s roadmap is driven by Laravel majors, so plan your own upgrades around it: a Laravel major without a corresponding package minor is your cue to test the override/fallback path before you need it.
  • It parses the raw request body only. XML stuffed into a form field (xml=<payload>) will not be seen; use $request->input('xml') and parse that yourself. Conversely, any XML-shaped body will parse regardless of Content-Type — add an explicit guard if your endpoint is strict about media types.
  • XXE and payload size. The package leans on SimpleXML, so safety depends on the libxml version underneath. On modern PHP, external entity loading is off by default, but do not assume it on older runtimes. Cap body size (post_max_size, a middleware check on strlen($request->getContent())) and never echo raw parse errors or the raw body back to the caller.
  • Malformed vs empty is hard to distinguish. A broken document often comes back as an empty collection rather than an exception. Check $request->getContent() is non-empty and well-formed first, or accept that you’ll need to parse with an explicit error path in critical flows.
  • Attributes, CDATA, and namespaces are the usual casualties. SimpleXML→array conversion tends to flatten attributes and produce awkward keys for namespaced nodes (soap:Body). Dot notation also doesn’t survive dots or hyphens in tag names well. If your payload is SOAP or attribute-heavy, test the exact node before committing to the package.
  • Single-child inconsistency. A path returning one node and a path returning five can produce different shapes. Normalize at the boundary: collect($value)->wrap() or an explicit is_array() check in your DTO factory.
  • Pin the return type. Whether you get a Collection, an array, or a scalar can depend on the path argument and any published config. Decide once (collection + toArray() at the DTO boundary) and don’t let callers assume otherwise.
  • Macro collisions. Check Request::hasMacro('xml') if you also use packages that extend Request. Macros registered later win, so boot order in config/app.php matters — and this is the one thing a compat bump like 2.7.0 can plausibly shift.
  • CSRF on web routes. XML webhooks registered in web.php will 419. Put them in api.php or exclude them explicitly (VerifyCsrfToken::$except on Laravel ≤10, bootstrap/app.php validateCsrfTokens on 11+).
  • Debugging. When a webhook misbehaves, log the raw body ($request->getContent()) with a request ID before parsing — redact PII, and rotate logs. Without the raw body you cannot reproduce the parse.
  • Config cache. If you publish and edit the config, remember php artisan config:cache in deploy scripts or your changes won’t apply in production.
  • Extension points. Everything rides on a Request macro, so you can re-register Request::macro('xml', …) in a later-booting provider to change behavior without forking. Given the package’s maturity level, that override — or a straight replacement with a well-tested XML library — is the sane long-term hedge for anything revenue-critical.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route