Avec l'obligation de recevoir des factures électroniques pour toutes les entreprises dès le 1er septembre 2026 (impots.gouv.fr), savoir lire un Factur-X côté réception devient aussi important que savoir le générer. Un développeur doit pouvoir ouvrir un PDF/A-3, en extraire le XML embarqué et récupérer les données structurées pour automatiser le rapprochement comptable.

Ce guide technique, neutre et sans promotion d'API, montre comment lire un Factur-X en PHP : extraction du fichier factur-x.xml, chargement avec horstoeko/zugferd, lecture de l'en-tête, des totaux et des lignes, puis normalisation vers un objet métier. Pour la génération, voyez d'abord notre tuto Factur-X en PHP / Laravel.

L'essentiel en 5 points

Que contient réellement un fichier Factur-X reçu ?

Un Factur-X reçu est un PDF/A-3. À l'intérieur, un fichier embarqué (embedded file selon la norme PDF) contient le XML structuré au format Cross Industry Invoice (CII) de l'UN/CEFACT. Ce fichier porte une relation d'association AFRelationship et un nom normalisé. Selon l'émetteur, vous rencontrerez trois noms.

Nom du fichier embarquéFormatOrigine
factur-x.xmlCIIFactur-X (France)
zugferd-invoice.xmlCIIZUGFeRD (Allemagne, anciennes versions)
xrechnung.xmlCII ou UBLSecteur public allemand

Votre code de réception doit donc accepter ces variantes. La bonne nouvelle : horstoeko/zugferd gère ces trois noms et devine automatiquement le profil. Inutile de coder la détection à la main.

Comment extraire le XML d'un PDF Factur-X en PHP ?

La voie la plus simple est ZugferdDocumentPdfReader. Il ouvre le PDF, localise le fichier embarqué, l'extrait et retourne un lecteur prêt à interroger. La méthode readAndGuessFromFile() devine le profil dans la foulée.

PHP — extraction + chargement depuis un PDF
use horstoeko\zugferd\ZugferdDocumentPdfReader;

// Ouvre le PDF/A-3, extrait factur-x.xml, devine le profil
$document = ZugferdDocumentPdfReader::readAndGuessFromFile(
    __DIR__ . "/facture-recue-facturx.pdf"
);

if ($document === null) {
    throw new \RuntimeException("Aucun XML Factur-X trouvé dans ce PDF.");
}

// $document est un ZugferdDocumentReader prêt à interroger

