El tutorial Jobeet

15.2. Canales Atom

15.2.1. Canal de las últimas ofertas de trabajo

Soportar diferentes formatos es tan sencillo como crear diferentes plantillas. Si quieres crear un canal en formato Atom que incluya las últimas ofertas de trabajo publicadas, crea un plantilla llamada indexSuccess.atom.php:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jobeet</title>
  <subtitle>Latest Jobs</subtitle>
  <link href="" rel="self"/>
  <link href=""/>
  <updated></updated>
  <author><name>Jobeet</name></author>
  <id>Unique Id</id>

  <entry>
    <title>Job title</title>
    <link href="" />
    <id>Unique id</id>
    <updated></updated>
    <summary>Job description</summary>
    <author><name>Company</name></author>
  </entry>
</feed>

Symfony modifica el valor del Content-Type de la respuesta en función del formato utilizado y además, deshabilita el layout para cualquier formato que no sea HTML. En el caso del canal Atom, Symfony cambia el valor del Content-Type a application/atom+xml; charset=utf-8

A continuación, actualiza en el pie de página del layout de Jobeet el enlace al nuevo canal:

<!-- apps/frontend/templates/layout.php -->
<li class="feed">
  <a href="<?php echo url_for('@job?sf_format=atom') ?>">Full feed</a>
</li>

La URI interna del canal Atom es la misma que la del listado job pero con sf_format añadido en forma de variable.

No te olvides de incluir también la etiqueta <link> de HTML en la cabecera del layout para que los navegadores puedan descubrir automáticamente la presencia de los canales:

<!-- apps/frontend/templates/layout.php -->
<link rel="alternate" type="application/atom+xml" title="Latest Jobs" href="<?php echo url_for('@job?sf_format=atom', true) ?>" />

En este caso, el atributo href incluye la URL absoluta del canal Atom, porque se ha utilizado el segundo argumento del helper url_for().

Para crear el canal Atom, en primer lugar reemplaza la cabecera de la plantilla de Atom por el siguiente código:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<title>Jobeet</title>
<subtitle>Latest Jobs</subtitle>
<link href="<?php echo url_for('@job?sf_format=atom', true) ?>" rel="self"/>
<link href="<?php echo url_for('@homepage', true) ?>"/>
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', JobeetJobPeer::getLatestPost()->getCreatedAt('U')) ?></updated>
<author>
  <name>Jobeet</name>
</author>
<id><?php echo sha1(url_for('@job?sf_format=atom', true)) ?></id>

Si te fijas en el código anterior, verás que hemos utilizado la letra U como argumento del método getCreatedAt() para obtener la fecha en forma de timestamp. Si quieres obtener la fecha de la última oferta de trabajo, crea un método llamado getLatestPost():

// lib/model/JobeetJobPeer.php
class JobeetJobPeer extends BaseJobeetJobPeer
{
  static public function getLatestPost()
  {
    $criteria = new Criteria();
    self::addActiveJobsCriteria($criteria);

    return JobeetJobPeer::doSelectOne($criteria);
  }

  // ...
}

Una vez terminada la cabecera, el cuerpo del canal Atom se puede generar con el siguiente código:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<?php use_helper('Text') ?>
<?php foreach ($categories as $category): ?>
  <?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $job): ?>
    <entry>
      <title>
        <?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)
      </title>
      <link href="<?php echo url_for('job_show_user', $job, true) ?>" />
      <id><?php echo sha1($job->getId()) ?></id>
      <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getCreatedAt('U')) ?></updated>
      <summary type="xhtml">
       <div xmlns="http://www.w3.org/1999/xhtml">
         <?php if ($job->getLogo()): ?>
           <div>
             <a href="<?php echo $job->getUrl() ?>">
               <img src="http://<?php echo $sf_request->getHost().'/uploads/jobs/'.$job->getLogo() ?>"
                 alt="<?php echo $job->getCompany() ?> logo" />
             </a>
           </div>
         <?php endif; ?>

         <div>
           <?php echo simple_format_text($job->getDescription()) ?>
         </div>

         <h4>How to apply?</h4>

         <p><?php echo $job->getHowToApply() ?></p>
       </div>
      </summary>
      <author>
        <name><?php echo $job->getCompany() ?></name>
      </author>
    </entry>
  <?php endforeach; ?>
<?php endforeach; ?>

El método getHost() del objeto de la petición ($sf_request) devuelve el host o servidor actual, lo que resulta muy útil para crear el enlace absoluto de la imagen del logotipo de la empresa.

Canal Atom tal y como se muestra en el navegador

Figura 15.2 Canal Atom tal y como se muestra en el navegador

Nota Cuando desarrollas canales RSS o Atom, es mucho más fácil depurarlos si utilizas herramientas de la línea de comandos como curl o wget, ya que te permiten ver directamente el contenido real del canal.

15.2.2. Canal de las últimas ofertas de trabajo de una categoría

Uno de los objetivos de Jobeet es ayudar a la gente a encontrar puestos de trabajo muy específicos. Por tanto, es imprescindible que incluyamos canales en cada categoría.

En primer lugar, actualiza la ruta category para añadir el soporte de varios formatos:

# apps/frontend/config/routing.yml
category:
  url:     /category/:slug.:sf_format
  class:   sfPropelRoute
  param:   { module: category, action: show, sf_format: html }
  options: { model: JobeetCategory, type: object }
  requirements:
    sf_format: (?:html|atom)

Ahora la ruta category ya es capaz de reconocer los formatos html y atom. El siguiente paso consiste en actualizar en la plantilla los enlaces a los canales de cada categoría:

<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<div class="feed">
  <a href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom')) ?>">Feed</a>
</div>
<!-- apps/frontend/modules/category/templates/showSuccess.php -->
<div class="feed">
  <a href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom')) ?>">Feed</a>
</div>

Por último, crea una plantilla llamada showSuccess.atom.php. Como esta plantilla también incluye un listado de ofertas de trabajo, vamos a refactorizar el código que genera los elementos del canal Atom mediante un elemento parcial llamado _list.atom.php. Al igual que para el formato html, los elementos parciales son dependientes del formato:

<!-- apps/frontend/job/templates/_list.atom.php -->
<?php use_helper('Text') ?>

<?php foreach ($jobs as $job): ?>
  <entry>
    <title><?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)</title>
    <link href="<?php echo url_for('job_show_user', $job, true) ?>" />
    <id><?php echo sha1($job->getId()) ?></id>
    <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getCreatedAt('U')) ?></updated>
    <summary type="xhtml">
     <div xmlns="http://www.w3.org/1999/xhtml">
       <?php if ($job->getLogo()): ?>
         <div>
           <a href="<?php echo $job->getUrl() ?>">
             <img src="http://<?php echo $sf_request->getHost().'/uploads/jobs/'.$job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" />
           </a>
         </div>
       <?php endif; ?>

       <div>
         <?php echo simple_format_text($job->getDescription()) ?>
       </div>

       <h4>How to apply?</h4>

       <p><?php echo $job->getHowToApply() ?></p>
     </div>
    </summary>
    <author>
      <name><?php echo $job->getCompany() ?></name>
    </author>
  </entry>
<?php endforeach; ?>

Utilizando este elemento parcial _list.atom.php se puede simplificar mucho la plantilla del canal que hemos creado en la sección anterior y que muestra las últimas ofertas de trabajo de todo el sitio:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jobeet</title>
  <subtitle>Latest Jobs</subtitle>
  <link href="<?php echo url_for('@job?sf_format=atom', true) ?>" rel="self"/>
  <link href="<?php echo url_for('@homepage', true) ?>"/>
  <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', JobeetJobPeer::getLatestPost()->getCreatedAt('U')) ?></updated>
  <author>
    <name>Jobeet</name>
  </author>
  <id><?php echo sha1(url_for('@job?sf_format=atom', true)) ?></id>

<?php foreach ($categories as $category): ?>
  <?php include_partial('job/list', array('jobs' => $category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
<?php endforeach; ?>
</feed>

Por último, crea la plantilla showSuccess.atom.php haciendo uso del elemento parcial _list.atom.php:

<!-- apps/frontend/modules/category/templates/showSuccess.atom.php -->
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jobeet (<?php echo $category ?>)</title>
  <subtitle>Latest Jobs</subtitle>
  <link href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom'), true) ?>" rel="self" />
  <link href="<?php echo url_for('category', array('sf_subject' => $category), true) ?>" />
  <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $category->getLatestPost()->getCreatedAt('U')) ?></updated>
  <author>
    <name>Jobeet</name>
  </author>
  <id><?php echo sha1(url_for('category', array('sf_subject' => $category), true)) ?></id>

  <?php include_partial('job/list', array('jobs' => $pager->getResults())) ?>
</feed>

Al igual que para el canal principal del sitio, tenemos que calcular la fecha de la última oferta de trabajo de cada categoría:

// lib/model/JobeetCategory.php
class JobeetCategory extends BaseJobeetCategory
{
  public function getLatestPost()
  {
    $jobs = $this->getActiveJobs(1);

    return $jobs[0];
  }

  // ...
}
Canal Atom de cada categoría

Figura 15.3 Canal Atom de cada categoría