E-invoicing
A business document, from code to signed XML
An electronic invoice, a quotation or a despatch advice is an XML with a strict schema, and often a signature. Derafu UBL gives you every UBL document as typed classes, Derafu XML works with the XML, and Derafu Certificate and Derafu Signature sign it.
The packages are generic on purpose: they do not carry the rules of a country or of a network. Those rules belong in a package built on top, and the one for Peppol, Derafu Peppol, is planned.
How it works
Four steps, from an object to a signed document.
Build the document
Write it, and check it
Work with the XML
Sign it
Four packages, one flow
Each one is installed on its own.
From code to a signed document
One short example for each step.
// Every UBL type is a class. This is a Quotation of UBL 2.1 (the V21 namespace),
// built with typed setters.
$quotation = (new Quotation())
->setId((new Identifier())->setValue('Q-001'))
->setIssueDate(new DateTimeImmutable('2026-01-15'))
->setSellerSupplierParty(
(new SupplierParty())->setParty(
(new Party())->setPartyNames([
(new PartyName())->setName((new Text())->setValue('Acme Corp')),
])
)
)
->setQuotedMonetaryTotal(
(new MonetaryTotal())->setPayableAmount(
(new Amount())->setValue('100')->setCurrencyID('EUR')
)
);
// ...and the lines of the quotation, with setQuotationLines([...]).
// Or start from a plain array, in the shape that the JSON and YAML formats carry:
$quotation = Quotation::fromArray([
'ID' => ['_' => 'Q-001'],
'IssueDate' => ['_' => '2026-01-15'],
// ...
]);
// The same document, in three formats.
$serializer = new Serializer();
$xml = $serializer->serialize($quotation, 'xml'); // the official UBL XML
$json = $serializer->serialize($quotation, 'json'); // the "UBL JSON Alternative Representation" of OASIS
$yaml = $serializer->serialize($quotation, 'yaml'); // the same data, in YAML
// And back. The XML does not say which UBL version it is, so you choose it
// (the default is 2.1):
$deserializer = new Deserializer();
$quotation = $deserializer->deserialize($xml, 'xml', ['version' => '2.3']);
// Validate it while it is read, against the official schemas:
$quotation = $deserializer->deserialize($json, 'json', ['validate' => true]);
// Or validate an XML on its own. If it is not valid, it throws a ValidationException
// with the errors that libxml reported.
try {
(new XmlSchemaValidator())->validate($xml);
} catch (ValidationException $e) {
foreach ($e->getErrors() as $error) {
printf("Line %d: %s\n", $error->line, trim($error->message));
}
}
// XML to a PHP array and back. Attributes go in "@attributes" and the text in "@value".
$document = new XmlDocument();
$document->loadXml($xml);
$array = (new XmlDecoder())->decode($document);
$xml = (new XmlEncoder())->encode($array)->saveXml();
// Or read the document without converting it, with XPath and with types.
// The text is '0021': the typed reads say what you want it to be.
$reader = $document->getReader();
$reader->requireString('//seller/name'); // 'Seller name'
$reader->getAsInt('//info/number'); // 21
$reader->getString('//info/number'); // '0021'
// The package also validates a document with an XSD, with the errors of libxml
// explained and translated, and gives the canonical XML (C14N).
$loader = new CertificateLoader();
$certificate = $loader->loadFromFile('/path/to/certificate.p12', 'password');
$certificate->getId(); // The identity, for example "12345678-9".
$certificate->getName();
$certificate->getEmail();
$certificate->isActive(); // It is not expired.
$certificate->getExpirationDays();
// Check that it has an ID, in the format that you expect (a regular expression).
// If it does not, it throws a CertificateException that says why.
(new CertificateValidator('/^\d+-[\dK]$/'))->validate($certificate);
// For tests, you can make a self-signed certificate instead of asking for one.
$selfSigned = new SelfSignedCertificate();
$certificate = $loader->loadFromArray($selfSigned->toArray());
// Sign the whole XML, or only the element that has a given ID.
$signedXml = $signatureService->signXml($xml, $certificate);
$signedXml = $signatureService->signXml($xml, $certificate, 'elementId');
// Validate a signed XML. You get one result for each signature in the document, and
// you check each one. It throws a SignatureException only if the XML is malformed
// or if it has no signature at all.
foreach ($signatureService->validateXml($signedXml) as $signature) {
if (!$signature->isValid()) {
echo $signature->getError()?->getMessage();
}
}
// The XML signature is an enveloped signature, with canonical XML 1.0, RSA-SHA1
// and a SHA1 digest. The certificate goes inside the signature.
// Plain data can be signed too, with any algorithm that OpenSSL offers.
$signature = $signatureService->sign($data, $privateKey, OPENSSL_ALGO_SHA256);
$valid = $signatureService->validate($data, $signature, $publicKey, OPENSSL_ALGO_SHA256);
Frequently asked questions
What it does, and what it leaves to you.
xterr/php-ubl, and the classes are generated from the official XSD with the generator of that project. Derafu UBL includes fixes for some known defects of it, as a script that patches it in vendor/. Composer does not run the scripts of a dependency, so your project has to add that script to its own composer.json; otherwise the original behavior stays, without a message. The documentation of UBL explains how.
openssl and mbstring extensions for certificates and signatures. YAML and the validation of JSON need symfony/yaml and opis/json-schema, which you add only if you use them. The Derafu packages are published as dev-main for now.
Where it fits
Other good tools exist. Choose by how you work.
| Approach | Examples | Choose it when | Derafu |
|---|---|---|---|
| An invoice for a profile | num-num/ubl-invoice, josemmo/einvoicing | You need a ready invoice for Peppol or EN 16931, with its rules. | A generic model of every UBL document, without profile rules. You build the rules on top. |
| A library for one tax authority | greenter (Peru), facturae-php (Spain) | You issue in that country and want its rules and its signature. | Generic pieces. The profile of a country is yours to write. |
| XML security | robrichards/xmlseclibs | You need more algorithms in the XML signature, or XML encryption. | A smaller XML signature, typed and with translatable errors, made to work with Derafu XML. |
| Public key infrastructure | sop/x509, spomky-labs/pki-framework | You need a full PKI: ASN.1 and the validation of a certification path. | A layer over OpenSSL for the certificate of a person or a company: PKCS#12, its identity, its expiration and self-signed ones for tests. |
Install it
Install only the packages that you use.
# The Derafu packages are published as dev-main for now, so your composer.json needs:
# "minimum-stability": "dev", "prefer-stable": true
composer require derafu/ubl
composer require derafu/xml
composer require derafu/certificate derafu/signature
# Optional for UBL: the YAML format and the validation of JSON and YAML.
composer require symfony/yaml opis/json-schema
# Derafu UBL also asks for a script in the composer.json of your project:
# see its documentation.
Next: the UBL, XML, Certificate and Signature documentation.
Ready to build your first document?
Start with the UBL documentation: the document types, the versions and the formats.