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.
- 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')
- PHP 8.2+
- Extensions:
ext-dom,ext-xmlwriter,ext-openssl
composer require carlos-veizaga/xml-flowuse 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>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>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.
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.
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.
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.
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
composer install
./vendor/bin/phpunitThe suite covers the builder, Node semantics, driver parity (DomDriver vs StreamDriver), and cryptographic signature verification.
MIT © Carlos Veizaga