Skip to content

carlosJCVC/xml-flow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XmlFlow

A fluent PHP library for building well-formed XML documents, validating them against XSD schemas, and signing them with RSA-SHA256 digital signatures — purpose-built for electronic invoicing systems and any integration that demands strict schema compliance.


Features

  • Fluent builder — chainable API from root declaration to final XML string
  • Native xsi:nil support — emit <field xsi:nil="true"/> for absent but schema-required fields
  • Multi-driver architecture — swap rendering backends without touching application code
  • XSD validation — validate against any schema with a structured error report
  • XMLDSig signing — enveloped RSA-SHA256 signatures via a dedicated, decoupled XmlSigner
  • Sequential array collections — arrays of items automatically repeat the parent tag
  • Decimal formattingasDecimal(n) guarantees precise numeric serialization (150.0'150.00')

Requirements

  • PHP 8.2+
  • Extensions: ext-dom, ext-xmlwriter, ext-openssl

Installation

composer require carlos-veizaga/xml-flow

Basic usage

use CarlosVeizaga\XmlFlow\XmlFlow;
use CarlosVeizaga\XmlFlow\Node;

$xml = XmlFlow::create('invoice')
    ->namespaces(['xsi' => 'http://www.w3.org/2001/XMLSchema-instance'])
    ->body([
        'documentId'   => 1234567,
        'businessName' => 'ACME Inc.',
        'line'         => [
            ['description' => 'Laptop', 'quantity' => 1, 'unitPrice' => 4500],
            ['description' => 'Mouse',  'quantity' => 2, 'unitPrice' => 120],
        ],
    ])
    ->toXml();

Output:

<?xml version="1.0" encoding="UTF-8"?>
<invoice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <documentId>1234567</documentId>
  <businessName>ACME Inc.</businessName>
  <line>
    <description>Laptop</description>
    <quantity>1</quantity>
    <unitPrice>4500</unitPrice>
  </line>
  <line>
    <description>Mouse</description>
    <quantity>2</quantity>
    <unitPrice>120</unitPrice>
  </line>
</invoice>

Power features

Null fields — Node::nil()

Some XSD schemas require absent fields to appear explicitly with xsi:nil="true" rather than being omitted. XmlFlow handles this natively.

use CarlosVeizaga\XmlFlow\Node;

$xml = XmlFlow::create('invoice')
    ->namespaces(['xsi' => 'http://www.w3.org/2001/XMLSchema-instance'])
    ->body([
        'documentId' => 1234567,
        'discount'   => Node::nil('discount'),   // <discount xsi:nil="true"/>
        'reference'  => Node::nil('reference'),  // <reference xsi:nil="true"/>
    ])
    ->toXml();

Note: The xsi namespace must be declared via ->namespaces(). XmlFlow does not inject it automatically — neither driver does.

Elements with custom attributes and text content use Node::create():

Node::create('unit')
    ->value('KGM')
    ->attributes(['code' => '58']);
// <unit code="58">KGM</unit>

Decimal precision — Node::asDecimal()

PHP silently drops decimal places when casting numbers to strings — (string) 150.0 produces '150', not '150.00'. If your XSD field is xs:decimal, this causes validation failures. asDecimal() fixes it:

Node::create('unitPrice')->value(150.0)->asDecimal(2)   // <unitPrice>150.00</unitPrice>
Node::create('taxRate')->value(0.13)->asDecimal(4)      // <taxRate>0.1300</taxRate>
Node::create('total')->value(1234.5)->asDecimal(2)      // <total>1234.50</total>

value() accepts string, int, or float. The precision defaults to 2 if omitted.


XSD validation

use CarlosVeizaga\XmlFlow\Exceptions\XsdValidationException;

try {
    $xml = XmlFlow::create('invoice')
        ->namespaces([...])
        ->body([...])
        ->validate('/schemas/invoice.xsd')
        ->toXml();
} catch (XsdValidationException $e) {
    foreach ($e->getErrors() as $error) {
        echo "[line {$error->line}] {$error->message}";
    }
}

validate() returns $this, so it composes naturally in the chain. It throws XsdValidationException with the raw LibXMLError[] list on failure.


Digital signing — XMLDSig RSA-SHA256

use CarlosVeizaga\XmlFlow\Security\XmlSigner;
use CarlosVeizaga\XmlFlow\Exceptions\SignatureException;

$xml = XmlFlow::create('invoice')
    ->namespaces([...])
    ->body([...])
    ->toXml();

try {
    $signedXml = XmlSigner::create()->sign($xml, '/certs/company.p12', 'password');
} catch (SignatureException $e) {
    // certificate not found, wrong password, or corrupt P12
}

The signature is inserted as the last child of the root element, as required by enveloped XMLDSig. The <ds:Signature> node declares xmlns:ds explicitly to satisfy strict schema validators.

Note: XmlSigner accepts any XML string — it is not coupled to XmlFlow. You can sign documents generated by other means.


High-volume generation — useStreamMode()

For batch jobs generating thousands of documents, switch to the StreamDriver. It uses PHP's XMLWriter extension and keeps memory usage O(1) regardless of document size.

$xml = XmlFlow::create('invoice')
    ->useStreamMode()
    ->namespaces([...])
    ->body([...])
    ->toXml();

When to use each driver:

Scenario Driver
XMLDSig digital signing DomDriver (default) — signing requires DOM node access
XSD validation via ->validate() DomDriver (default)
Batch / high-volume generation (no signing) StreamDriver via ->useStreamMode()

Switching drivers is transparent — both implement XmlDriverInterface and produce semantically identical output.


Architecture

XmlFlow follows SOLID principles throughout:

  • Strategy patternXmlDriverInterface decouples the rendering backend from the builder. Implement it to add a custom driver without modifying any existing class.
  • Value ObjectNode is immutable by convention; it carries no identity, only data.
  • Decoupled signingXmlSigner depends on robrichards/xmlseclibs and is entirely separate from the builder. Applications that do not need signing do not pay any cost for it.
XmlFlow (builder)
  └── XmlDriverInterface
        ├── DomDriver    — DOMDocument, supports XMLDSig
        └── StreamDriver — XMLWriter, O(1) memory

XmlSigner (independent)
  └── SignerInterface
        └── XmlSigner — RSA-SHA256 enveloped signature

Running the tests

composer install
./vendor/bin/phpunit

The suite covers the builder, Node semantics, driver parity (DomDriver vs StreamDriver), and cryptographic signature verification.


License

MIT © Carlos Veizaga

About

Fluent PHP library for building XML documents, XSD validation, and RSA-SHA256 digital signing.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages