Por defecto, los formularios sólo son válidos si todos los campos rellenados por el usuario disponen de un validador. De esta forma, Symfony se asegura que todos los campos tienen establecidas reglas de validación y también se asegura que no sea posible incluir información en campos que no están definidos en el formulario original.
Para comprender mejor la seguridad de los formularios, se muestra el ejemplo del listado 2-6 en el que se utiliza un objeto de tipo usuario.
Listado 2-6 - La clase Usuario
class Usuario
{
protected
$nombre = '',
$es_administrador = false;
public function setCampos($campos)
{
if (isset($campos['nombre']))
{
$this->nombre = $campos['nombre'];
}
if (isset($campos['es_administrador']))
{
$this->es_administrador = $campos['es_administrador'];
}
}
// ...
}
El objeto Usuario
incluye dos propiedades, el nombre del usuario (nombre
) y un valor booleano que indica si el usuario es de tipo administrador (es_administrador
). El método setCampos()
actualiza el valor de estas dos propiedades. El listado 2-7 muestra el formulario relacionado con la clase Usuario
y que permite modificar solamente la propiedad nombre
.
Listado 2-7 - Formulario Usuario
class UsuarioForm extends sfForm
{
public function configure()
{
$this->setWidgets(array('nombre' => new sfWidgetFormInputString()));
$this->widgetSchema->setNameFormat('usuario[%s]');
$this->setValidators(array('nombre' => new sfValidatorString()));
}
}
El listado 2-8 muestra el código de un módulo llamado usuario
que utiliza el formulario anterior para permitir al usuario cambiar el valor del campo nombre
.
Listado 2-8 - Código del módulo usuario
class usuarioActions extends sfActions
{
public function executeIndex($request)
{
$this->formulario = new UsuarioForm();
if ($request->isMethod('post'))
{
$this->formulario->bind($request->getParameter('usuario'));
if ($this->formulario->isValid())
{
$usuario = // obtener el objeto del usuario
$usuario->setCampos($this->formulario->getValues());
$this->redirect('...');
}
}
}
}
Si no se utiliza ninguna protección, el código de la aplicación es vulnerable ya que el usuario podría enviar el formulario con los campos nombre
y es_administrador
rellenos. Modificar el código HTML de los formularios para enviar cualquier información es muy sencillo gracias a herramientas como Firebug. Además, como el campo es_administrador
no tiene asociado ningún validador, su valor siempre es válido. Por lo tanto, sea cual sea su valor, el método setCampos()
no sólo actualiza la propiedad nombre
sino que también modifica la propiedad es_administrador
.
Si se prueba el código anterior con un formulario que incluya los campos nombre
y es_administrador
, se produce un error global y se muestra el mensaje de error "Extra field es_administrador
, tal y como se observa en la figura 2-8. El error se produce porque algunos de los campos enviados no tienen ningún validador asociado, ya que el campo es_administrador
no está definido en el formulario UsuarioForm
.
Todos los validadores utilizados hasta el momento generan errores asociados con sus propios campos. Por lo tanto, ¿de dónde proviene este error global? Cuando se emplea el método setValidators()
, Symfony crea un objeto de tipo sfValidatorSchema
que a su vez define una colección de validadores. Invocar el método setValidators()
es equivalente al siguiente código:
$this->setValidatorSchema(new sfValidatorSchema(array(
'email' => new sfValidatorEmail(),
'asunto' => new sfValidatorChoice(array('choices' => array_keys(self::$asuntos))),
'mensaje' => new sfValidatorString(array('min_length' => 4)),
)));
El objeto sfValidatorSchema
incluye dos reglas de validación activadas por defecto para proteger la colección de validadores. Estas reglas se pueden configurar con las opciones allow_extra_fields
y filter_extra_fields
.
La opción allow_extra_fields
, que vale false
por defecto, comprueba si todos los campos enviados disponen de un validador. En caso negativo, genera el error global "Extra field
Volviendo al formulario de contacto, vamos a modificar sus reglas de validación y vamos a establecer que el campo nombre
es obligatorio. Como el valor por defecto de la opción required
es true
, se puede establecer el validador de nombre
simplemente como:
$validadorNombre = new sfValidatorString();
Como el validador no establece ni la opción min_length
ni max_length
, no tiene efecto sobre el texto introducido. De hecho, se podría reemplazar por un validador vacío:
$validadorNombre = new sfValidatorPass();
En el ejemplo anterior, en vez de utilizar un validador vacío, se puede eliminar por completo el validador, pero la protección por defecto del formulario impide eliminar el validador. El listado 2-9 muestra cómo deshabilitar la protección por defecto mediante la opción allow_extra_fields
.
Listado 2-9 - Deshabilitando la protección allow_extra_fields
class ContactoForm extends sfForm
{
public function configure()
{
// ...
$this->setValidators(array(
'email' => new sfValidatorEmail(),
'asunto' => new sfValidatorChoice(array('choices' => array_keys(self::$asuntos))),
'mensaje' => new sfValidatorString(array('min_length' => 4)),
));
$this->validatorSchema->setOption('allow_extra_fields', true);
}
}
Con el código anterior, ya es posible validar el formulario como se muestra en la figura 2-9.
Si observas con atención la imagen anterior, verás que aunque el formulario es válido, el valor del campo nombre
está vacío en la página de agradecimiento, independientemente del valor que se haya escrito el usuario en ese campo. De hecho, el valor enviado no existe ni siquiera en el array que se obtiene mediante $this->formulario->getValues()
. Deshabilitar la opción allow_extra_fields
evita que se muestre el error debido a la falta del validador, pero como la otra opción llamada filter_extra_fields
vale true
por defecto, se filtran todos los campos que no tienen validador, por lo que sus valores no se encuentran entre los valores válidos. El listado 2-10 muestra cómo modificar este comportamiento.
Listado 2-10 - Deshabilitando la protección de filter_extra_fields
class ContactoForm extends sfForm
{
public function configure()
{
// ...
$this->setValidators(array(
'email' => new sfValidatorEmail(),
'asunto' => new sfValidatorChoice(array('choices' => array_keys(self::$asuntos))),
'mensaje' => new sfValidatorString(array('min_length' => 4)),
));
$this->validatorSchema->setOption('allow_extra_fields', true);
$this->validatorSchema->setOption('filter_extra_fields', false);
}
}
Ahora ya es posible validar el formulario y obtener el valor introducido por el usuario para mostrarlo en la página de agradecimiento.
En el capítulo 4 se muestra cómo utilizar estas protecciones para serializar de forma segura los objetos Propel a partir de los valores del formulario.