Las acciones son el corazón de la aplicación, puesto que contienen toda la lógica de la aplicación. Las acciones realizan llamadas al modelo y definen variables para la vista. Cuando se realiza una petición web en una aplicación Symfony, la URL define una acción y los parámetros de la petición.

6.2.1. La clase de la acción

Las acciones son métodos con el nombre executeNombreAccion de una clase llamada nombreModuloActions que hereda de la clase sfActions y se encuentran agrupadas por módulos. La clase que representa las acciones de un módulo se encuentra en el archivo actions.class.php, en el directorio actions/ del módulo.

El listado 6-3 muestra un ejemplo de un archivo actions.class.php con una única acción index para todo el módulo mimodulo.

Listado 6-3 - Ejemplo de la clase de la acción, en app/frontend/modules/mimodulo/actions/actions.class.php

class mimoduloActions extends sfActions
{
  public function executeIndex($request)
  {
    // ...
  }
}

Nota Aunque en PHP no se distinguen las mayúsculas y minúsculas de los nombres de los métodos, Symfony si los distingue. Así que se debe tener presente que los métodos de las acciones deben comenzar con execute en minúscula, seguido por el nombre exacto de la acción con la primera letra en mayúscula.

Para ejecutar un acción, se debe llamar al script del controlador frontal con el nombre del módulo y de la acción como parámetros. Por defecto, se añade nombre_modulo/nombre_accion al script. Esto significa que la acción del listado 6-3 se puede ejecutar llamándola con la siguiente URL:

http://localhost/index.php/mimodulo/index

Añadir más acciones simplemente significa agregar más métodos execute al objeto sfActions, como se muestra en el listado 6-4.

Listado 6-4 - Clase con dos acciones, en frontend/modules/mimodulo/actions/actions.class.php

class mimoduloActions extends sfActions
{
  public function executeIndex($request)
  {
    // ...
  }

  public function executeListar($request)
  {
    // ...
  }
}

Si el tamaño de la clase de la acción crece demasiado, probablemente tendrás que refactorizar la clase para mover algo de codigo a la capa del modelo. El código de las acciones debería ser muy corto (no mas que una pocas líneas), y toda la lógica del negocio debería encontrarse en el modelo.

Aun así, el número de acciones en un módulo puede llegar a ser tan importante que sea necesario dividirlas en 2 módulos.

6.2.2. Sintaxis alternativa para las clases de las Acciones

Se puede utilizar una sintaxis alternativa para distribuir las acciones en archivos separados, un archivo por acción. En este caso, cada clase acción extiende sfAction (en lugar de sfActions) y su nombre es nombreAccionAction. El nombre del método es simplemente execute. El nombre del archivo es el mismo que el de la clase. Esto significa que el equivalente del Listado 6-4 puede ser escrito en dos archivos mostrados en los listados 6-5 y 6-6.

Listado 6-5 - Archivo de una sola acción, en frontend/modules/mimodulo/actions/indexAction.class.php

class indexAction extends sfAction
{
  public function execute($request)
  {
    // ...
  }
}

Listado 6-6 - Archivo de una sola acción, en frontend/modules/mimodulo/actions/listAction.class.php

class listarAction extends sfAction
{
  public function execute($request)
  {
    // ...
  }
}

6.2.3. Obteniendo Información en las Acciones

Las clases de las acciones ofrecen un método para acceder a la información relacionada con el controlador y los objetos del núcleo de Symfony. El listado 6-7 muestra como utilizarlos.

Listado 6-7 - Métodos comunes de sfActions

class mimoduloActions extends sfActions
{
  public function executeIndex(sfWebRequest $request)
  {
    // Obteniendo parametros de la petición
    $password      = $request->getParameter('password');

    // Obteniendo información del controlador
    $nombreModulo  = $this->getModuleName();
    $nombreAccion  = $this->getActionName();

    // Obteniendo objetos del núcleo del framework
    $sesionUsuario = $this->getUser();
    $respuesta     = $this->getResponse();
    $controlador   = $this->getController();
    $contexto      = $this->getContext();

    // Creando variables de la acción para pasar información a la plantilla
    $this->setVar('parametro', 'valor');
    $this->parametro = 'valor';           // Versión corta.
  }
}

6.2.4. Terminación de las Acciones

Existen varias alternativas posibles cuando se termina la ejecución de una acción. El valor retornado por el método de la acción determina como será producida la vista. Para especificar la plantilla que se utiliza al mostrar el resultado de la acción, se emplean las constantes de la clase sfView.

Si existe una vista por defecto que se debe llamar (este es el caso más común), la acción debería terminar de la siguiente manera:

