carlos-veizaga / xml-flow
A fluent, agnostic XML builder with native support for xsi:nil and complex namespaces.
Requires
- php: >=8.2
- ext-dom: *
- ext-openssl: *
- ext-xmlwriter: *
- robrichards/xmlseclibs: ^3.1.5
Requires (Dev)
- phpunit/phpunit: ^11.5
README
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:nilsupport — 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 formatting —
asDecimal(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
xsinamespace 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:
XmlSigneraccepts any XML string — it is not coupled toXmlFlow. 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 accessXSD validation via ->validate()DomDriver(default)Batch / high-volume generation (no signing) StreamDrivervia->useStreamMode()
Switching drivers is transparent — both implement XmlDriverInterface and produce semantically identical output.
Architecture
XmlFlow follows SOLID principles throughout:
- Strategy pattern —
XmlDriverInterfacedecouples the rendering backend from the builder. Implement it to add a custom driver without modifying any existing class. - Value Object —
Nodeis immutable by convention; it carries no identity, only data. - Decoupled signing —
XmlSignerdepends onrobrichards/xmlseclibsand 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