Si vous recevez directement un XML CII brut (sans PDF, fréquent quand l'échange passe par une plateforme), chargez-le avec ZugferdDocumentReader. La lecture des champs est ensuite strictement identique.

PHP — chargement depuis un XML brut
use horstoeko\zugferd\ZugferdDocumentReader;

$document = ZugferdDocumentReader::readAndGuessFromFile(
    __DIR__ . "/facture-recue.xml"
);

Comment lire l'en-tête et les totaux d'un Factur-X ?

Les getters de la librairie utilisent un passage par référence : on déclare les variables, on les passe, elles sont remplies. On récupère ainsi le numéro, le type, la date, la devise, puis les sommes (HT, TVA, TTC, net à payer).

PHP — en-tête et totaux
// En-tête
$document->getDocumentInformation(
    $documentNo, $documentType, $documentDate,
    $invoiceCurrency, $taxCurrency, $documentName,
    $documentLanguage, $effectiveSpecifiedPeriod
);

// Vendeur et acheteur
$document->getDocumentSeller($sellerName, $sellerIds, $sellerDescription);
$document->getDocumentBuyer($buyerName, $buyerIds, $buyerDescription);

// Totaux : HT, TVA, TTC, net à payer...
$document->getDocumentSummation(
    $grandTotalAmount,   // TTC
    $duePayableAmount,   // net à payer
    $lineTotalAmount,    // HT
    $chargeTotalAmount,
    $allowanceTotalAmount,
    $taxBasisTotalAmount,
    $taxTotalAmount,     // total TVA
    $roundingAmount,
    $totalPrepaidAmount
);

echo "Facture {$documentNo} — TTC {$grandTotalAmount} {$invoiceCurrency}";

Comment itérer les lignes d'une facture Factur-X ?

Les lignes ne sont présentes que dans les profils qui les portent (BASIC, EN 16931, EXTENDED). On se positionne sur la première ligne, on lit, puis on avance tant qu'il en reste.

PHP — itération des lignes
$lignes = [];

if ($document->firstDocumentPosition()) {
    do {
        $document->getDocumentPositionProductDetails(
            $name, $description, $sellerAssignedId,
            $buyerAssignedId, $globalIdType, $globalId
        );
        $document->getDocumentPositionQuantity($quantity, $unitCode);
        $document->getDocumentPositionLineSummation($lineTotal, $totalAllowance, $totalCharge);

        $lignes[] = [
            "libelle"  => $name,
            "quantite" => $quantity,
            "total_ht" => $lineTotal,
        ];
    } while ($document->nextDocumentPosition());
}

Attention aux profils légers : sur un profil MINIMUM ou BASIC WL, firstDocumentPosition() renvoie false car le XML ne contient aucune ligne. Votre code doit gérer ce cas sans planter et se rabattre sur les totaux d'en-tête.

Comment normaliser vers un objet métier ?

En production, on ne disperse pas les getters dans toute l'application. On encapsule la lecture dans un service qui retourne un DTO (Data Transfer Object) propre, consommé par le reste du code. Cela isole la dépendance à la librairie et facilite les tests.

PHP — service de lecture normalisé
final class FacturXReader
{
    public function read(string $pdfPath): array
    {
        $doc = ZugferdDocumentPdfReader::readAndGuessFromFile($pdfPath);
        if ($doc === null) {
            throw new \RuntimeException("Factur-X invalide.");
        }

        $doc->getDocumentInformation($no, $type, $date, $cur, ...);
        $doc->getDocumentSummation($ttc, $due, $ht, $c, $a, $base, $tva, ...);

        return [
            "numero"   => $no,
            "date"     => $date,
            "devise"   => $cur,
            "total_ht"=> $ht,
            "total_tva" => $tva,
            "total_ttc" => $ttc,
        ];
    }
}

Et sans la librairie : extraction manuelle du XML

Si vous voulez comprendre le mécanisme bas niveau ou éviter la dépendance, vous pouvez extraire le fichier embarqué avec un parseur PDF (smalot/pdfparser ou setasign/fpdi) puis lire le XML avec DOMDocument et XPath en déclarant les namespaces CII. C'est instructif, mais vous réimplémentez la gestion des profils et des chemins — rarement rentable en production.

Quelles erreurs éviter en lecture Factur-X ?

La réception automatisée est sensible : une facture mal lue, c'est un rapprochement comptable faux. Quatre pièges reviennent systématiquement.

ErreurConséquenceParade
Supposer un profil EN 16931Crash sur facture MINIMUMLire le profil détecté
Ignorer la deviseMontants en mauvaise unitéToujours lire $invoiceCurrency
Confiance au PDF visuelDonnées non automatisablesTraiter le XML, contrôler l'écart
Pas de gestion des avoirsMontants négatifs ignorésTester le type 381 (avoir)

Comment brancher la lecture sur un logiciel existant ?

Lire le fichier n'est qu'un début. La valeur réside dans le rapprochement : associer la facture reçue à un bon de commande, déclencher un workflow de validation, alimenter la compta. C'est un travail d'intégration sur le logiciel métier. Sur ce volet, lisez notre guide connecter un logiciel métier à une PDP sans devenir PDP, et côté éditeurs, solution compatible vs plateforme agréée.

FAQ : lire et extraire un Factur-X en PHP

Comment extraire le XML d'un PDF Factur-X en PHP ?

Le XML est un fichier embarqué du PDF/A-3 nommé factur-x.xml. Avec horstoeko/zugferd, ZugferdDocumentPdfReader l'extrait automatiquement et devine le profil. On peut aussi l'extraire manuellement via les EmbeddedFiles du PDF, mais la librairie gère tous les noms (factur-x.xml, zugferd-invoice.xml, xrechnung.xml).

Quelle différence entre lire un PDF Factur-X et un XML CII brut ?

Un PDF Factur-X contient le XML embarqué : il faut d'abord l'extraire. Un XML CII brut se charge directement. horstoeko/zugferd fournit ZugferdDocumentPdfReader pour le PDF et ZugferdDocumentReader pour le XML seul. La lecture des champs est ensuite identique.

Comment récupérer les lignes d'une facture Factur-X ?

Après chargement, on appelle firstDocumentPosition() puis on boucle avec nextDocumentPosition(). getDocumentPositionProductDetails() donne le libellé, getDocumentPositionQuantity() la quantité, getDocumentPositionLineSummation() le montant. Les profils MINIMUM et BASIC WL ne contiennent pas de lignes.

Peut-on lire un Factur-X sans horstoeko/zugferd ?

Oui. On extrait le fichier embarqué avec smalot/pdfparser ou FPDI puis on parse le XML avec DOMDocument ou SimpleXML selon les namespaces CII. Mais on réinvente la gestion des profils et des chemins XPath. La librairie reste plus fiable et plus rapide à mettre en oeuvre.

Faut-il vérifier la cohérence entre le PDF visuel et le XML ?

Oui. L'administration impose la cohérence entre données visibles et données structurées. Côté réception, on se fie au XML pour l'automatisation, mais un contrôle d'écart (TTC, numéro, date) entre le rendu et le XML détecte les fichiers mal formés.

Comment intégrer la lecture Factur-X dans un logiciel existant ?

On encapsule la lecture dans un service qui retourne un DTO normalisé consommé par le reste de l'application. C'est le type de mission d'intégration que réalise ComAll Agency : brancher la réception et le rapprochement des e-factures sur un logiciel métier sur-mesure. Devis gratuit sous 24h.

Conclusion : maîtriser la réception avant l'échéance

Lire un Factur-X en PHP se résume à trois gestes : extraire le XML embarqué, charger le document, interroger les getters. La librairie horstoeko/zugferd absorbe la complexité des profils et des variantes de nommage. Le reste — gestion des profils légers, des avoirs, normalisation métier — relève d'un code défensif et bien testé.

Si vous devez fiabiliser la réception d'e-factures sur un logiciel existant avant le 1er septembre 2026, nous intervenons en mission d'intégration. Demandez un devis gratuit sous 24h.

Brancher la réception d'e-factures sur votre logiciel ?

Mission d'intégration : lecture, rapprochement et raccordement PDP sur-mesure. Devis sous 24h.

Demander un devis gratuit →