Continuo dándole caña a Symfony. Ahora toca el controlador.

¿Qué hace el controlador? Pues lo siguiente:

  • El controlador frontal es el único punto de entrada a la aplicación. Carga la configuración y determina la acción a ejecutarse.
  • Las acciones contienen la lógica de la aplicación. Verifican la integridad de las peticiones y preparan los datos requeridos por la capa de presentación.
  • Los objetos request, response y session dan acceso a los parámetros de la petición, las cabeceras de las respuestas y a los datos persistentes del usuario. Se utilizan muy a menudo en la capa del controlador.
  • Los filtros son trozos de código ejecutados para cada petición, antes o después de una acción. Por ejemplo, los filtros de seguridad y validación son comúnmente utilizados en aplicaciones web. Puedes extender el framework creando tus propios filtros.

EL CONTROLADOR FRONTAL

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

La URL de arriba es un ejemplo de que tarea realiza el controlador frontal. En este caso nuestro controlador frontal es el archivo index.php que se encargará de ejecutar miAccion de mimodulo y generar la página que mostrará al usuario.

EL TRABAJO DEL CONTROLADOR EN DETALLE

Estas son las tareas que ejecuta el controlador antes de que se muestre la página al usuario:

  1. Carga la clase de configuración del proyecto y las librerías de Symfony.
  2. Crea la configuración de la aplicación y el contexto de Symfony.
  3. Carga e inicializa las clases del núcleo del framework.
  4. Carga la configuración.
  5. Decodifica la URL de la petición para determinar la acción a ejecutar y los parámetros de la petición.
  6. Si la acción no existe, redireccionará a la acción del error 404.
  7. Activa los filtros (por ejemplo, si la petición necesita autenticación).
  8. Ejecuta los filtros, primera pasada.
  9. Ejecuta la acción y produce la vista.
  10. Ejecuta los filtros, segunda pasada.
  11. Muestra la respuesta.

index.php, EL CONTROLADOR FRONTAL POR DEFECTO

Este archivo se encuentra en la carpeta /web del proyecto y contiene el siguiente código:

[codesyntax lang=»php»]

<?php    

  require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

  $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
  sfContext::createInstance($configuration)-&gt;dispatch();

[/codesyntax]

Lo que hace es simple, carga el archivo de la clase de configuración, llama a la clase y despacha la petición usando el método dispatch() de la clase sfContext.

Podemos crear otros controladores frontales simplemente copiando el archivo y modificando el segundo parámetro del método getApplicationConfiguration().

Para ello copiaremos el código del archivo index.php a frontend_staging.php y sustituiremos el segundo parámetro del método getApplicationConfiguration() que en vez de ser prod (de producción), será staging. Utilizaremos este controlador para que el cliente pueda probar la aplicación antes de ponerla en producción.

Una vez hecho esto modificaremos el archivo app.yml en la carpeta /config de la aplicación (en este caso sería /frontend/config) como sigue:

[codesyntax lang=»php»]

staging:
  mail:
    webmaster: falso@misitio.com
    contacto: falso@misitio.com
all:
  mail:
    webmaster: webmaster@misitio.com
    contacto: contacto@mysite.com

[/codesyntax]

Ahora solo tienes que escribir la siguiente URL para ver como se comporta el nuevo controlador frontal:

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

LAS ACCIONES

[codesyntax lang=»php»]

<?php

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

[/codesyntax]

Las acciones son métodos de una clase que hereda de sfActions y se encuentran agrupadas por módulos. Esta clase se encuentra en el archivo actions.class.php en la carpeta /actions del módulo.

Para añadir más acciones sólo es necesario añadir más métodos execute al objeto sfActions:

[codesyntax lang=»php»]

<?php

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

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

[/codesyntax]

Y para ver en el navegador estas acciones sólo habría que escribir:

Para executeIndex() -> http://localhost/frontend_dev.php/mimodulo/index
Para executeListar() -> http://localhost/frontend_dev.php/mimodulo/listar

 

SEPARANDO LAS ACCIONES EN ARCHIVOS DIFERENTES

Para crear acciones de un mismo módulo en archivos diferentes se debe crear una clase que extienda sfAction (en vez de sfActions que utilizabamos anteriormente), en un archivo llamado nombreAccionAction.class.php y el nombre del método en cada archivo será, simplemente, execute.

[codesyntax lang=»php» title=»Archivo de una sola acción, en frontend/modules/mimodulo/actions/indexAction.class.php»]

<?php

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

[/codesyntax]

[codesyntax lang=»php» title=»Archivo de una sola acción, en frontend/modules/mimodulo/actions/listAction.class.php»]

<?php

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

[/codesyntax]

 

OBTENIENDO INFORMACIÓN DE LAS ACCIONES

sfActions proporciona acceso a sfContext::createInstance() a través de getContext() que devuelve un objeto que guarda una referencia de todos los objetos del núcleo de symfony relacionados con la petición dada:

