TigerZF
🌐Español

33.9. Zend_Feed_Reader

33.9.1. Introducción

Zend_Feed_Reader es un componente utilizado para consumir feeds RSS y Atom de cualquier versión, incluyendo RDF/RSS 1.0, RSS 2.0, Atom 0.3 y Atom 1.0. La API para recuperar los datos del feed es deliberadamente simple ya que Zend_Feed_Reader es capaz de buscar en cualquier feed de cualquier tipo la información solicitada a través de la API. Si los elementos típicos que contienen esta información no están presentes, se adaptará y recurrirá a una variedad de elementos alternativos en su lugar. Esta capacidad de elegir entre alternativas elimina la necesidad de que los usuarios creen su propia capa de abstracción sobre el componente para hacerlo útil o de tener un conocimiento profundo de los estándares subyacentes, las alternativas actuales y las extensiones con espacio de nombres.

Internamente, Zend_Feed_Reader funciona casi por completo realizando consultas XPath contra el Document Object Model del XML del feed. El DOM no se expone mediante una API de propiedades encadenadas como Zend_Feed, aunque los objetos subyacentes DOMDocument, DOMElement y DOMXPath se exponen para su manipulación externa. Este enfoque singular de análisis es consistente y el componente ofrece un sistema de plugins para añadir a la API de nivel de Feed y Entry escribiendo Extensiones de manera similar.

El rendimiento se ve favorecido de tres maneras. En primer lugar, Zend_Feed_Reader admite el uso de caché mediante Zend_Cache para mantener una copia del XML original del feed. Esto le permite omitir peticiones de red para una URI de feed si la caché es válida. En segundo lugar, la API de nivel de Feed y Entry está respaldada por una caché interna (no persistente) de modo que las llamadas repetidas a la API para el mismo feed evitarán un uso adicional del DOM o de XPath. En tercer lugar, importar feeds desde una URI puede aprovechar las peticiones HTTP Condicionales GET, que permiten a los servidores emitir una respuesta 304 vacía cuando el feed solicitado no ha cambiado desde la última vez que lo solicitó. En este último caso, una instancia de Zend_Cache mantendrá el último feed recibido junto con los valores de las cabeceras ETag y Last-Modified enviados en la respuesta HTTP.

En relación con Zend_Feed, Zend_Feed_Reader se concibió como un reemplazo independiente de Zend_Feed, pero no es compatible hacia atrás con Zend_Feed. Más bien, es una alternativa que sigue una ideología diferente centrada en ser sencillo de usar, flexible, consistente y ampliable mediante el sistema de plugins. Zend_Feed_Reader tampoco es capaz de construir feeds y delega esa responsabilidad en Zend_Feed_Writer, su compañero de armas.

33.9.2. Importando feeds

Importar un feed con Zend_Feed_Reader no es muy diferente de hacerlo con Zend_Feed. Los feeds pueden importarse desde una cadena, un archivo, una URI o una instancia de tipo Zend_Feed_Abstract. Al importar desde una URI puede utilizarse adicionalmente una petición HTTP Condicional GET. Si la importación falla, se lanzará una excepción. El resultado final será un objeto de tipo Zend_Feed_Reader_FeedInterface, cuyas implementaciones principales son Zend_Feed_Reader_Feed_Rss y Zend_Feed_Reader_Feed_AtomZend_Feed se quedó con todos los nombres cortos!). Ambos objetos admiten múltiples versiones (todas las existentes) de estos tipos generales de feed.

En el siguiente ejemplo, importamos un feed RDF/RSS 1.0 y extraemos alguna información básica que puede guardarse en una base de datos o en otro lugar.

$feed = Zend_Feed_Reader::import('http://www.planet-php.net/rdf/');
$data = array(
    'title'        => $feed->getTitle(),
    'link'         => $feed->getLink(),
    'dateModified' => $feed->getDateModified(),
    'description'  => $feed->getDescription(),
    'language'     => $feed->getLanguage(),
    'entries'      => array(),
);

foreach ($feed as $entry) {
    $edata = array(
        'title'        => $entry->getTitle(),
        'description'  => $entry->getDescription(),
        'dateModified' => $entry->getDateModified(),
        'authors'       => $entry->getAuthors(),
        'link'         => $entry->getLink(),
        'content'      => $entry->getContent()
    );
    $data['entries'][] = $edata;
}

El ejemplo anterior demuestra la API de Zend_Feed_Reader, y también demuestra parte de su funcionamiento interno. En realidad, el feed RDF seleccionado no tiene ningún elemento nativo de fecha o autor, sin embargo utiliza el módulo Dublin Core 1.1, que ofrece elementos de creador y fecha con espacio de nombres. Zend_Feed_Reader recurre a estos y opciones similares si no existen elementos nativos relevantes. Si no puede encontrar en absoluto una alternativa, devolverá NULL, indicando que no se pudo encontrar la información en el feed. Debe tener en cuenta que las clases que implementan Zend_Feed_Reader_FeedInterface también implementan las interfaces SPL Iterator y Countable.

Los feeds también pueden importarse desde cadenas, archivos, e incluso objetos de tipo Zend_Feed_Abstract.

// desde una URI
$feed = Zend_Feed_Reader::import('http://www.planet-php.net/rdf/');

// desde una cadena
$feed = Zend_Feed_Reader::importString($feedXmlString);

// desde un archivo
$feed = Zend_Feed_Reader::importFile('./feed.xml');

// desde un objeto Zend_Feed_Abstract
$zfeed = Zend_Feed::import('http://www.planet-php.net/atom/');
$feed  = Zend_Feed_Reader::importFeed($zfeed);

33.9.3. Recuperando las fuentes subyacentes de Feed y Entry

