0

I am trying to read an XML file in PHP using the library SimpleXMLElement but when obtaining its value I find the following error:

Fatal error: Uncaught Error: Call to a member function attributes() on null

The XML file I am trying to read is the following:

<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante Version="3.3" Total="45264.13">
    <cfdi:Emisor Rfc="ABC123456AB1" Nombre="JOHN DOE"/>
     <cfdi:Conceptos>
         <cfdi:Concepto ValorUnitario="93.80">
        </cfdi:Concepto>
    </cfdi:Conceptos>
     <cfdi:Complemento>
        <tfd:Digital Version="1.1" Sello="BXTJPwDh+" NoCertificado="0000100"
            FechaTimbrado="2019-07-18" UID="C58C"
            xmlns:tfd="http://www.stackoverflow.com"/>
    </cfdi:Complemento>
</cfdi:Comprobante>

I am reading different values ​​inside the XML such as the rfc and total and without them I have no problems.

The value that I am trying to read and am getting trouble with is UID.

The way I am reading the XML values ​​is as follows:

 $xml = new SimpleXMlElement( $_FILES['XmlToUpload']['tmp_name'], 0, true );
  $factura = (isset($_POST['factura'])) ? trim($_POST['factura']) : '';
  $total = (float)$xml['Total'];      
  $rfc = (string)$xml->children('cfdi',true)->Emisor[0]->attributes()['Rfc'];
  $uid = (string)$xml->children('tfd',true)->Digital[0]->attributes()['UID'];

I repeat with rfc and total I have not had any problem, I am following the same idea but to read the UID if it generates the error mentioned at the beginning.

You know if the way I should read this value of is different UID or I am making a mistake.

2 Answers2

1

The "Digital" element is inside the "Complemento" element, but your $xml variable represents the top-level "Comprobante" element.

So you need to navigate through:

$uid = (string)$xml
    ->children('cfdi',true)->Complemento[0]
    ->children('tfd',true)->Digital[0]
    ->attributes()['UID'];

Note that you can leavel off the [0] which makes it easier to read when there's only one element with that name:

$uid = (string)$xml
    ->children('cfdi',true)->Complemento
    ->children('tfd',true)->Digital
    ->attributes()['UID'];

As a separate note, I would strongly advise you to use the full namespace identifiers (URIs) rather than the short prefixes, unless this is code you're going to throw away. The prefixes could change at any time (e.g. because the software generating the XML gets changed), but the full identifiers won't. You can put them in constants to avoid re-typing/pasting them, like this:

const XMLNS_CFDI='http://example.com/you-didnt-include-this-in-your-example';
const XMLNS_TFD='http://www.stackoverflow.com';

$uid = (string)$xml
    ->children(XMLNS_CFDI)->Complemento
    ->children(XMLNS_TFD)->Digital
    ->attributes()['UID'];
IMSoP
  • 89,526
  • 13
  • 117
  • 169
0

An alternative to the earlier answer posted, you could facilitate the parsing and referencing of your XML data by processing it to an associative array. Strip the namespaces and turn the xml into an associative array using json_decode(json_encode($xml), true);.

You can use var_dump() or print_r() to get a visual of the array structure to target your elements of choice.

<?php
$xml = file_get_contents('data.xml');
$cleanXml = str_ireplace(['cfdi:', 'tfd:'], '', $xml); // strip namespace prefixes from xml
$xml = simplexml_load_string($cleanXml);
$arr = json_decode(json_encode($xml), true);

// reference the elements you need in the associative array
$total = (float)$arr['@attributes']['Total'];
$rfc = (string)$arr['Emisor']['@attributes']['Rfc'];
$uid = (string)$arr['Complemento']['Digital']['@attributes']['UID'];

// output
echo $total;
echo $rfc;
echo $uid;

demo

jibsteroos
  • 1,366
  • 2
  • 7
  • 13