return sfView::SUCCESS;

Symfony buscará entonces una plantilla llamada nombreAccionSuccess.php. Este comportamiento se ha definido como el comportamiento por defecto, por lo que si omites la sentencia return en el método de la acción, Symfony también buscará una plantilla llamada nombreAccionSuccess.php. Las acciones vacías también siguen este comportamiento. El listado 6-8 muestra un ejemplo de terminaciones exitosas de acciones.

Listado 6-8 - Acciones que llaman a las plantillas indexSuccess.php y listarSuccess.php

public function executeIndex()
{
  return sfView::SUCCESS;
}

public function executeListar()
{
}

Si existe una vista de error que se debe llamar, la acción deberá terminar de la siguiente manera:

return sfView::ERROR;

Symonfy entonces buscará un plantilla llamada nombreAccionError.php.

Para utilizar una vista personalizada, se debe utilizar el siguiente valor de retorno:

return 'MiResultado';

Symfony entonces buscará una plantilla llamada nombreAccionMiResultado.php.

Si no se utiliza ninguna vista --por ejemplo, en el caso de una acción ejecutada en un archivo de lotes-- la acción debe terminar de la siguiente forma:

return sfView::NONE;

En este caso, no se ejecuta ninguna plantilla. De esta forma, se evita por completo la capa de vista y se establece directamente el código HTML producido por la acción. Como muestra el Listado 6-9, Symfony provee un método renderText() específico para este caso. Este método puede ser útil cuando se necesita una respuesta muy rápida en una acción, como por ejemplo para las interacciones creadas con Ajax, como se verá en el Capítulo 11.

Listado 6-9 - Evitando la vista mediante una respuesta directa y un valor de retorno sfView::NONE

public function executeIndex()
{
  $this->getResponse()->setContent("<html><body>¡Hola Mundo!</body></html>");

  return sfView::NONE;
}

// Es equivalente a
public function executeIndex()
{
  return $this->renderText("<html><body>¡Hola Mundo!</body></html>");
}

En algunos casos, se necesita una respuesta vacía pero con algunas cabeceras definidas (sobre todo la cabecera X-JSON). Para conseguirlo, se definen las cabeceras con el objeto sfResponse, que se ve en el próximo capítulo, y se devuelve como valor de retorno la constante sfView::HEADER_ONLY, como muestra el Listado 6-10.

Listado 6-10 - Evitando la producción de la vista y enviando solo cabeceras

public function executeActualizar()
{
  $salida = '<"titulo","Mi carta sencilla"],["nombre","Sr. Pérez">';
  $this->getResponse()->setHttpHeader("X-JSON", '('.$salida.')');

  return sfView::HEADER_ONLY;
}

Si la acción debe ser producida por una plantilla específica, se debe prescindir de la sentencia return y se debe utilizar el método setTemplate() en su lugar.

public function executeIndex()
{
  $this->setTemplate('miPlantillaPersonalizada');
}

El código anterior hace que Symfony busque un archivo llamado miPlantillaPersonalizada.php en vez del archivo estándar indexSuccess.php.

6.2.5. Saltando a Otra Acción

En algunos casos, la ejecución de un acción termina solicitando la ejecución de otra acción. Por ejemplo, una acción que maneja el envío de un formulario en una solicitud POST normalmente redirecciona a otra acción después de actualizar la base de datos.

La clase de la acción provee dos métodos para ejecutar otra acción:

  • Si la acción pasa la llamada hacia otra acción forward):
$this->forward('otroModulo', 'index');
  • Si la acción produce un redireccionamiento web redirect):
$this->redirect('otroModulo/index');
$this->redirect('http://www.google.com/');

Nota El código que se encuentra después de una llamada a los métodos forward o redirect en una acción nunca se ejecuta. Se puede considerar que estas llamadas son equivalentes a la sentencia return. Estos métodos lanzan una excepción sfStopException para detener la ejecución de la acción; esta excepción es interceptada más adelante por Symfony y simplemente se ignora.

La elección entre redirect y forward es a veces engañosa. Para elegir la mejor solución, ten en cuenta que un forward es una llamada interna a la aplicación y transparente para el usuario. En lo que concierne al usuario, la URL mostrada es la misma que la solicitada. Por el contrario, un redirect resulta en un mensaje al navegador del usuario, involucrando una nueva petición por parte del mismo y un cambio en la URL final resultante.

Si la acción es llamada desde un formulario enviado con method="post", deberías siempre realizar un redirect. La principal ventaja es que si el usuario recarga la página resultante, el formulario no será enviado nuevamente; además, el botón de retroceder funciona como se espera, ya que muestra el formulario y no una alerta preguntando al usuario si desea reenviar una petición POST.

