El mecanismo de seguridad puede ser entendido como un filtro, por el que debe pasar cada petición antes de ejecutar la acción. Según las comprobaciones realizadas en el filtro, se puede modificar el procesamiento de la petición --por ejemplo, cambiando la acción ejecutada (default/secure
en lugar de la acción solicitada en el caso del filtro de seguridad). Symfony extiende esta idea a clases de filtros. Se puede especificar cualquier número de clases de filtros a ser ejecutadas antes de que se procese la respuesta, y además hacerlo de forma sistemática para todas las peticiones. Se pueden entender los filtros como una forma de empaquetar cierto código de forma similar a preExecute()
y postExecute()
, pero a un nivel superior (para toda una aplicación en lugar de para todo un módulo).
6.7.1. La Cadena de Filtros
Symfony de hecho procesa cada petición como una cadena de filtros ejecutados de forma sucesiva. Cuando el framework recibe una petición, se ejecuta el primer filtro (que siempre es sfRenderingFilter
). En algún punto, llama al siguiente filtro en la cadena, luego el siguiente, y asi sucesivamente. Cuando se ejecuta el último filtro (que siempre es sfExecutionFilter
), los filtros anteriores pueden finalizar, y asi hasta el filtro de sfRenderingFilter
. La Figura 6-3 ilustra esta idea con un diagrama de secuencias, utilizando una cadena de filtros simplificada (la cadena real tiene muchos más filtros).
Este proceso es la razón de la estructura de la clases de tipo filtro. Todas estas clases extienden la clase sfFilter
y contienen un método execute()
que espera un objeto de tipo $filterChain
como parámetro. En algún punto de este método, el filtro pasa al siguiente filtro en la cadena, llamando a $filterChain->execute()
. El listado 6-30 muestra un ejemplo. Por lo tanto, los filtros se dividen en dos partes:
- El código que se encuentra antes de la llamada a
$filterChain->execute()
se ejecuta antes de que se ejecute la acción. - El código que se encuentra después de la llamada a
$filterChain->execute()
se ejecuta después de la acción y antes de producir la vista.
Listado 6-30 - Estructura de la clase filtro
class miFiltro extends sfFilter
{
public function execute ($filterChain)
{
// Código que se ejecuta antes de la ejecución de la acción
...
// Ejecutar el siguiente filtro de la cadena
$filterChain->execute();
// Código que se ejecuta después de la ejecuciñon de la acción y antes de que se genere la vista
...
}
}
La cadena de filtros por defecto se define en el archivo de configurarcion de la aplicación filters.yml
, y su contenido se muestra en el listado 6-31. Este archivo lista los filtros que se ejecutan para cada petición.
Listado 6-31 - Cadena de filtros por defecto, en miaplicacion/config/filters.yml
rendering: ~
web_debug: ~
security: ~
# Normalmente los filtros propios se insertan aqui
cache: ~
common: ~
flash: ~
execution: ~
Estas declaraciones no tienen parámetros (el caracter tilde, ~
, significa null
en YAML), porque heredan los parámetros definidos en el núcleo de Symfony. En su núcleo, Symfony define las opciones class
y param
para cada uno de estos filtros. Por ejemplo, el listado 6-32 muestra los parámetros por defecto para el filtro rendering
.
Listado 6-32 - Parámetros por defecto del filtro sfRenderingFilter
, en $sf_symfony_data_dir/config/filters.yml
rendering:
class: sfRenderingFilter # Clase del filtro
param: # Parámetros del filtro
type: rendering
Si se deja el valor vacío (~
) en el archivo filters.yml
de la aplicación, Symfony aplica el filtro con las opciones por defecto definidas en su núcleo.
Se pueden personalizar la cadenas de filtros en varias formas:
- Desactivando algún filtro de la cadena agregando un parámetro
enabled: off
. Por ejemplo, para desactivar el filtro de depuración web (web_debug
), se añade:
web_debug:
enabled: off
- No se deben borrar las entradas del archivo
filters.yml
para desactivar un filtro ya que Symfony lanzará una excepción. - Se pueden añadir declaraciones propias en cualquier lugar de la cadena (normalmente después del filtro
security
) para agregar un filtro propio (como se verá en la próxima sección). En cualquier caso, el filtrorendering
debe ser siempre la primera entrada, y el filtroexecution
debe ser siempre la ultima entrada en la cadena de filtros. - Redefinir la clase y los parámetros por defecto del filtro por defecto (normalmente para modificar el sistema de seguridad y utilizar un filtro de seguridad propio).
Truco El parámetro enabled: off
funciona correctamente para desactivar los filtros propios, pero se pueden desactivar los filtros por defecto a través del archivo settings.myl
, modificando los valores de las opciones web_debug
, use_security
, cache
, y use_flash
. El motivo es que cada uno de los filtros por defecto posee un parámetro condition
que comprueba el valor de estas opciones.
6.7.2. Construyendo Tu Propio Filtro
Construir un filtro propio es bastante sencillo. Se debe crear una definición de una clase similar a la demostrada en el listado 6-30, y se coloca en uno de los directorios lib/
del proyecto para aprovechar la carga automática de clases.
Como una acción puede pasar el control o redireccionar hacia otra acción y en consecuencia relanzar toda la cadena de filtros, quizás sea necesario restringir la ejecución de los filtros propios a la primera acción de la petición. El método isFirstCall()
de la clase sfFilter
retorna un valor booleano con este propósito. Esta llamada solo tiene sentido antes de la ejecución de una acción.
Este concepto se puede entender fácilmente con un ejemplo. El listado 6-33 muestra un filtro utilizado para auto-loguear a los usuarios con una cookie MiSitioWeb
, que se supone que se crea en la acción login. Se trata de una forma rudimentaria pero que funciona para incluir la característica Recuérdame
de un formulario de login.
Listado 6-33 - Ejemplo de archivo de clase de filtro, en apps/miaplicacion/lib/rememberFilter.class.php
class rememberFilter extends sfFilter
{
public function execute($filterChain)
{
// Ejecutar este filtro solo una vez
if ($this->isFirstCall())
{
// Los filtros no tienen acceso directo a los objetos user y request.
// Se necesita el contexto para obtenerlos
$peticion = $this->getContext()->getRequest();
$usuario = $this->getContext()->getUser();
if ($peticion->getCookie('MiSitioWeb'))
{
// logueado
$usuario->setAuthenticated(true);
}
}
// Ejecutar el proximo filtro
$filterChain->execute();
}
}
En ocasiones, en lugar de continuar con la ejecución de la cadena de filtros, se necesita pasar el control a una acción específica al final de un filtro. sfFilter
no tiene un método forward()
, pero sfController
si, por lo que simplemente se puede llamar al siguiente método:
return $this->getContext()->getController()->forward('mimodulo', 'miAccion');
Nota La clase sfFilter
tiene un método initialize()
, ejecutado cuando se crea el objeto filtro. Se puede redefinir en el filtro propio si se necesita trabajar de forma personalizada con los parámetros de los filtros (definidos en filters.yml
, como se describe a continuación).
6.7.3. Activación de Filtros y Parámetros
Crear un filtro no es suficiente para activarlo. Se necesita agregar el filtro propio a la cadena, y para eso, se debe declar la clase del filtro en el archivo filters.yml
, localizado en el directorio config/
de la aplicación o del módulo, como se muestra en el listado 6-34.
Listado 6-34 - Ejemplo de archivo de activación de filtro, en apps/miaplicacion/config/filters.yml
rendering: ~
web_debug: ~
security: ~
remember: # Los filtros requieren un nombre único
class: rememberFilter
param:
cookie_name: MiSitioWeb
condition: %APP_ENABLE_REMEMBER_ME%
cache: ~
common: ~
flash: ~
execution: ~
Cuando se encuentra activo, el filtro se ejecuta en cada petición. El archivo de configuración de los filtros puede contener una o más definiciones de parámetros en la sección param
. La clase filtro puede obtener estos parámetros con el método getParameter()
. El listado 6-35 muestra como obtener los valores de los parámetros.
Listado 6-35 - Obteniendo el valor del parámetro, en apps/miaplicacion/lib/rememberFilter.class.php
class rememberFilter extends sfFilter
{
public function execute($filterChain)
{
...
if ($request->getCookie($this->getParameter('cookie_name')))
...
}
}
El parámetro condition
se comprueba en la cadena de filtros para ver si el filtro debe ser ejecutado. Por lo que las declaraciones del filtro propio puede basarse en la configuración de la aplicación, como muestra el listado 6-34. El filtro remeber
se ejecuta solo si el archivo app.yml
incluye lo siguiente:
all:
enable_remember_me: on
6.7.4. Filtros de Ejemplo
Los filtros son útiles para repetir cierto código en todas las acciones. Por ejemplo, si se utiliza un sistema remoto de estadísticas, puede ser necesario añadir un trozo de código que realice una llamada a un script de las estadísticas en cada página. Este código se puede colocar en el layout global, pero entonces estaría activo para toda la aplicación. Otra forma es colocarlo en un filtro, como se muestra el listado 6-36, y activarlo en cada módulo.
Listado 6-36 - Filtro para el sistema de estadísticas de Google Analytics
class sfGoogleAnalyticsFilter extends sfFilter
{
public function execute($filterChain)
{
// No se hace nada antes de la acción
$filterChain->execute();
// Decorar la respuesta con el código de Google Analytics
$codigoGoogle = '
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct="UA-'.$this->getParameter('google_id').'";urchinTracker();
</script>';
$respuesta = $this->getContext()->getResponse();
$respuesta->setContent(str_ireplace('</body>', $codigoGoogle.'</body>',$respuesta->getContent()));
}
}
No obstante, este filtro no es perfecto, ya que no se debería añadir el código de Google si la respuesta no es de tipo HTML.
Otro ejemplo es el de un filtro que cambia las peticiones a SSL si no lo son, para hacer más segura la comunicación, como muestra el Listado 6-37.
Listado 6-37 - Filtro de comunicación segura
class sfSecureFilter extends sfFilter
{
public function execute($filterChain)
{
$contexto = $this->getContext();
$peticion = $context->getRequest();
if (!$peticion->isSecure())
{
$urlSegura = str_replace('http', 'https', $peticion->getUri());
return $contexto->getController()->redirect($urlSegura);
// No se continúa con la cadena de filtros
}
else
{
// La petición ya es segura, asi que podemos continuar
$filterChain->execute();
}
}
}
Los filtros se utilizan mucho en los plugins, porque permiten extender las características de una aplicación de forma global. El Capítulo 17 incluye más información sobre los plugins, y el wiki del proyecto Symfony (http://trac.symfony-project.org/) también tiene más ejemplos de filtros.