Zend_Feed_Reader hace todo lo posible por no encasillarle en un espacio limitado. Si necesita trabajar con un feed fuera de Zend_Feed_Reader, puede extraer los objetos DOMDocument o DOMElement base de cualquier clase, o incluso una cadena XML que los contenga. También se proporcionan métodos para extraer el objeto DOMXPath actual (con todos los espacios de nombres del núcleo y de las Extensiones registrados) y el prefijo correcto usado en todas las consultas XPath para el Feed o Entry actual. Los métodos básicos a utilizar (en cualquier objeto) son saveXml(), getDomDocument(), getElement(), getXpath() y getXpathPrefix(). Estos le permitirán liberarse de Zend_Feed_Reader y hacer cualquier otra cosa que desee.

  • saveXml() devuelve una cadena XML que contiene únicamente el elemento que representa el objeto actual.

  • getDomDocument() devuelve el objeto DOMDocument que representa el feed completo (incluso si se llama desde un objeto Entry).

  • getElement() devuelve el DOMElement del objeto actual (es decir, el Feed o el Entry actual).

  • getXpath() devuelve el objeto DOMXPath del feed actual (incluso si se llama desde un objeto Entry) con los espacios de nombres del tipo de feed actual y todas las Extensiones cargadas preregistrados.

  • getXpathPrefix() devuelve el prefijo de consulta para el objeto actual (es decir, el Feed o el Entry actual) que incluye la ruta de consulta XPath correcta para ese Feed o Entry específico.

He aquí un ejemplo donde un feed podría incluir una Extensión RSS no soportada por Zend_Feed_Reader de forma predeterminada. Cabe destacar que podría escribir y registrar una Extensión (tratado más adelante) para hacer esto, pero eso no siempre se justifica para una comprobación rápida. Debe registrar cualquier espacio de nombres nuevo en el objeto DOMXPath antes de usarlo, a menos que ya esté registrado por Zend_Feed_Reader o por una Extensión de antemano.

$feed        = Zend_Feed_Reader::import('http://www.planet-php.net/rdf/');
$xpathPrefix = $feed->getXpathPrefix();
$xpath       = $feed->getXpath();
$xpath->registerNamespace('admin', 'http://webns.net/mvcb/');
$reportErrorsTo = $xpath->evaluate('string('
                                 . $xpathPrefix
                                 . '/admin:errorReportsTo)');
[Warning] Advertencia

Si registra un espacio de nombres ya registrado con un nombre de prefijo diferente al usado internamente por Zend_Feed_Reader, esto romperá el funcionamiento interno de este componente.

33.9.4. Soporte de caché y peticiones inteligentes

33.9.4.1. Añadiendo soporte de caché a Zend_Feed_Reader

Zend_Feed_Reader admite el uso de una instancia de Zend_Cache para almacenar en caché feeds (como XML) y así evitar peticiones de red innecesarias. Añadir una caché es aquí tan sencillo como en otros componentes de Zend Framework: cree y configure su caché y luego indique a Zend_Feed_Reader que la use. La clave de caché utilizada es "Zend_Feed_Reader_" seguida del hash MD5 de la URI del feed.

$frontendOptions = array(
   'lifetime' => 7200,
   'automatic_serialization' => true
);
$backendOptions = array('cache_dir' => './tmp/');
$cache = Zend_Cache::factory(
    'Core', 'File', $frontendOptions, $backendOptions
);

Zend_Feed_Reader::setCache($cache);
[Note] Nota

Aunque se aparte un poco del tema, también debería considerar añadir una caché a Zend_Loader_PluginLoader, que es utilizado por Zend_Feed_Reader para cargar Extensiones.

33.9.4.2. Soporte de HTTP Conditional GET

La gran pregunta que a menudo se hace al importar un feed con frecuencia es si realmente ha cambiado. Con una caché habilitada, puede añadir soporte de HTTP Condicional GET a su arsenal para responder esa pregunta.

Usando este método, puede solicitar feeds desde URIs e incluir los últimos valores conocidos de las cabeceras de respuesta ETag y Last-Modified con la petición (usando las cabeceras If-None-Match e If-Modified-Since). Si el feed en el servidor permanece sin cambios, debería recibir una respuesta 304 que indica a Zend_Feed_Reader que use la versión en caché. Si se envía un feed completo en una respuesta con un código de estado 200, esto significa que el feed ha cambiado y Zend_Feed_Reader analizará la nueva versión y la guardará en la caché. También almacenará en caché los nuevos valores de las cabeceras ETag y Last-Modified para uso futuro.

Estas peticiones "condicionales" no tienen garantizado el soporte por parte del servidor al que se solicita una URI, pero pueden intentarse de todos modos. Sin embargo, la mayoría de las fuentes de feed comunes, como los blogs, deberían soportarlo. Para habilitar las peticiones condicionales, necesitará proporcionar una caché a Zend_Feed_Reader.

$frontendOptions = array(
   'lifetime' => 86400,
   'automatic_serialization' => true
);
$backendOptions = array('cache_dir' => './tmp/');
$cache = Zend_Cache::factory(
    'Core', 'File', $frontendOptions, $backendOptions
);

Zend_Feed_Reader::setCache($cache);
Zend_Feed_Reader::useHttpConditionalGet();

$feed = Zend_Feed_Reader::import('http://www.planet-php.net/rdf/');

En el ejemplo anterior, con las peticiones HTTP Condicionales GET habilitadas, los valores de las cabeceras de respuesta ETag y Last-Modified se almacenarán en caché junto con el feed. Durante las próximas 24 horas (el tiempo de vida de la caché), los feeds solo se actualizarán en la caché si se recibe una respuesta distinta de 304 que contenga un documento XML RSS o Atom válido.

