Leer ficheros XML eficientemente en PHP

PHP

Durante los últimos 4 años he tenido que trabajar con muchos proveedores XML (interconectando sistemas B2B) y algunos de estos proveedores ofrecen ficheros XML de gran tamaño algunos de estos ficheros llegan a pesar más de 2GB, como ya puede sospechar no es conveniente cargar 2GB de información en memoria porque PHP lanzará un error fatal de desbordamiento de memoria y su aplicación no será capaz de recuperarse, por tanto hoy les traigo como resolver este problema, para ello usaremos:

* Generadores (Generators): Los generadores permiten implementar iteradores de manera legible y sencilla sin tener que entrar en la complejidad de implementar la interfaz Iterator, para mas información puede leer:Un vistazo a PHP 5.5

* XMLReader: Es una implementación de PHP sobre la librería libxml. que permite leer ficheros XML de manera eficiente ya que actúa como un cursor hacia delante.

* SimpleXMLElement: Crea un Objeto XML a partir de una cadena XML.

Combinando estos 3 conceptos logrará mejor rendimiento y escalabilidad en su aplicación, la lógica general sería:

1. Crear objeto XMLReader a partir del fichero XML que deseamos analizar.

2. Encontrar los nodos con los que deseamos trabajar.

3. Crear objeto SimpleXMLElement a partir del nodo encontrado, como el nodo encontrado es un subconjunto pequeño de toda la información disponible (en el XML) no representa un consumo de memoria excesivo.

4. Devolver un generador. El generador permitirá cargar en memoria solo un nodo de información a la vez.

Implementación precedural

Fork me on Github
<?php
/**
 * Encuentra nodos XML usando XMLReader, Generadores y SimpleXMLElement
 *
 * @param XMLReader $reader
 * @param string $path Camino del nodo XML que se desea encontrar
 * @return Generator
 */
function read(XMLReader $reader, $path) {
    // Establece el camino recorrido por el reader
    $pathNode = '';
    // Comenzar a leer el XML desde el primer nodo
    while ($reader->read()) {
        // Nombre y tipo del nodo en el cual se encuentra el reader
        $nodeName = $reader->name;
        $nodeType = $reader->nodeType;
        /**
         * Analizar, si el nodo es un "start element"
         * @see https://secure.php.net/manual/es/class.xmlreader.php
         */
        if (XMLReader::ELEMENT == $nodeType) {
            if (empty($pathNode) ) {
                $pathNode = $nodeName;
            } else {
                $newPath = implode('/', [$pathNode, $nodeName]);
                /**
                 * Adiciona el nombre del nodo actual al camino recorrido si
                 * forma parte del camino que se está analizando
                 */
                if (false !== strpos($path, $newPath)) {
                    $pathNode = $newPath;
                }
            }
            // Comparar el camino recorrido con el node que se desea encontrar
            if ($pathNode == $path) {
                // Eliminar el nombre del nodo del camino recorrido
                $pathNode = preg_replace("/\/?{$nodeName}$/", '', $pathNode);
                /**
                 * Obtener la representación XML como cadena del nodo encontrado
                 * se incluyen los tags del nodo, se crea un Objeto SimpleXMLElement
                 * y se retorna un Generador
                 */
                yield (new SimpleXMLElement($reader->readOuterXML()));
            }
        }
    }
}

XML de ejemplo

Fork me on Github
<?xml version="1.0" encoding="UTF-8"?>
<records>
  <record>
    <Name>Nicole</Name>
    <Company>Ac Mattis LLC</Company>
    <Address>Ap #823-8881 Adipiscing Avenue</Address>
    <City>Fontanellato</City>
    <Country>Puerto Rico</Country>
    <Phone>(444) 834-6922</Phone>
    <Geo>5.84145, -13.30146</Geo>
    <Description>auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi enim, condimentum eget, volutpat ornare, facilisis eget, ipsum. Donec sollicitudin adipiscing ligula. Aenean gravida nunc sed pede. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel arcu eu odio tristique pharetra. Quisque ac libero nec ligula consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales at, velit. Pellentesque ultricies dignissim lacus. Aliquam rutrum lorem ac risus. Morbi metus. Vivamus euismod urna. Nullam lobortis quam a felis ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem, auctor quis, tristique ac, eleifend vitae, erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus molestie dapibus ligula. Aliquam erat volutpat. Nulla dignissim. Maecenas ornare egestas ligula. Nullam feugiat placerat velit. Quisque varius. Nam porttitor scelerisque neque. Nullam nisl. Maecenas malesuada fringilla est. Mauris eu turpis. Nulla aliquet. Proin velit. Sed malesuada augue ut lacus. Nulla tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a sollicitudin orci sem eget massa. Suspendisse eleifend. Cras sed leo. Cras vehicula aliquet libero. Integer in magna. Phasellus dolor elit, pellentesque a, facilisis non, bibendum sed, est. Nunc laoreet lectus quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc risus varius orci, in consequat enim diam vel arcu. Curabitur ut odio vel est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis purus ac tellus. Suspendisse sed dolor. Fusce mi lorem, vehicula et, rutrum eu, ultrices sit amet, risus. Donec nibh enim, gravida sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec non justo. Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi quis urna. Nunc quis arcu vel quam dignissim pharetra. Nam ac nulla. In tincidunt congue turpis. In condimentum. Donec at arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae Donec tincidunt. Donec vitae erat vel pede blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus vitae velit egestas lacinia. Sed congue, elit sed consequat auctor, nunc nulla vulputate dui, nec tempus mauris erat eget ipsum. Suspendisse sagittis. Nullam vitae diam. Proin dolor. Nulla semper tellus id nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec luctus aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis a, magna. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in sodales elit erat vitae risus. Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus pede sagittis augue, eu tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque sed sem egestas blandit. Nam nulla magna, malesuada vel, convallis in, cursus et, eros. Proin ultrices. Duis volutpat nunc sit amet metus. Aliquam erat volutpat. Nulla facilisis. Suspendisse commodo tincidunt nibh. Phasellus nulla. Integer vulputate, risus a ultricies adipiscing, enim mi tempor lorem, eget mollis lectus pede et risus. Quisque libero lacus, varius et, euismod et, commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem semper erat, in consectetuer ipsum nunc id enim. Curabitur massa. Vestibulum accumsan neque et nunc. Quisque ornare tortor at risus. Nunc ac sem ut dolor dapibus gravida. Aliquam tincidunt, nunc ac mattis ornare, lectus ante dictum mi, ac mattis velit justo nec ante. Maecenas mi felis, adipiscing fringilla, porttitor</Description>
  </record>
  <record>
    <Name>Brady</Name>
    <Company>Nullam Enim Institute</Company>
    <Address>818-4904 Lectus Av.</Address>
    <City>Jennersdorf</City>
    <Country>Virgin Islands, British</Country>
    <Phone>(583) 930-1188</Phone>
    <Geo>-24.42546, 145.2476</Geo>
    <Description>et netus et malesuada fames ac turpis egestas. Fusce aliquet magna a neque. Nullam ut nisi a odio semper cursus. Integer mollis. Integer tincidunt aliquam arcu. Aliquam ultrices iaculis odio. Nam interdum enim non nisi. Aenean eget metus. In nec orci. Donec nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros. Nam consequat dolor vitae dolor. Donec fringilla. Donec feugiat metus sit amet ante. Vivamus non lorem vitae odio sagittis semper. Nam tempor diam dictum sapien. Aenean massa. Integer vitae nibh. Donec est mauris, rhoncus id, mollis nec, cursus a, enim. Suspendisse aliquet, sem ut cursus luctus, ipsum leo elementum sem, vitae aliquam eros turpis non enim. Mauris quis turpis vitae purus gravida sagittis. Duis gravida. Praesent eu nulla at sem molestie sodales. Mauris blandit enim consequat purus. Maecenas libero est, congue a, aliquet vel, vulputate eu, odio. Phasellus at augue id ante dictum cursus. Nunc mauris elit, dictum eu, eleifend nec, malesuada ut, sem. Nulla interdum. Curabitur dictum. Phasellus in felis. Nulla tempor augue ac ipsum. Phasellus vitae mauris sit amet lorem semper auctor. Mauris vel turpis. Aliquam adipiscing lobortis risus. In mi pede, nonummy ut, molestie in, tempus eu, ligula. Aenean euismod mauris eu elit. Nulla facilisi. Sed neque. Sed eget lacus. Mauris non dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae Phasellus ornare. Fusce mollis. Duis sit amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat nec, mollis vitae, posuere at, velit. Cras lorem lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed molestie. Sed id risus quis diam luctus lobortis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Mauris ut quam vel sapien imperdiet ornare. In faucibus. Morbi vehicula. Pellentesque tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi enim, condimentum eget, volutpat ornare, facilisis eget, ipsum. Donec sollicitudin adipiscing ligula. Aenean gravida nunc sed pede. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel arcu eu odio tristique pharetra. Quisque ac libero nec ligula consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales at, velit. Pellentesque ultricies dignissim lacus. Aliquam rutrum lorem ac risus. Morbi metus. Vivamus euismod urna. Nullam lobortis quam a felis ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem, auctor quis, tristique ac, eleifend vitae, erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus molestie dapibus ligula. Aliquam erat volutpat. Nulla dignissim. Maecenas ornare egestas ligula. Nullam feugiat placerat velit. Quisque varius. Nam porttitor scelerisque neque. Nullam nisl. Maecenas malesuada fringilla est. Mauris eu turpis. Nulla aliquet. Proin velit. Sed malesuada augue ut lacus. Nulla tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a sollicitudin orci sem eget massa. Suspendisse eleifend. Cras sed leo. Cras vehicula aliquet libero. Integer in magna. Phasellus dolor elit, pellentesque a, facilisis non, bibendum sed, est. Nunc laoreet lectus quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc risus varius orci, in consequat enim diam vel arcu. Curabitur ut odio vel est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis purus ac tellus. Suspendisse sed dolor. Fusce mi lorem, vehicula et, rutrum eu, ultrices sit amet, risus. Donec nibh enim, gravida sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec non justo. Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper</Description>
  </record>
