The robust alternative to fragile PHP Factur-X libraries. Handle strict E-Rechnung and Factur-X compliance in Symfony, Laravel, or WordPress without complex PDF or XML dependencies.
guzzlehttp/guzzle clientpost('http://localhost:8000/v1/convert', [
'multipart' => [
[
'name' => 'pdf',
'contents' => fopen('input.pdf', 'r'),
'filename' => 'input.pdf'
],
[
'name' => 'metadata',
'contents' => json_encode([
'invoice_number' => 'PHP-999',
'seller' => ['name' => 'PHP Solutions'],
'totals' => ['net_amount' => 120.50]
])
]
]
]);
file_put_contents('factur-x-final.pdf', $response->getBody());
echo "Success: File generated.";
} catch (\Exception $e) {
echo "Error: " . $e->getMessage();
}
?>
Libraries like horstoeko/zugferd generate CII XML but skip the Schematron
validation required for legal compliance. Libraries wrapping tcpdf or
Ghostscript for PDF/A-3 embedding consume large amounts of RAM and break
on edge cases. The Docker API approach isolates all complexity:
| Approach | Schematron | Composer deps | PDF/A-3 |
|---|---|---|---|
| horstoeko/zugferd | None | fpdf, dompdf, setasign | Partial |
| atgp/factur-x | None | smalot/pdfparser | Partial |
| Factur-X Engine API | Yes (Saxon-HE) | guzzlehttp/guzzle only | Yes (VeraPDF) |
Use Symfony's native HttpClientInterface — no Guzzle required. Inject the
service and call the engine from a Symfony command or event listener:
<?php
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class InvoiceService
{
public function __construct(
private HttpClientInterface $http,
private string $engineUrl = 'http://facturx-engine:8000'
) {}
public function generateFacturX(string $pdfPath, array $metadata): string
{
$response = $this->http->request('POST', $this->engineUrl . '/v1/convert', [
'body' => [
'pdf' => fopen($pdfPath, 'r'),
'metadata' => json_encode($metadata),
],
]);
if ($response->getStatusCode() !== 200) {
throw new \RuntimeException('Engine error: ' . $response->getContent(false));
}
return $response->getContent(); // binary PDF string
}
}
Dispatch invoice generation as a Laravel queued job after payment confirmation. Store the resulting PDF in S3 via Laravel's filesystem abstraction:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class GenerateFacturXInvoice implements ShouldQueue
{
use Queueable;
public function __construct(
private int $orderId,
private string $pdfPath,
private array $metadata
) {}
public function handle(): void
{
$response = Http::attach('pdf', file_get_contents($this->pdfPath), 'invoice.pdf')
->post(config('services.facturx.url') . '/v1/convert', [
'metadata' => json_encode($this->metadata),
]);
$response->throw(); // throws on 4xx/5xx
Storage::disk('s3')->put(
"invoices/{$this->orderId}.pdf",
$response->body()
);
}
}
When receiving e-invoices from suppliers via EDI or email attachment, validate compliance before injecting into your accounting system. Non-compliant invoices (broken VAT math, missing IBAN, wrong profile) are rejected with a structured error report:
<?php
use GuzzleHttp\Client;
function validateInvoice(string $filePath): array
{
$client = new Client();
$response = $client->post('http://localhost:8000/v1/validate', [
'multipart' => [[
'name' => 'file',
'contents' => fopen($filePath, 'r'),
'filename' => basename($filePath),
]],
]);
$result = json_decode($response->getBody(), true);
if (!$result['is_valid']) {
foreach ($result['errors'] as $error) {
error_log("[{$error['code']}] {$error['message']}");
}
throw new \RuntimeException('Invoice failed EN 16931 validation');
}
return $result;
}
curl instead of Guzzle?Yes. PHP's native curl_setopt with
CURLOPT_POSTFIELDS set to a multipart array works perfectly.
The Guzzle example is shown for readability but any HTTP client that supports
multipart/form-data works — including Symfony HttpClient and Laravel's Http facade.
Set the appropriate headers and echo the binary content:
header('Content-Type: application/pdf'),
header('Content-Disposition: attachment; filename="invoice.pdf"'),
then echo $response->getBody(). In Laravel use
return response($response->body(), 200, ['Content-Type' => 'application/pdf']).
The Factur-X Engine Docker container runs separately from your PHP environment — it can be hosted on any VPS, cloud VM, or local server. Your PHP app on shared hosting simply makes HTTP requests to it. The engine requires at minimum 0.5 vCPU and 512 MB RAM on the host where it runs.