Si pretende gestionar las cabeceras de la petición desde fuera de Zend_Feed_Reader, puede establecer las cabeceras de petición If-None-Matches e If-Modified-Since correspondientes mediante el método de importación por URI.

$lastEtagReceived = '5e6cefe7df5a7e95c8b1ba1a2ccaff3d';
$lastModifiedDateReceived = 'Wed, 08 Jul 2009 13:37:22 GMT';
$feed = Zend_Feed_Reader::import(
    $uri, $lastEtagReceived, $lastModifiedDateReceived
);

33.9.5. Localizando URIs de feeds desde sitios web

Hoy en día, muchos sitios web son conscientes de que la ubicación de sus feeds XML no siempre es obvia. Un pequeño gráfico RDF, RSS o Atom ayuda cuando el usuario está leyendo la página, pero ¿qué ocurre cuando una máquina visita la página intentando identificar dónde están ubicados sus feeds? Para ayudar en esto, los sitios web pueden apuntar a sus feeds usando etiquetas <link> en la sección <head> de su HTML. Para aprovechar esto, puede usar Zend_Feed_Reader para localizar estos feeds usando el método estático findFeedLinks().

Este método llama a cualquier URI y busca la ubicación de feeds RSS, RDF y Atom asumiendo que el HTML del sitio web contiene los enlaces relevantes. Luego devuelve un objeto de valor donde puede comprobar la existencia de una URI de feed RSS, RDF o Atom.

El objeto devuelto es una subclase de ArrayObject llamada Zend_Feed_Reader_Collection_FeedLink, de modo que puede convertirla a un array, o iterar sobre ella, para acceder a todos los enlaces detectados. Sin embargo, como un atajo sencillo, puede simplemente tomar el primer enlace RSS, RDF o Atom usando sus propiedades públicas como en el ejemplo siguiente. De lo contrario, cada elemento del ArrayObject es un array simple con las claves "type" y "uri" donde el type es uno de "rdf", "rss" o "atom".

$links = Zend_Feed_Reader::findFeedLinks('http://www.planet-php.net');

if(isset($links->rdf)) {
    echo $links->rdf, "\n"; // http://www.planet-php.org/rdf/
}
if(isset($links->rss)) {
    echo $links->rss, "\n"; // http://www.planet-php.org/rss/
}
if(isset($links->atom)) {
    echo $links->atom, "\n"; // http://www.planet-php.org/atom/
}

Basándose en estos enlaces, puede entonces importar desde la fuente que desee de la manera habitual.

Este método rápido solo le proporciona un enlace por cada tipo de feed, pero los sitios web pueden indicar muchos enlaces de cualquier tipo. Quizás se trate de un sitio de noticias con un feed RSS para cada categoría de noticias. Puede iterar sobre todos los enlaces usando el iterador del ArrayObject.

$links = Zend_Feed_Reader::findFeedLinks('http://www.planet-php.net');

foreach ($links as $link) {
    echo $link['uri'], "\n";
}

33.9.6. Colecciones de atributos

En un intento por simplificar los tipos de retorno, con Zend Framework 1.10 los tipos devueltos por los distintos métodos de nivel de feed y entrada pueden incluir un objeto de tipo Zend_Feed_Reader_Collection_CollectionAbstract. A pesar del nombre especial de la clase, que explicaré a continuación, esto es solo una simple subclase de ArrayObject de SPL.

El propósito principal aquí es permitir la presentación de la mayor cantidad posible de datos de los elementos solicitados, a la vez que se permite el acceso a los datos más relevantes como un simple array. Esto también impone un enfoque estándar para devolver dichos datos, que anteriormente podían oscilar entre arrays y objetos.

El nuevo tipo de clase actúa de forma idéntica a ArrayObject con la única adición de un nuevo método getValues() que devuelve un simple array plano que contiene la información más relevante.

Un ejemplo sencillo de esto es Zend_Feed_Reader_FeedInterface::getCategories(). Cuando se usa con cualquier feed RSS o Atom, este método devolverá los datos de categoría como un objeto contenedor llamado Zend_Feed_Reader_Collection_Category. El objeto contenedor contendrá, por categoría, tres campos de datos: term, scheme y label. El "term" es el nombre básico de la categoría, a menudo legible por máquina (es decir, funciona bien con URIs). El scheme representa un esquema de categorización (normalmente un identificador URI) también conocido como "domain" en RSS 2.0. El "label" es un nombre de categoría legible por humanos que admite entidades HTML. En RSS 2.0 no existe un atributo label, por lo que siempre se establece con el mismo valor que el term, por comodidad.

Para acceder a las etiquetas de categoría por sí mismas en un simple array de valores, podría hacer algo como:

$feed = Zend_Feed_Reader::import('http://www.example.com/atom.xml');
$categories = $feed->getCategories();
$labels = array();
foreach ($categories as $cat) {
    $labels[] = $cat['label']
}

Es un ejemplo un tanto forzado, pero el punto es que las etiquetas están entrelazadas con otra información.

Sin embargo, la clase contenedora le permite acceder a los datos "más relevantes" como un simple array usando el método getValues(). El concepto de "más relevante" es obviamente una decisión subjetiva. Para las categorías significa las etiquetas de categoría (no los terms ni los schemes), mientras que para los autores serían sus nombres (no sus direcciones de correo electrónico ni URIs). El array simple es plano (solo valores) y se pasa por array_unique() para eliminar duplicados.

$feed = Zend_Feed_Reader::import('http://www.example.com/atom.xml');
$categories = $feed->getCategories();
$labels = $categories->getValues();

El ejemplo anterior muestra cómo extraer únicamente las etiquetas y nada más, ofreciendo así un acceso sencillo a las etiquetas de categoría sin trabajo adicional para extraer esos datos por sí mismos.

33.9.7. Recuperando información del feed

Recuperar información de un feed (cubriremos las entradas e items en la siguiente sección, aunque siguen los mismos principios) utiliza una API claramente definida que es exactamente la misma independientemente de si el feed en cuestión es RSS, RDF o Atom. Lo mismo aplica para las subversiones de estos estándares, y hemos probado cada una de las versiones de RSS y Atom. Aunque el XML subyacente del feed puede diferir sustancialmente en términos de las etiquetas y elementos que presentan, no obstante todos intentan transmitir información similar y para reflejar esto todas las diferencias y disparidades entre etiquetas alternativas son gestionadas internamente por Zend_Feed_Reader, presentándole una interfaz idéntica para cada uno. Idealmente, no debería tener que preocuparse por si un feed es RSS o Atom, siempre que pueda extraer la información que desea.

[Note] Nota

Aunque determinar el terreno común entre los tipos de feed es en sí mismo complejo, cabe señalar que RSS en particular es una "especificación" constantemente disputada. Esto tiene sus raíces en el documento original de RSS 2.0, que contiene ambigüedades y no detalla el tratamiento correcto de todos los elementos. Como resultado, este componente aplica rigurosamente la Especificación RSS 2.0.11 publicada por la RSS Advisory Board y su Perfil de Mejores Prácticas de RSS que la acompaña. No se soportará ninguna otra interpretación de RSS 2.0, aunque pueden permitirse excepciones cuando no impidan directamente la aplicación de los dos documentos mencionados anteriormente.

Por supuesto, no vivimos en un mundo ideal, por lo que puede haber ocasiones en las que la API simplemente no cubra lo que está buscando. Para ayudarle, Zend_Feed_Reader ofrece un sistema de plugins que le permite escribir Extensiones para ampliar la API del núcleo y cubrir cualquier dato adicional que esté tratando de extraer de los feeds. Si escribir otra Extensión resulta demasiado complicado, puede simplemente tomar los objetos DOM o XPath subyacentes y hacerlo manualmente en su aplicación. Por supuesto, realmente le animamos a escribir una Extensión simplemente para hacerla más portable y reutilizable, y las Extensiones útiles pueden proponerse al Framework para su incorporación formal.

He aquí un resumen de la API del núcleo para Feeds. Debe tener en cuenta que comprende no solo los estándares básicos de RSS y Atom, sino que también tiene en cuenta una serie de Extensiones incluidas junto con Zend_Feed_Reader. La nomenclatura de estos métodos provenientes de Extensiones sigue siendo bastante genérica: todos los métodos de las Extensiones operan al mismo nivel que la API del núcleo, aunque le permitimos recuperar cualquier objeto de Extensión específico por separado si es necesario.

Tabla 33.1. Métodos de la API a nivel de Feed

getId() Devuelve un ID único asociado con este feed
getTitle() Devuelve el título del feed
getDescription() Devuelve la descripción de texto del feed.
getLink() Devuelve una URI al sitio web HTML que contiene la misma información o similar a este feed (es decir, si el feed proviene de un blog, debería proporcionar la URI del blog donde se puede leer la versión HTML de las entradas).
getFeedLink() Devuelve la URI de este feed, que puede ser la misma URI usada para importar el feed. Hay casos importantes en los que el enlace del feed puede diferir porque la URI de origen se está actualizando y se pretende eliminarla en el futuro.
getAuthors() Devuelve un objeto de tipo Zend_Feed_Reader_Collection_Author, que es un ArrayObject cuyos elementos son cada uno arrays simples que contienen cualquier combinación de las claves "name", "email" y "uri". Cuando no sean relevantes para los datos de origen, algunas de estas claves pueden omitirse.
getAuthor(integer $index = 0) Devuelve el primer autor conocido, o con el parámetro opcional $index cualquier índice específico del array de Authors descrito anteriormente (devolviendo NULL si el índice no es válido).
getDateCreated() Devuelve la fecha en la que se creó este feed. Generalmente solo aplica a Atom, donde representa la fecha en que se creó el recurso descrito por un documento Atom 1.0. La fecha devuelta será un objeto Zend_Date.
getDateModified() Devuelve la fecha en la que este feed se modificó por última vez. La fecha devuelta será un objeto Zend_Date.
getLastBuildDate() Devuelve la fecha en la que este feed se construyó por última vez. La fecha devuelta será un objeto Zend_Date. Esto solo es compatible con RSS: los feeds Atom siempre devolverán NULL.
getLanguage() Devuelve el idioma del feed (si está definido) o simplemente el idioma indicado en el documento XML.
getGenerator() Devuelve el generador del feed, por ejemplo, el software que lo generó. Esto puede diferir entre RSS y Atom ya que Atom define una notación distinta.
getCopyright() Devuelve cualquier aviso de copyright asociado con el feed.
getHubs() Devuelve un array de todos los endpoints URI de servidores Hub anunciados por el feed para su uso con el Protocolo Pubsubhubbub, permitiendo suscripciones al feed para actualizaciones en tiempo real.
getCategories() Devuelve un objeto Zend_Feed_Reader_Collection_Category que contiene los detalles de las categorías asociadas con el feed general. Los campos soportados incluyen "term" (el nombre de categoría legible por máquina), "scheme" (el esquema de categorización y dominio para esta categoría), y "label" (un nombre de categoría legible por humanos, decodificado de HTML). Cuando cualquiera de los tres campos está ausente, se establecen al valor alternativo más cercano disponible o, en el caso de "scheme", se establece en NULL.
getImage() Devuelve un array que contiene datos relacionados con cualquier imagen o logo del feed, o NULL si no se encuentra ninguna imagen. El array resultante puede contener las siguientes claves: uri, link, title, description, height, y width. Los logos de Atom solo contienen una URI, por lo que el resto de los metadatos se toman únicamente de feeds RSS.

Dada la variedad de feeds existentes, algunos de estos métodos sin duda devolverán NULL, indicando que la información relevante no pudo localizarse. Cuando sea posible, Zend_Feed_Reader recurrirá a elementos alternativos durante su búsqueda. Por ejemplo, buscar una fecha de modificación en un feed RSS es más complicado de lo que parece. Los feeds RSS 2.0 deberían incluir una etiqueta <lastBuildDate> y (o) un elemento <pubDate>. Pero ¿qué ocurre si no lo hace, tal vez se trate de un feed RSS 1.0? Quizás en su lugar tenga un elemento <atom:updated> con información idéntica (Atom puede usarse para complementar la sintaxis de RSS). De no ser así, podríamos simplemente mirar las entradas, elegir la más reciente y usar su elemento <pubDate>. Suponiendo que exista... Muchos feeds también usan elementos <dc:date> de Dublin Core 1.0 o 1.1 para feeds y entradas. O podríamos encontrar a Atom acechando de nuevo.

El punto es que Zend_Feed_Reader fue diseñado para saber esto. Cuando solicita la fecha de modificación (o cualquier otra cosa), buscará todas estas alternativas hasta que se rinda y devuelva NULL, o encuentre una alternativa que debería tener la respuesta correcta.

Además de los métodos anteriores, todos los objetos Feed implementan métodos para recuperar los objetos DOM y XPath de los feeds actuales, según se describió anteriormente. Los objetos Feed también implementan las interfaces SPL Iterator y Countable. La API extendida se resume a continuación.

Tabla 33.2. Métodos de la API extendida a nivel de Feed

getDomDocument() Devuelve el objeto DOMDocument padre para todo el documento XML de origen
getElement() Devuelve el objeto DOMElement de nivel de feed actual
saveXml() Devuelve una cadena que contiene un documento XML del elemento del feed completo (esto no es el documento original sino una versión reconstruida)
getXpath() Devuelve el objeto DOMXPath usado internamente para ejecutar consultas sobre el objeto DOMDocument (esto incluye espacios de nombres del núcleo y de las Extensiones preregistrados)
getXpathPrefix() Devuelve el prefijo de ruta DOM válido antepuesto a todas las consultas XPath que coinciden con el feed consultado
getEncoding() Devuelve la codificación del documento XML de origen (nota: esto no puede tener en cuenta errores como que el servidor envíe documentos en una codificación diferente). Cuando no está definida, se aplica la codificación UTF-8 predeterminada de Unicode.
count() Devuelve un recuento de las entradas o items que contiene este feed (implementa la interfaz SPL Countable)
current() Devuelve la entrada actual (usando el índice actual de key())
key() Devuelve el índice de la entrada actual
next() Incrementa el valor del índice de entrada en uno
rewind() Reinicia el índice de entrada a 0
valid() Comprueba que el índice de la entrada actual sea válido, es decir, que no sea inferior a 0 y no exceda el número de entradas existentes.
getExtensions() Devuelve un array de todos los objetos de Extensión cargados para el feed actual (nota: existen tanto Extensiones a nivel de feed como a nivel de entrada, y aquí solo se devuelven las Extensiones a nivel de feed). Las claves del array tienen la forma {ExtensionName}_Feed.
getExtension(string $name) Devuelve un objeto de Extensión para el feed registrado bajo el nombre proporcionado. Esto permite un acceso más detallado a las Extensiones que de otro modo podrían estar ocultas dentro de la implementación de los métodos estándar de la API.
getType() Devuelve una constante estática de la clase (por ejemplo, Zend_Feed_Reader::TYPE_ATOM_03, es decir, Atom 0.3) que indica exactamente qué tipo de feed se está consumiendo.

33.9.8. Recuperando información de Entry/Item

Recuperar información de entradas o items específicos (dependiendo de si habla de Atom o de RSS) es idéntico a los datos a nivel de feed. Acceder a las entradas es simplemente cuestión de iterar sobre un objeto Feed o usar la interfaz SPL Iterator que implementan los objetos Feed y llamar al método apropiado en cada uno.

Tabla 33.3. Métodos de la API a nivel de Entry

getId() Devuelve un ID único para la entrada actual.
getTitle() Devuelve el título de la entrada actual.
getDescription() Devuelve una descripción de la entrada actual.
getLink() Devuelve una URI a la versión HTML de la entrada actual.
getPermaLink() Devuelve el enlace permanente a la entrada actual. En la mayoría de los casos, es lo mismo que usar getLink().
getAuthors() Devuelve un objeto de tipo Zend_Feed_Reader_Collection_Author, que es un ArrayObject cuyos elementos son cada uno arrays simples que contienen cualquier combinación de las claves "name", "email" y "uri". Cuando no sean relevantes para los datos de origen, algunas de estas claves pueden omitirse.
getAuthor(integer $index = 0) Devuelve el primer autor conocido, o con el parámetro opcional $index cualquier índice específico del array de Authors descrito anteriormente (devolviendo NULL si el índice no es válido).
getDateCreated() Devuelve la fecha en la que se creó la entrada actual. Generalmente solo aplica a Atom, donde representa la fecha en que se creó el recurso descrito por un documento Atom 1.0.
getDateModified() Devuelve la fecha en que la entrada actual se modificó por última vez
getContent() Devuelve el contenido de la entrada actual (con cualquier entidad revertida si es posible, suponiendo que el tipo de contenido sea HTML). Se devuelve la descripción si no existe un elemento de contenido separado.
getEnclosure() Devuelve un array que contiene el valor de todos los atributos de un elemento <enclosure> multimedia, incluyendo como claves del array: url, length, type. De acuerdo con el Perfil de Mejores Prácticas de RSS de la RSS Advisory Board, no se ofrece soporte para múltiples enclosures, ya que dicho soporte no forma parte de la especificación de RSS.
getCommentCount() Devuelve el número de comentarios realizados en esta entrada en el momento en que se generó el feed por última vez
getCommentLink() Devuelve una URI que apunta a la página HTML donde se pueden hacer comentarios sobre esta entrada
getCommentFeedLink([string $type = 'atom'|'rss']) Devuelve una URI que apunta a un feed del tipo indicado que contiene todos los comentarios de esta entrada (el tipo por defecto es Atom/RSS según el tipo de feed actual).
getCategories() Devuelve un objeto Zend_Feed_Reader_Collection_Category que contiene los detalles de las categorías asociadas con la entrada. Los campos soportados incluyen "term" (el nombre de categoría legible por máquina), "scheme" (el esquema de categorización y dominio para esta categoría), y "label" (un nombre de categoría legible por humanos, decodificado de HTML). Cuando cualquiera de los tres campos está ausente, se establecen al valor alternativo más cercano disponible o, en el caso de "scheme", se establece en NULL.

La API extendida para entradas es idéntica a la de los feeds, con la excepción de los métodos del Iterator, que no son necesarios aquí.

[Caution] Precaución

A menudo existe confusión sobre los conceptos de fechas de modificación y de creación. En Atom, se trata de dos conceptos claramente definidos (así que no se preocupe), pero en RSS son vagos. RSS 2.0 define un único elemento <pubDate> que típicamente se refiere a la fecha en que se publicó esta entrada, es decir, una especie de fecha de creación. Esto no siempre es así, y puede cambiar con actualizaciones o no. Como resultado, si realmente desea comprobar si una entrada ha cambiado, no confíe en los resultados de getDateModified(). En su lugar, considere rastrear el hash MD5 de tres elementos concatenados, por ejemplo, usando getTitle(), getDescription() y getContent(). Si la entrada realmente fue actualizada, este cálculo de hash dará un resultado distinto al de los hashes guardados previamente para la misma entrada. Esto está obviamente orientado al contenido, y no ayudará a detectar cambios en otros elementos relevantes. Los feeds Atom no deberían requerir estos pasos.

Para complicar aún más las cosas, las fechas en los feeds pueden seguir diferentes estándares. Las fechas de Atom y de Dublin Core deberían seguir ISO 8601, y las fechas de RSS deberían seguir RFC 822 o RFC 2822, lo cual también es común. Los métodos de fecha lanzarán una excepción si Zend_Date no puede cargar la cadena de fecha usando uno de los estándares anteriores, o las posibilidades reconocidas por PHP para las fechas de RSS.

[Warning] Advertencia

Los valores devueltos por estos métodos no se validan. Esto significa que los usuarios deben realizar validación sobre todos los datos recuperados, incluido el filtrado de cualquier HTML, como el proveniente de getContent(), antes de que sea generado como salida por su aplicación. Recuerde que la mayoría de los feeds provienen de fuentes externas y, por lo tanto, la suposición predeterminada debería ser que no se puede confiar en ellos.

Tabla 33.4. Métodos de la API extendida a nivel de Entry

getDomDocument() Devuelve el objeto DOMDocument padre para todo el feed (no solo la entrada actual)
getElement() Devuelve el objeto DOMElement de nivel de entrada actual
getXpath() Devuelve el objeto DOMXPath usado internamente para ejecutar consultas sobre el objeto DOMDocument (esto incluye espacios de nombres del núcleo y de las Extensiones preregistrados)
getXpathPrefix() Devuelve el prefijo de ruta DOM válido antepuesto a todas las consultas XPath que coinciden con la entrada consultada
getEncoding() Devuelve la codificación del documento XML de origen (nota: esto no puede tener en cuenta errores como que el servidor envíe documentos en una codificación diferente). La codificación predeterminada aplicada en ausencia de cualquier otra es la codificación UTF-8 de Unicode.
getExtensions() Devuelve un array de todos los objetos de Extensión cargados para la entrada actual (nota: existen tanto Extensiones a nivel de feed como a nivel de entrada, y aquí solo se devuelven las Extensiones a nivel de entrada). Las claves del array tienen la forma {ExtensionName}_Entry.
getExtension(string $name) Devuelve un objeto de Extensión para la entrada registrada bajo el nombre proporcionado. Esto permite un acceso más detallado a las Extensiones que de otro modo podrían estar ocultas dentro de la implementación de los métodos estándar de la API.
getType() Devuelve una constante estática de la clase (por ejemplo, Zend_Feed_Reader::TYPE_ATOM_03, es decir, Atom 0.3) que indica exactamente qué tipo de feed se está consumiendo.

33.9.9. Extendiendo las APIs de Feed y Entry

Extender Zend_Feed_Reader le permite añadir métodos tanto a nivel de feed como de entrada que cubran la recuperación de información no soportada aún por Zend_Feed_Reader. Dado el número de extensiones de RSS y Atom que existen, esto es algo bueno ya que Zend_Feed_Reader no podría posiblemente añadir todo.

Existen dos tipos posibles de Extensiones: aquellas que recuperan información de elementos que son hijos inmediatos del elemento raíz (por ejemplo, <channel> para RSS o <feed> para Atom) y aquellas que recuperan información de elementos hijos de una entrada (por ejemplo, <item> para RSS o <entry> para Atom). En el sistema de archivos, estas se agrupan como clases dentro de un espacio de nombres basado en el nombre del estándar de la extensión. Por ejemplo, internamente tenemos las clases Zend_Feed_Reader_Extension_DublinCore_Feed y Zend_Feed_Reader_Extension_DublinCore_Entry, que son dos Extensiones que implementan el soporte de Dublin Core 1.0 y 1.1.

Las Extensiones se cargan en Zend_Feed_Reader usando Zend_Loader_PluginLoader, por lo que su funcionamiento le resultará familiar por otros componentes de Zend Framework. Zend_Feed_Reader ya incluye varias de estas Extensiones; sin embargo, aquellas que no se usan internamente ni se registran de forma predeterminada (las llamadas Extensiones del núcleo) deben registrarse en Zend_Feed_Reader antes de usarse. Las Extensiones incluidas son:

Tabla 33.5. Extensiones del núcleo (preregistradas)

DublinCore (Feed y Entry) Implementa el soporte para Dublin Core Metadata Element Set 1.0 y 1.1
Content (solo Entry) Implementa el soporte para Content 1.0
Atom (Feed y Entry) Implementa el soporte para Atom 0.3 y Atom 1.0
Slash Implementa el soporte para el módulo Slash de RSS 1.0
WellFormedWeb Implementa el soporte para Well Formed Web CommentAPI 1.0
Thread Implementa el soporte para las Atom Threading Extensions descritas en RFC 4685
Podcast Implementa el soporte para la DTD Podcast 1.0 de Apple

Las Extensiones del núcleo son algo especiales ya que son extremadamente comunes y multifacéticas. Por ejemplo, tenemos una Extensión del núcleo para Atom. Atom se implementa como una Extensión (no solo como una clase base) porque también funciona como un módulo RSS válido: se pueden insertar elementos de Atom en feeds RSS. Incluso he visto feeds RDF que usan mucho Atom en lugar de Extensiones más comunes como Dublin Core.

Tabla 33.6. Extensiones no incluidas en el núcleo (deben registrarse manualmente)

Syndication Implementa el soporte de Syndication 1.0 para feeds RSS
CreativeCommons Un módulo de RSS que añade un elemento a nivel de <channel> o <item> que especifica qué licencia de Creative Commons aplica.

Las Extensiones adicionales que no son del núcleo se ofrecen pero no se registran en Zend_Feed_Reader de forma predeterminada. Si desea usarlas, deberá indicar a Zend_Feed_Reader que las cargue antes de importar un feed. Se incluirán Extensiones adicionales que no son del núcleo en futuras iteraciones del componente.

Registrar una Extensión con Zend_Feed_Reader, de modo que se cargue y su API esté disponible para los objetos Feed y Entry, es algo sencillo usando el Zend_Loader_PluginLoader. Aquí registramos la Extensión opcional Slash, y descubrimos que puede llamarse directamente desde la API de nivel de Entry sin ningún esfuerzo. Tenga en cuenta que los nombres de las Extensiones distinguen entre mayúsculas y minúsculas y usan camel case para términos múltiples.

Zend_Feed_Reader::registerExtension('Syndication');
$feed = Zend_Feed_Reader::import('http://rss.slashdot.org/Slashdot/slashdot');
$updatePeriod = $feed->current()->getUpdatePeriod();

En el sencillo ejemplo anterior, comprobamos con qué frecuencia se actualiza un feed usando el método getUpdatePeriod(). Dado que no forma parte de la API del núcleo de Zend_Feed_Reader, solo podía tratarse de un método soportado por la recién registrada Extensión Syndication.

Como también puede notar, los nuevos métodos de las Extensiones son accesibles desde la API principal usando los métodos mágicos de PHP. Como alternativa, también puede acceder directamente a cualquier objeto de Extensión con un resultado similar, como se ve a continuación.

Zend_Feed_Reader::registerExtension('Syndication');
$feed = Zend_Feed_Reader::import('http://rss.slashdot.org/Slashdot/slashdot');
$syndication = $feed->getExtension('Syndication');
$updatePeriod = $syndication->getUpdatePeriod();

33.9.9.1. Escribiendo Extensiones de Zend_Feed_Reader

Inevitablemente, habrá ocasiones en las que la API de Zend_Feed_Reader no sea capaz de obtener algo que necesita de un feed o una entrada. Puede usar los objetos de origen subyacentes, como DOMDocument, para obtenerlos manualmente; sin embargo, hay un método más reutilizable disponible escribiendo Extensiones que soporten estas nuevas consultas.

Como ejemplo, tomemos el caso de una corporación puramente ficticia llamada Jungle Books. Jungle Books ha estado publicando muchas reseñas de los libros que vende (procedentes de fuentes externas y clientes), distribuidas como un feed RSS 2.0. Su departamento de marketing se da cuenta de que las aplicaciones web que usan este feed no pueden actualmente averiguar exactamente qué libro se está reseñando. Para facilitarle la vida a todos, determinan que el departamento técnico necesita extender RSS 2.0 para incluir un nuevo elemento por entrada que proporcione el número ISBN-10 o ISBN-13 de la publicación a la que se refiere la entrada. Definen el nuevo elemento <isbn> de manera bastante simple con un nombre estándar y una URI de espacio de nombres:

JungleBooks 1.0:
http://example.com/junglebooks/rss/module/1.0/

Un fragmento de RSS que contiene esta extensión en la práctica podría ser algo similar a:

<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0"
   xmlns:content="http://purl.org/rss/1.0/modules/content/"
   xmlns:jungle="http://example.com/junglebooks/rss/module/1.0/">
<channel>
    <title>Jungle Books Customer Reviews</title>
    <link>http://example.com/junglebooks</link>
    <description>Many book reviews!</description>
    <pubDate>Fri, 26 Jun 2009 19:15:10 GMT</pubDate>
    <jungle:dayPopular>
        http://example.com/junglebooks/book/938
    </jungle:dayPopular>
    <item>
        <title>Review Of Flatland: A Romance of Many Dimensions</title>
        <link>http://example.com/junglebooks/review/987</link>
        <author>Confused Physics Student</author>
        <content:encoded>
        A romantic square?!
        </content:encoded>
        <pubDate>Thu, 25 Jun 2009 20:03:28 -0700</pubDate>
        <jungle:isbn>048627263X</jungle:isbn>
    </item>
</channel>
</rss>

Implementar este nuevo elemento ISBN como una simple extensión a nivel de entrada requeriría la siguiente clase (usando su propio espacio de nombres de clase fuera de Zend).

class My_FeedReader_Extension_JungleBooks_Entry
    extends Zend_Feed_Reader_Extension_EntryAbstract
{
    public function getIsbn()
    {
        if (isset($this->_data['isbn'])) {
            return $this->_data['isbn'];
        }
        $isbn = $this->_xpath->evaluate(
            'string(' . $this->getXpathPrefix() . '/jungle:isbn)'
        );
        if (!$isbn) {
            $isbn = null;
        }
        $this->_data['isbn'] = $isbn;
        return $this->_data['isbn'];
    }

    protected function _registerNamespaces()
    {
        $this->_xpath->registerNamespace(
            'jungle', 'http://example.com/junglebooks/rss/module/1.0/'
        );
    }
}

Esta extensión es bastante fácil de seguir. Crea un nuevo método getIsbn() que ejecuta una consulta XPath sobre la entrada actual para extraer el número ISBN incluido en el elemento <jungle:isbn>. Opcionalmente puede almacenarlo en la caché interna no persistente (no es necesario seguir consultando el DOM si se vuelve a llamar en la misma entrada). El valor se devuelve a quien lo llama. Al final tenemos un método protegido (es abstracto, por lo que debe existir) que registra el espacio de nombres de Jungle Books para su módulo RSS personalizado. Aunque lo llamemos un módulo RSS, no hay nada que impida que el mismo elemento se use en feeds Atom, y todas las Extensiones que usan el prefijo proporcionado por getXpathPrefix() son en realidad neutrales y funcionan tanto en feeds RSS como Atom sin código adicional.

Dado que esta Extensión se almacena fuera de Zend Framework, deberá registrar la ruta de prefijo para sus Extensiones de modo que Zend_Loader_PluginLoader pueda encontrarlas. Después de eso, simplemente hay que registrar la Extensión, si aún no está cargada, y usarla en la práctica.

if(!Zend_Feed_Reader::isRegistered('JungleBooks')) {
    Zend_Feed_Reader::addPrefixPath(
        'My_FeedReader_Extension', '/path/to/My/FeedReader/Extension'
    );
    Zend_Feed_Reader::registerExtension('JungleBooks');
}
$feed = Zend_Feed_Reader::import('http://example.com/junglebooks/rss');

// ISBN for whatever book the first entry in the feed was concerned with
$firstIsbn = $feed->current()->getIsbn();

Escribir una Extensión a nivel de feed no es muy diferente. El feed de ejemplo anterior incluía un elemento <jungle:dayPopular> sin mencionar, que Jungle Books ha añadido a su estándar para incluir un enlace al libro más popular del día (en términos de tráfico de visitantes). Aquí hay una Extensión que añade un método getDaysPopularBookLink() a la API a nivel de feed.

class My_FeedReader_Extension_JungleBooks_Feed
    extends Zend_Feed_Reader_Extension_FeedAbstract
{
    public function getDaysPopularBookLink()
    {
        if (isset($this->_data['dayPopular'])) {
            return $this->_data['dayPopular'];
        }
        $dayPopular = $this->_xpath->evaluate(
            'string(' . $this->getXpathPrefix() . '/jungle:dayPopular)'
        );
        if (!$dayPopular) {
            $dayPopular = null;
        }
        $this->_data['dayPopular'] = $dayPopular;
        return $this->_data['dayPopular'];
    }

    protected function _registerNamespaces()
    {
        $this->_xpath->registerNamespace(
            'jungle', 'http://example.com/junglebooks/rss/module/1.0/'
        );
    }
}

Repitamos el último ejemplo usando una Extensión personalizada para mostrar el método en uso.

if(!Zend_Feed_Reader::isRegistered('JungleBooks')) {
    Zend_Feed_Reader::addPrefixPath(
        'My_FeedReader_Extension', '/path/to/My/FeedReader/Extension'
    );
    Zend_Feed_Reader::registerExtension('JungleBooks');
}
$feed = Zend_Feed_Reader::import('http://example.com/junglebooks/rss');

// URI to the information page of the day's most popular book with visitors
$daysPopularBookLink = $feed->getDaysPopularBookLink();

// ISBN for whatever book the first entry in the feed was concerned with
$firstIsbn = $feed->current()->getIsbn();

Repasando estos ejemplos, notará que no registramos las Extensiones de feed y de entrada por separado. Las Extensiones dentro del mismo estándar pueden incluir o no tanto una clase de feed como de entrada, por lo que Zend_Feed_Reader solo requiere que registre el nombre general, por ejemplo, JungleBooks, DublinCore, Slash. Internamente, puede comprobar en qué nivel existen las Extensiones y cargarlas si se encuentran. En nuestro caso, tenemos un conjunto completo de Extensiones: JungleBooks_Feed y JungleBooks_Entry.