</records>

Usando el reader

Fork me on Github
<?php
// Creamo el reader
$reader = new XMLReader();
// Abrimos el reader pasándole el camino donde se encuentra el XML que se desea analizar
if ($reader->open(__DIR__ . '/data.xml', LIBXML_NOBLANKS|LIBXML_COMPACT)) {
    $i = 0;
    /**
     * Iteramos sobre los valores encontrados, esto es posible debido a que
     * la clase Generator implementa la interfaz Iterator.
     * @var SimpleXMLElement $node
     */
    foreach (read($reader, 'records/record') as $node) {
        echo ++$i, ' - ', $node->Name,', ', $node->Country, PHP_EOL;
    }
    // Tambien es posible especificar un node interno
    foreach (read($reader, 'records/record/Name') as $node) {
        echo ++$i, ' - ', $node, PHP_EOL;
    }
} else {
    echo "Failed to open";
}

Implementación Orientado a Objetos

Fork me on Github
<?php
namespace Util\XML;

use XMLReader;
use SimpleXMLElement;

class Reader
{
    /**
     * @property XMLReader $reader
     */
    private $reader;

    /**
     * Create a new instance of Reader
     *
     * @param string $file The XML file full path
     */
    public function __construct($file)
    {
        if (!is_readable($file)) {
            throw new \UnexpectedValueException("Failed to open file:
                {$file} Permission denied " . __CLASS__ . ' ' .
                __FUNCTION__ . ' method');
        }

        $this->reader = new XMLReader();
        $this->reader->open($file, LIBXML_NOBLANKS|LIBXML_COMPACT);
    }

    public function read($path)
    {
         if (empty($path)) {
            throw new \UnexpectedValueException("Node path can't be empty" .
                __CLASS__ . ' ' .
                __FUNCTION__ . ' method');
        }

        // Establece el camino recorrido por el reader
        $pathNode = '';
        // Comenzar a leer el XML desde el primer nodo
        while ($this->reader->read()) {
            // Nombre y tipo del nodo en el cual se encuentra el reader
            $nodeName = $this->reader->name;
            $nodeType = $this->reader->nodeType;
            /**
             * Analizar, si el nodo es un "start element"
             * @see https://secure.php.net/manual/es/class.xmlreader.php
             */
            if (XMLReader::ELEMENT == $nodeType) {
                if (empty($pathNode) ) {
                    $pathNode = $nodeName;
                } else {
                    $newPath = implode('/', [$pathNode, $nodeName]);
                    /**
                     * Adiciona el nombre del nodo actual al camino recorrido si
                     * forma parte del camino que se está analizando
                     */
                    if (false !== strpos($path, $newPath)) {
                        $pathNode = $newPath;
                    }
                }
                // Comparar el camino recorrido con el node que se desea encontrar
                if ($pathNode == $path) {
                    // Eliminar el nombre del nodo del camino recorrido
                    $pathNode = preg_replace("/\/?{$nodeName}$/", '', $pathNode);
                    /**
                     * Obtener la representación XML como cadena del nodo encontrado
                     * se incluyen los tags del nodo, se crea un Objeto SimpleXMLElement
                     * y se retorna un Generador
                     */
                    yield (new SimpleXMLElement($this->reader->readOuterXML()));
                }
            }
        }
    }
}

Usando la clase Reader

Fork me on Github
<?php
use Util\XML\Reader as Reader;

 $i = 0;
 /**
 * Iteramos sobre los valores encontrados, esto es posible debido a que
 * la clase Generator implementa la interfaz Iterator.
 * @var SimpleXMLElement $node
 */
foreach ((new Reader(__DIR__ . '/data.xml'))->read('records/record') as $node) {
    echo ++$i, ' - ', $node->Name,', ', $node->Country, PHP_EOL;
}

Nota: Esta implementación ha sido probada con un XML de 875 MB, generado usando: generatedata.com




Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *