Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/eclipxe13/CfdiUtils/llms.txt

Use this file to discover all available pages before exploring further.

The CfdiUtils\Cfdi class is the primary entry point for reading Mexican electronic invoice (CFDI) documents in PHP. It parses an XML document and converts it into an internal Nodes structure — a structured, traversable tree of NodeInterface objects — making it straightforward to extract data from CFDI 3.2, 3.3, and 4.0 files. No validation is performed at read time; the class only checks that the document is a recognisable CFDI.

The Cfdi Class

The CfdiUtils\Cfdi class supports CFDI versions 3.2, 3.3, and 4.0. When you construct an instance it converts the raw XML into the internal Nodes data structure. All navigation, attribute lookup, and child traversal happens through the resulting NodeInterface object returned by getNode().
The Cfdi class only reads documents — it does not validate them. Use the dedicated validation classes for CFDI validation.

Create a Cfdi Object

The recommended way to create a Cfdi instance is through the static factory method Cfdi::newFromString(). Pass the raw XML string, and the method returns a fully initialised Cfdi object.
<?php
$xmlContents = file_get_contents('/cfdi/recibidos/FEI-456823.xml');
$cfdi = \CfdiUtils\Cfdi::newFromString($xmlContents);
The object exposes four primary getters:
MethodReturn typeDescription
getVersion()stringDetected version string: "3.2", "3.3", or "4.0"
getDocument()DOMDocumentA clone of the internal DOMDocument
getSource()stringThe original XML source string
getNode()NodeInterfaceRoot node for the cfdi:Comprobante element
<?php
$xmlContents = '<cfdi:Comprobante Version="3.3">...</cfdi:Comprobante>';
$cfdi = \CfdiUtils\Cfdi::newFromString($xmlContents);

$cfdi->getVersion();   // (string) "3.3"
$cfdi->getDocument();  // clone of the DOMDocument object
$cfdi->getSource();    // (string) original XML content
$comprobante = $cfdi->getNode(); // NodeInterface for cfdi:Comprobante
getDocument() returns a clone, so changes to the returned DOMDocument do not affect the internal state of the Cfdi object. Similarly, changes to the NodeInterface tree do not propagate back to the DOMDocument.

Constructor Validation Rules

When a Cfdi object is constructed (either directly or via newFromString()), the following rules are applied to the DOMDocument:
  1. Namespace — The document must implement the CFDI namespace http://www.sat.gob.mx/cfd/3 (versions 3.2 and 3.3) or http://www.sat.gob.mx/cfd/4 (version 4.0).
  2. Namespace prefix — The CFDI namespace must be bound to the prefix cfdi.
  3. Root element name — The root element must be named cfdi:Comprobante.
  4. Version attribute — The Version attribute on the root element must match the namespace in use.
If none of the supported version/namespace combinations match, a CfdiCreateObjectException is thrown listing the individual exceptions for each attempted version.
The constructor performs structural checks only — it does not validate business rules or SAT compliance. Construct a Cfdi object to read the document; use the validator classes to verify it.
Once you have the root NodeInterface from getNode(), you can read attributes, check for optional fields, and traverse child nodes.

Read an Attribute

Access any attribute by name using array notation. If the attribute is absent, an empty string is returned — no exception is thrown.
<?php
/** @var \CfdiUtils\Cfdi $cfdi */
$comprobante = $cfdi->getNode();

echo $comprobante['Serie'];    // e.g. "A" — empty string if not set
echo $comprobante['Version'];  // e.g. "4.0"

Check Whether an Attribute is Set

Use isset() to distinguish between an absent attribute and one explicitly set to an empty value.
<?php
/** @var \CfdiUtils\Cfdi $cfdi */
$comprobante = $cfdi->getNode();

if (isset($comprobante['MetodoPago'])) {
    echo $comprobante['MetodoPago'];
}

Find All Matching Child Nodes

Use searchNodes() to retrieve all descendants matching a path of node names. It returns an iterable collection.
<?php
/** @var \CfdiUtils\Cfdi $cfdi */
$comprobante = $cfdi->getNode();

$conceptos = $comprobante->searchNodes('cfdi:Conceptos', 'cfdi:Concepto');
foreach ($conceptos as $concepto) {
    echo $concepto['Unidad'];
}

Find the First Matching Child Node

Use searchNode() to find the first descendant matching a path. Returns the node or null if not found.
searchNode() (singular) returns a single NodeInterface|null. searchNodes() (plural) returns a collection. Do not confuse the two.
<?php
/** @var \CfdiUtils\Cfdi $cfdi */
$comprobante = $cfdi->getNode();

$tfd = $comprobante->searchNode('cfdi:Complemento', 'tfd:TimbreFiscalDigital');
if (null === $tfd) {
    echo 'No existe el timbre fiscal digital';
} else {
    echo 'UUID: ', $tfd['UUID'], PHP_EOL;
}

Working with Complementos

The cfdi:Complemento node holds the Timbre Fiscal Digital (stamp) and any other complements added by the PAC. Reach it via searchNode() and then navigate to the specific complement by its prefixed name.
<?php
/** @var \CfdiUtils\Cfdi $cfdi */
$comprobante = $cfdi->getNode();

// Access the Timbre Fiscal Digital inside Complemento
$tfd = $comprobante->searchNode('cfdi:Complemento', 'tfd:TimbreFiscalDigital');
if ($tfd !== null) {
    echo 'UUID: ', $tfd['UUID'], PHP_EOL;
    echo 'Fecha de timbrado: ', $tfd['FechaTimbrado'], PHP_EOL;
    echo 'NoCertificadoSAT: ', $tfd['NoCertificadoSAT'], PHP_EOL;
}

Version Detection

When you already have the XML content but want to check the version before constructing a Cfdi object, use CfdiUtils\CfdiVersion. This is useful if your application needs to route different versions to different processing paths.
MethodUse when you have…
getFromXmlString(string $xml)Raw XML string
getFromNode(NodeInterface $node)An existing NodeInterface
getFromDOMDocument(DOMDocument $doc)A loaded DOMDocument
getFromDOMElement(DOMElement $el)A DOMElement
Each method returns the version string (e.g. "3.3") or an empty string if no supported version is detected.
<?php
$cfdiFile    = '/cfdi/archivo-cfdi.xml';
$xmlContents = file_get_contents($cfdiFile);

$cfdiVersion = new \CfdiUtils\CfdiVersion();
$version     = $cfdiVersion->getFromXmlString($xmlContents);

if ('' === $version) {
    echo 'No se reconoció la versión del CFDI';
} else {
    echo 'Versión: ', $version;
}
Cfdi::newFromString() performs version detection internally. Call CfdiVersion directly only when you need the version without constructing the full Cfdi object.

Cleaning Before Reading

CFDI files received from third parties often contain minor XML errors introduced after signing. Run the cleaner before constructing the Cfdi object to avoid parsing failures:
<?php
$cfdiFile    = '/cfdi/recibidos/2018/FEI-456823.xml';
$xmlContents = file_get_contents($cfdiFile);

// Clean the XML first
$xmlContents = \CfdiUtils\Cleaner\Cleaner::staticClean($xmlContents);

// Then create the Cfdi object
$cfdi = \CfdiUtils\Cfdi::newFromString($xmlContents);
See the Cleaning CFDI page for a full explanation of what the cleaner does and when to use it.

Quick Read Alternative

If you only need to read data — not edit the document or validate its structure — consider using getQuickReader() for a simpler, case-insensitive navigation API.
<?php
$comprobante = \CfdiUtils\Cfdi::newFromString(file_get_contents('cfdi.xml'))
    ->getQuickReader();

echo $comprobante['version'];           // "4.0"
echo $comprobante->receptor['nombre'];  // Receptor name
See the Quick Reader page for the full API.

Build docs developers (and LLMs) love