Existe un tipo especial de forward que se utiliza comúnmente. El método forward404() redirecciona a una acción de Página no encontrada. Este método se utiliza normalmente cuando un parámetro necesario para la ejecución de la acción no está presente en la petición (por tanto detectando una URL mal escrita). El Listado 6-11 muestra un ejemplo de una acción mostrar que espera un parámetro llamado id.

Listado 6-11 - Uso del método forward404()

public function executeVer(sfWebRequest $request)
{
  // Doctrine
  $articulo = Doctrine::getTable('Articulo')->find($request->getParameter('id'));

  // Propel
  $articulo = ArticuloPeer::retrieveByPK($request->getParameter('id'));

  if (!$articulo)
  {
    $this->forward404();
  }
}

Nota Si estás buscando la acción y la plantilla del error 404, las puedes encontrar en el directorio $sf_symfony_lib_dir/controller/default/. Se puede personalizar esta página agregado un módulo default a la aplicación, sobrescribiendo el del framework, y definiendo una acción error404 y una plantilla error404Success dentro del nuevo módulo. Otro método alternativo es el de establecer las constantes error_404_module y error_404_action en el archivo settings.yml para utilizar una acción existente.

La experiencia muestra que, la mayoría de las veces, una acción hace un redirect o un forward después de probar algo, como en el listado 6-12. Por este motivo, la clase sfActions tiene algunos métodos más, llamados forwardIf(), forwardUnless(), forward404If(), forward404Unless(), redirectIf() y redirectUnless(). Estos métodos simplemente requieren un parámetro que representa la condición cuyo resultado se emplea para ejecutar el método. El método se ejecuta si el resultado de la condición es true y el método es de tipo xxxIf() o si el resultado de la condición es false y el método es de tipo xxxUnless(), como se muestra en el listado 6-12.

Listado 6-12 - Uso del método forward404If()

// Esta acción es equivalente a la mostrada en el Listado 6-11
public function executeVer(sfWebRequest $request)
{
  $articulo = Doctrine::getTable('Articulo')->find($request->getParameter('id'));
  $this->forward404If(!$articulo);
}

// Esta acción también es equivalente
public function executeVer(sfWebRequest $request)
{
  $articulo = Doctrine::getTable('Articulo')->find($peticion->getParameter('id'));
  $this->forward404Unless($articulo);
}

El uso de estos métodos permite mantener el código de las acciones muy corto y también lo hacen más fácil de leer.

Nota Cuando la acción llama al método forward404() o alguno de sus similares, Symfony lanza una excepción sfError404Exception que maneja la respuesta al error 404. Esto significa que si se quiere mostrar un mensaje de error de tipo 404 desde cualquier parte del código desde donde no se quiere acceder al controlador, se puede lanzar una excepción similar.

6.2.6. Repitiendo Código para varias Acciones de un Módulo

La convención en el nombre de las acciones executeNombreAccion() (en el caso de una clase de tipo sfActions) o execute() (en el caso de una clase sfAction) garantiza que Symfony encontrará el método de la acción. Además, permite crear métodos propios que no serán considerados como acciones, siempre que su nombre no empiece con execute.

Existe otra convención útil cuando se necesita ejecutar repetidamente en cada acción una serie de sentencias antes de ejecutar la propia acción. Esas sentencias comunes se pueden colocar en el método preExecute() de la clase de la acción. De forma análoga, se pueden definir sentencias que se ejecuten después de cada acción añadiéndolas al método postExecute(). La sintaxis de estos métodos se muestra en el Listado 6-13.

Listado 6-13 - Usando los métodos preExecute(), postExecute() y otros métodos propios en la clase de la acción

class mimoduloActions extends sfActions
{
  public function preExecute()
  {
    // El código insertado aquí se ejecuta al principio de cada llamada a una acción
    // ...
  }

  public function executeIndex($request)
  {
    // ...
  }

  public function executeListar($request)
  {
    // ...
    $this->miPropioMetodo();  // Se puede acceder a cualquier método de la clase acción
  }

  public function postExecute()
  {
    // El código insertado aquí se ejecuta al final de cada llamada a la acción
    ...
  }

  protected function miPropioMetodo()
  {
    // Se pueden crear métodos propios, siempre que su nombre no comience por "execute"
    // En ese case, es mejor declarar los métodos como protected o private
    // ...
  }
}

Nota Como los métodos preExecute y postExecute se ejecutan para todas las acciones del módulo, asegúrate de que realmente necesitas ejecutar ese código para todas ellas.