[codesyntax lang=»php» title=»Métodos comunes de sfActions»]

<?php

class mimoduloActions extends sfActions
{
  public function executeIndex($peticion)
  {
    // Obteniendo parametros de la petición
    $password      = $peticion->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.
  }
}

[/codesyntax]

  • sfController: El objeto controlador (->getController())
  • sfRequest: El objeto de la petición (->getRequest())
  • sfResponse: El objeto de la respuesta (->getResponse())
  • sfUser: El objeto de la sesión del usuario (->getUser())
  • sfDatabaseConnection: La conexión a la base de datos (->getDatabaseConnection())
  • sfLogger: El objeto para los logs (->getLogger())
  • sfI18N: El objeto de internacionalización (->getI18N())

Se puede llamar al método sfContext::getInstance() desde cualquier parte del código.

 

TERMINACIÓN DE LAS ACCIONES

Normalmente cuando finalizamos un método o función y queremos devolver algún dato utilizamos la palabra return seguida de alguna variable u objeto. En Symfony es más o menos igual, me explico:

Symfony, una vez a procesado la acción, enviará los datos a la plantilla para que el usuario pueda visualizarlo. Por defecto, Symfony siempre utiliza return sfView::SUCCESS para buscar la plantilla que debe mostrar los datos:

[codesyntax lang=»php» title=»Acciones que llaman a las plantillas indexSuccess.php y listarSuccess.php»]

<?php

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

public function executeListar()
{
}

[/codesyntax]

Al ser sfView::SUCCESS la vista por defecto, si no se indica en la acción, Symfony buscará una plantilla llamada nombreacciónSuccess.php.

En el caso de que quisieramos mostrar al usuario otra plantilla en caso de error haríamos lo siguiente:

[codesyntax lang=»php»]

<?php

return sfView::ERROR;

[/codesyntax]

Esto buscará una plantilla llamada nombreacciónError.php. Si queremos mostrar una vista personalizada:

[codesyntax lang=»php»]

<?php

return 'MiResultado';

[/codesyntax]

En ete caso, Symfony buscará una plantilla llamada nombreacciónMiResultado.php (ojo a las mayusculas ya que Symfony es case sensitive)

Si no se quiere utilizar ninguna vista:

[codesyntax lang=»php»]

<?php

return sfView::NONE;

[/codesyntax]

En el caso de que la acción vaya a ser utilizada por Ajax, podemos devolver los datos sin necesidad de pasar por la vista con el método renderText():

[codesyntax lang=»php»]

<?php

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>");
}

[/codesyntax]

En algunos casos se necesita enviar sólo las cabeceras de la petición, como en el caso de J-SON:

[codesyntax lang=»php»]

<?php

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

  return sfView::HEADER_ONLY;
}

[/codesyntax]

Si se quiere utilizar una plantilla específica, se debe prescindir de la sentencia return y utilizar el método setTemplate():

[codesyntax lang=»php»]

<?php

$this->setTemplate('miPlantillaPersonalizada');

[/codesyntax]

 

SALTANDO A OTRA ACCIÓN

Hay dos formas de saltar a otra acción:

[codesyntax lang=»php»]

<?php

//Si la acción debe continuar en otro módulo
$this->forward('otroModulo', 'index');

//Si la acción debe redireccionar a otro módulo o una web externa
$this->redirect('otroModulo/index');
$this->redirect('http://www.google.com/');

[/codesyntax]

En el caso de que quisieramos redireccionar para mostrar un error 404:

[codesyntax lang=»php»]

<?php

public function executeVer($peticion)
{
  $articulo = ArticuloPeer::retrieveByPK($peticion->getParameter('id'));
  if (!$articulo)
  {
    $this->forward404();
  }
}

[/codesyntax]

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 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():

[codesyntax lang=»php»]

<?php

// Esta acción es equivalente a la mostrada en el Listado 6-11
public function executeVer($peticion)
{
  $articulo = ArticuloPeer::retrieveByPK($peticion->getParameter('id'));
  $this->forward404If(!$articulo);
}

// Esta acción también es equivalente
public function executeVer()
{
  $articulo = ArticuloPeer::retrieveByPK($peticion->getParameter('id'));
  $this->forward404Unless($articulo);
}

[/codesyntax]

 

REPITIENDO CÓDIGO PARA VARIAS ACCIONES DE UN MÓDULO

Si por alguna razón nuestras acciones siempre van a ejecutar el mismo código al inicio o al final del método, podemos evitar duplicar código creando los métodos postExecute() y preExecute(), pero además podemos añadir al archivo de la acción (con sfAction) o acciones (con sfActions) nuestros propios métodos siempre que no empiecen con execute y no sean métodos públicos (utilizando para ello las sentencias protected o private).

[codesyntax lang=»php»]

<?php

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($peticion)
  {
    // ...
  }

  public function executeListar($peticion)
  {
    // ...
    $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
    // ...
  }
}

[/codesyntax]