Weave Code
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 Wrangler Laravel Package

saloonphp/xml-wrangler

XML Wrangler is a Saloon plugin that makes working with XML APIs painless. It adds XML request bodies, automatic XML responses parsing, and convenient helpers for converting between arrays and XML so you can focus on building integrations, not boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require saloonphp/xml-wrangler
    

    Ensure your project uses PHP 8.1+ (required for generics and type safety).

  2. First Use Case: Parse an XML string into a typed array/collection:

    use Saloon\XmlWrangler\XmlReader;
    
    $xml = '<root><user id="123"><name>John</name></user></root>';
    $data = XmlReader::fromString($xml)
        ->query('//user')
        ->first()?->getAttribute('id'); // Returns "123"
    
  3. Key Entry Points:

    • XmlReader: Parse XML into traversable nodes (arrays/objects).
    • XmlWriter: Generate XML from arrays/objects.
    • Query: XPath-like querying with Laravel Collection methods (e.g., map, filter).
  4. Where to Look First:

    • Documentation (if available; otherwise, check the Saloon ecosystem docs).
    • tests/ folder: Real-world examples of parsing/writing XML.
    • src/Query.php: Core querying logic (e.g., XPath, attribute access).

Implementation Patterns

1. Parsing XML Responses (Saloon Integration)

Use XmlReader in Saloon connectors to normalize XML responses:

use Saloon\Connector;
use Saloon\XmlWrangler\XmlReader;

class PaymentGatewayConnector extends Connector
{
    public function resolve(): array
    {
        return [
            'base_uri' => 'https://api.gateway.com',
            'response_object' => XmlResponse::class,
        ];
    }
}

class XmlResponse extends Response
{
    public function parse(): array
    {
        return XmlReader::fromString($this->body)
            ->query('//Transaction')
            ->map(fn ($node) => [
                'id' => $node->getAttribute('id'),
                'status' => $node->getContent(),
            ])
            ->toArray();
    }
}

2. Generating XML Requests

Convert arrays/DTOs to XML for SOAP/REST+XML APIs:

use Saloon\XmlWrangler\XmlWriter;

$xml = XmlWriter::make()
    ->element('Envelope')
        ->element('Body')
            ->element('PurchaseOrder', [
                'orderId' => 'PO123',
                'date' => date('Y-m-d'),
            ])
            ->element('Items')
                ->element('Item', ['sku' => 'SKU456'], 'Quantity: 2')
        ->up()
    ->up()
->toString();

3. Querying with XPath + Collections

Leverage Laravel Collection methods on XML nodes:

$nodes = XmlReader::fromString($xml)
    ->query('//Product')
    ->filter(fn ($node) => $node->getAttribute('price') > 100)
    ->pluck('name'); // Returns Collection of product names

4. Handling Namespaces

Map namespaces for XPath queries:

$reader = XmlReader::fromString($xml)
    ->mapNamespace('ns', 'http://example.com/ns');

$nodes = $reader->query('//ns:Order'); // Queries namespaced elements

5. Streaming Large XML Files

Process files >10MB without loading into memory:

$reader = XmlReader::fromFile('large_file.xml', stream: true);
foreach ($reader->query('//Record') as $node) {
    // Process each node incrementally
}

6. Type-Safe Parsing (PHP 8.1+)

Use generics to enforce return types:

use Saloon\XmlWrangler\Query;

function getUserData(string $xml): array
{
    return XmlReader::fromString($xml)
        ->query('//User')
        ->first()?->getAttributes(); // Returns ?array
}

7. Testing XML Workflows

Mock XML responses in tests:

use Saloon\Testing\Mock;

$mock = Mock::soap()
    ->withRequestMatching('//PurchaseOrder')
    ->withResponseFromFile(__DIR__.'/fixtures/response.xml');

$response = $this->connector->send(new PurchaseOrderRequest());
$orders = $response->parse(); // Uses XmlReader under the hood

Gotchas and Tips

Pitfalls

  1. Namespace Quirks:

    • Forgetting to map namespaces before querying:
      // ❌ Fails silently if namespace isn't mapped
      $reader->query('//ns:Order');
      
    • Fix: Always map namespaces explicitly:
      $reader->mapNamespace('ns', 'http://example.com/ns')->query('//ns:Order');
      
  2. Streaming Mode Pitfalls:

    • Streams cannot be reused: Once consumed, the stream is exhausted.
      // ❌ Throws "Stream position already read"
      $reader = XmlReader::fromFile('file.xml', stream: true);
      $reader->query('//Node1'); // Consumes stream
      $reader->query('//Node2'); // Fails
      
    • Fix: Rewind the stream or parse in a single pass.
  3. XPath Edge Cases:

    • Relative paths (e.g., .//Node) behave differently than absolute paths (//Node).
    • Tip: Use absolute paths (//) for consistency:
      // Prefer:
      $reader->query('//User/Address');
      // Over:
      $reader->query('.//Address'); // May fail if context is wrong
      
  4. Attribute vs. Content Confusion:

    • getAttribute() vs. getContent() are easy to mix up:
      // ❌ Returns empty string (looks for attribute "name")
      $node->getContent('name');
      // ✅ Correct:
      $node->getAttribute('name'); // For attributes
      $node->getContent();        // For text content
      
  5. PHP 8.1+ Generics:

    • Return types may be strict in newer PHP versions. Ensure your methods align:
      // ❌ May throw type error in PHP 8.2+
      public function parse(): array { ... }
      // ✅ Explicit nullable:
      public function parse(): ?array { ... }
      
  6. Malformed XML:

    • The package does not validate XML schema (XSD). Use veezee/xml for validation:
      // Add to your pipeline:
      $xml = XmlReader::fromString($rawXml);
      $validator = new \Veewee\Xml\Validator();
      $validator->validate($xml->toString());
      

Debugging Tips

  1. Inspect Raw XML: Use toString() to debug:

    $writer = XmlWriter::make()->element('Root');
    dump($writer->toString()); // Visualize generated XML
    
  2. XPath Testing: Test queries in isolation:

    $reader = XmlReader::fromString($xml);
    dump($reader->query('//*')->toArray()); // List all nodes
    
  3. Stream Position: Reset streams for debugging:

    $stream = fopen('file.xml', 'r');
    rewind($stream);
    $reader = XmlReader::fromStream($stream);
    
  4. Performance:

    • Avoid chaining queries unnecessarily. Cache results:
      $users = $reader->query('//User')->cache();
      $activeUsers = $users->filter(...);
      

Extension Points

  1. Custom Node Classes: Extend XmlNode to add domain-specific methods:

    class UserNode extends XmlNode
    {
        public function getFullName(): string
        {
            return $this->query('./FirstName')->getContent() . ' ' .
                   $this->query('./LastName')->getContent();
        }
    }
    
  2. Query Macros: Add reusable query logic:

    Query::macro('byStatus', function (string $status) {
        return $this->filter(fn ($node) =>
            $node->getAttribute('status') === $status
        );
    });
    // Usage:
    $reader->query('//Order')->byStatus('shipped');
    
  3. Writer Customization: Override default XML generation:

    $writer = XmlWriter::make()
        ->setEncoding('UTF-8')
        ->setStandalone(true);
    
  4. Integration with Saloon: Create a base XmlResponse class for all connectors:

    abstract class XmlResponse extends Response
    {
        public function parse(): array
        {
            return XmlReader::fromString($this->body)
                ->query($this->
    
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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata