El tutorial Jobeet

5.6. La clase para las rutas basadas en objetos

La URI interna de la página de una oferta de trabajo es muy larga y bastante aburrida de escribir (url_for('job/show?id='.$job->getId().'&company='.$job->getCompany().'&location='.$job->getLocation().'&position='.$job->getPosition())). Como se ha comentado en la sección anterior, es posible modificar la clase que utiliza cada ruta. En el caso de la ruta llamada job_show_user, se va a emplear la clase sfPropelRoute, ya que es una clase optimizada para las rutas que representan objetos Propel o colecciones de objetos Propel:

job_show_user:
  url:     /job/:company/:location/:id/:position
  class:   sfPropelRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: show }
  requirements:
    id: \d+
    sf_method: [get]

La opción options establece el comportamiento de la ruta. La opción model define la clase del modelo de Propel relacionada con la ruta (en este caso, JobeetJob) y la opción type indica que esta ruta está relacionada con un solo objeto. Si la ruta representara una colección de objetos, se debería utilizar el valor list en esta opción type.

Como la ruta job_show_user ahora está relacionada con JobeetJob, se puede simplificar la llamanda al helper url_for() de la siguiente manera:

url_for(array('sf_route' => 'job_show_user', 'sf_subject' => $job))

Incluso se puede simplificar todavía más:

url_for('job_show_user', $job)

Nota La primera forma es útil cuando tienes que pasar más argumentos aparte del objeto.

Todo esto es posible porque todas las variables de la ruta tienen un método para acceder a su valor dentro de la clase JobeetJob. La variable company por ejemplo se sustituye por el valor devuelto por el método getCompany().

Si observas el aspecto de las URL generadas, verás que todavía no son exactamente como queríamos:

http://jobeet.localhost/frontend_dev.php/job/Sensio+Labs/Paris%2C+France/1/Web+Developer

El siguiente paso consiste en preparar los valores de cada columna para que se muestren correctamente en la URL, proceso que se conoce con el nombre de slugify, por lo que debemos sustituir todos los caracteres que no sean ASCII por un guión medio -. Para ello, abre el archivo JobeetJob y añade los siguientes métodos en la clase:

// lib/model/JobeetJob.php
public function getCompanySlug()
{
  return Jobeet::slugify($this->getCompany());
}

public function getPositionSlug()
{
  return Jobeet::slugify($this->getPosition());
}

public function getLocationSlug()
{
  return Jobeet::slugify($this->getLocation());
}

A continuación, crea un archivo llamado lib/Jobeet.class.php y añade el método slugify a la nueva clase:

// lib/Jobeet.class.php
class Jobeet
{
  static public function slugify($text)
  {
    // replace all non letters or digits by -
    $text = preg_replace('/\W+/', '-', $text);

    // trim and lowercase
    $text = strtolower(trim($text, '-'));

    return $text;
  }
}

Nota Para optimizar un poco el sitio disponible, este tutorial no muestra la instrucción <?php en los trozos de código que sólo incluyen PHP. Obviamente no te olvides de incluir esa instrucción cada vez que crees un archivo PHP.

Los cambios anteriores han creado tres métodos accesores virtuales: getCompanySlug(), getPositionSlug() y getLocationSlug(). Los tres métodos devuelven el valor original de la columna de datos después de aplicarle el método slugify(). Por tanto, ahora la ruta job_show_user también puede hacer uso de estos métodos accesores para reemplazar los valores originales de cada columna por sus valores virtuales:

job_show_user:
  url:     /job/:company_slug/:location_slug/:id/:position_slug
  class:   sfPropelRoute
  options: { model: JobeetJob, type: object }
  param:   { module: job, action: show }
  requirements:
    id: \d+
    sf_method: [get]

Como acabamos de añadir una nueva clase, antes de refrescar la portada de Jobeet es necesario que borres la cache de Symfony:

$ php symfony cc

Si vuelves a acceder a la portada de Jobeet, verás que las URL ahora sí que son tal y como las queríamos:

http://jobeet.localhost/frontend_dev.php/job/sensio-labs/paris-france/1/web-developer

Todo lo anterior es sólo parte de lo que son capaces las rutas de Symfony. Las rutas pueden generar una URL en función de un objeto, pero también pueden obtener el objeto relacionado con una URL. El objeto relacionado se puede obtener mediante el método getObject() del objeto de la ruta. Cuando procesa una petición, el sistema de enrutamiento guarda el objeto relacionado con la ruta para que lo utilices en las acciones. Por tanto, modifica el método executeShow() para obtener el objeto Jobeet mediante el objeto de la ruta:

class jobActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
    $this->job = $this->getRoute()->getObject();

    $this->forward404Unless($this->job);
  }

  // ...
}

Si tratas de obtener la oferta de trabajo relacionada con un id desconocido, verás una página de error 404, pero esta vez el mensaje ha cambiado:

Mensaje de error 404 cuando se utiliza sfPropelRoute

Figura 5.1 Mensaje de error 404 cuando se utiliza sfPropelRoute

El motivo es que la excepción del error 404 se ha lanzado automáticamente desde el método getRoute(). Por tanto, puedes simplificar todavía más el método executeShow:

class jobActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
    $this->job = $this->getRoute()->getObject();
  }

  // ...
}

Nota Si no quieres que la ruta muestre un error de tipo 404, establece la opción allow_empty a true en la definición de esa ruta.

Nota El objeto relacionado con la ruta no se carga de forma automática. Este objeto sólo se obtiene de la base de datos cuando se invoca el método getRoute().