En esta sección se muestra cómo configurar los validadores y los widgets generados por defecto.
El formulario ArticuloForm
dispone de un campo llamado slug
. Un slug es una cadena de caracteres que representa de forma única al artículo dentro de la URL. Si se dispone por ejemplo de un artículo cuyo título es "Optimizando la programación con Symfony" y cuyo id
es 12
, su slug es 12-optimizando-la-programacion-con-symfony
. Este campo se determina automáticamente a partir del campo titulo
cada vez que se guarda el objeto, pero también se permite al usuario establecerlo de forma explícita. Aunque este campo es obligatorio según el esquema de datos, no puede ser un campo obligatorio en el formulario. Por este motivo se modifica el validador para hacer el campo opcional, como se muestra en el listado 4-8. Además, se personaliza el campo contenido
aumentando su tamaño y obligando al usuario a escribir al menos cinco caracteres.
Listado 4-8 - Personalizando los validadores y los widgets
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
// ...
$this->validatorSchema['slug']->setOption('required', false);
$this->validatorSchema['contenido']->setOption('min_length', 5);
$this->widgetSchema['contenido']->setAttributes(array('rows' => 10, 'cols' => 40));
}
}
En el código anterior se utilizan los objetos validatorSchema
y widgetSchema
como si fueran arrays de PHP. Estos arrays aceptan el nombre del campo como clave y devuelven respectivamente el objeto del validador y el objeto del widget. Por lo tanto, es posible personalizar cada campo y cada widget.
Nota Para poder acceder a los objetos como si fueran arrays de PHP, las clases sfValidatorSchema
y sfWidgetFormSchema
implementan la interfaz ArrayAccess
, disponible desde la versión PHP 5.
Para asegurar que dos artículos no tengan el mismo slug
, la definición del esquema incluye la restricción para hacer el slug único. Esta restricción a nivel de base de datos, se refleja en el formulario ArticuloForm
a través del validador sfValidatorPropelUnique
. Este validador se puede emplear para asegurar que el valor de un campo sea único en la base de datos, lo que puede ser útil para campos como el login o el email. El listado 4-9 muestra cómo utilizarlo en el formulario ArticuloForm
.
Listado 4-9 - Utilizando el validador sfValidatorPropelUnique
para asegurar que el valor de un campo sea único
class BaseArticuloForm extends BaseFormPropel
{
public function setup()
{
// ...
$this->validatorSchema->setPostValidator(
new sfValidatorPropelUnique(array('model' => 'Articulo', 'column' => array('slug')))
);
}
}
El validador sfValidatorPropelUnique
se establece como post-validador para que se ejecute después de comprobar que toda la información enviada por el usuario es válida. Para validar que el campo slug
sea único, el validador no sólo tiene que accceder a su valor, sino que también debe acceder al valor de la clave o claves primarias. Además, las reglas de validación son diferentes en la creación y en la modificación del objeto, ya que el slug puede permanecer invariante durante la modificación de un artículo.
A continuación se modifica el campo activo
de la tabla autor
, que se utiliza para determinar si un usuario es activo o no. El listado 4-10 muestra cómo se pueden excluir los autores inactivos en el formulario ArticuloForm
modificando la opción criteria
del widget sfWidgetPropelSelect
asociado al campo autor_id
. La opción criteria
toma como valor un objeto de tipo Criteria
de Propel, lo que permite restringir el número de elementos de la lista desplegable.
Listado 4-10 - Personalizando el widget sfWidgetPropelSelect
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
// ...
$autorCriteria = new Criteria();
$autorCriteria->add(AutorPeer::ACTIVO, true);
$this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria);
}
}
El código anterior sólo modifica la lista de opciones disponibles en el widget, pero también se deben restringir las opciones en el validador, tal y como se muestra en el listado 4-11. Al igual que el widget sfWidgetProperSelect
, el validador sfValidatorPropelChoice
acepta una opción llamada criteria
para restringir las opciones válidas para este campo.
Listado 4-11 - Personalizando el validador sfValidatorPropelChoice
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
// ...
$autorCriteria = new Criteria();
$autorCriteria->add(AutorPeer::ACTIVO, true);
$this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria);
$this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria);
}
}
En el ejemplo anterior se define el objeto Criteria
directamente en el método configure()
. En un proyecto real, este objeto Criteria
puede ser muy útil en otras partes del código, por lo que es mejor crear un método llamado getCriteriaAutoresActivos()
dentro de la clase AutorPeer
y llamar a este método desde ArticuloForm
como muestra el listado 4-12.
Listado 4-12 - Refactorizando el objeto Criteria
en la clase del modelo
class AutorPeer extends BaseAutorPeer
{
static public function getCriteriaAutoresActivos()
{
$criteria = new Criteria();
$criteria->add(AutorPeer::ACTIVO, true);
return $criteria;
}
}
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
$autorCriteria = AutorPeer::getCriteriaAutoresActivos();
$this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria);
$this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria);
}
}
Nota Mientras que el widget sfWidgetPropelSelect
y el validador sfValidatorPropelChoice
representan una relación 1-n entre dos tablas, el widget sfWidgetFormPropelSelectMany
y el validador sfValidatorPropelChoiceMany
representan una relación de tipo n-n y aceptan las mismas opciones. En el formulario ArticuloForm
, estas clases se emplean para representar la relación entre las tablas articulo
y etiqueta
.
4.5.1. Modificando el validador
El campo email
se define en el esquema como varchar(255)
, por lo que Symfony crea un validador de tipo sfValidatorString()
para restringir su longitud a un máximo de 255
caracteres. Como este campo requiere que el valor introducido sea una dirección válida de correo eletrónico, el listado 4-14 reemplaza el validador generado por un validador de tipo sfValidatorEmail
.
Listado 4-13 - Modificando el validador del campo email
del formulario AutorForm
class AutorForm extends BaseAutorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorEmail();
}
}
4.5.2. Añadiendo un validador
En la sección anterior se explica cómo modificar un validador generado automáticamente. Pero en el caso del campo emaill
, también es necesario mantener el validador que limita el máximo número de caracteres. El listado 4-14 utiliza el validador sfValidatorAnd
para garantizar que la dirección de email sea válida y que su longitud sea igual o inferior al máximo establecido.
Listado 4-14 - Utilizando un validador múltiple
class AutorForm extends BaseAutorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorAnd(array(
new sfValidatorString(array('max_length' => 255)),
new sfValidatorEmail(),
));
}
}
El ejemplo anterior tiene un inconveniente, ya que si se modifica el tamaño del campo email
en el esquema de la base de datos, es preciso acordarse de que también hay que modificarlo en el formulario. Por lo tanto, en vez de reemplazar el validador generado automáticamente, es mejor añadir otro validador como se muestra en el listado 4-15.
Listado 4-15 - Añadiendo un validador
class AutorForm extends BaseAutorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorAnd(array(
$this->validatorSchema['email'],
new sfValidatorEmail(),
));
}
}
4.5.3. Modificando el widget
El campo estado
de la tabla articulo
se define en el esquema como una cadena de caracteres. Sus posibles valores se definen en la clase ArticuloPeer
, como se muestra en el listado 4-16.
Listado 4-16 - Definiendo los posibles valores del estado en la clase ArticuloPeer
class ArticuloPeer extends BaseArticuloPeer
{
static protected $estados = array('borrador', 'online', 'offline');
static public function getEstados()
{
return self::$estados;
}
// ...
}
Cuando se modifica un artículo, el campo estado
se debe representar como una lista desplegable en vez de como un cuadro de texto. Para ello, se modifica el widget de la forma que se muestra en el listado 4-17.
Listado 4-17 - Modificando el widget del campo estado
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
$this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => ArticuloPeer::getEstados()));
}
}
Por último, también es necesario modificar el validador para asegurar que el estado seleccionado se encuentre dentro de la lista de estados posibles (ver listado 4-18).
Listado 4-18 - Modificando el validador del campo estado
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
$estados = ArticuloPeer::getEstados();
$this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => $estados));
$this->validatorSchema['estado'] = new sfValidatorChoice(array('choices' => array_keys($estados)));
}
}
4.5.4. Eliminando un campo
La tabla articulo
tiene dos columnas especiales llamadas created_at
y updated_at
, cuyos valores gestiona Propel de forma automática. Por lo tanto, para evitar que el usuario modifique sus valores, es necesario eliminar estos dos campos del formulario, tal y como muestra el listado 4-19.
Listado 4-19 - Eliminando un campo
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
unset($this->validatorSchema['created_at']);
unset($this->widgetSchema['created_at']);
unset($this->validatorSchema['updated_at']);
unset($this->widgetSchema['updated_at']);
}
}
Eliminar un campo consiste en eliminar su validador y su widget. El listado 4-20 muestra cómo borrar los dos de forma simultánea mediante una sola instrucción, accediendo al formulario como si fuera un array de PHP.
Listado 4-20 - Accediendo al formulario como si fuera un array para eliminar un campo
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
unset($this['created_at'], $this['updated_at']);
}
}
4.5.5. Resultado final
Los listados 4-21 y 4-22 muestran respectivamente las versiones finales de los formularios ArticuloForm
y AutorForm
después de su personalización.
Listado 4-21 - Formulario ArticuloForm
class ArticuloForm extends BaseArticuloForm
{
public function configure()
{
$autorCriteria = AutorPeer::getCriteriaAutoresActivos();
// widgets
$this->widgetSchema['contenido']->setAttributes(array('rows' => 10, 'cols' => 40));
$this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => ArticuloPeer::getEstados()));
$this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria);
// validators
$this->validatorSchema['slug']->setOption('required', false);
$this->validatorSchema['contenido']->setOption('min_length', 5);
$this->validatorSchema['estado'] = new sfValidatorChoice(array('choices' => array_keys(ArticuloPeer::getEstados())));
$this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria);
unset($this['created_at']);
unset($this['updated_at']);
}
}
Listado 4-22 - Formulario AutorForm
class AutorForm extends BaseAutorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorAnd(array(
$this->validatorSchema['email'],
new sfValidatorEmail(),
));
}
}
La tarea propel:build-forms
genera de forma automática la mayoría de elementos de los formularios a partir de la información del modelo de datos. Este proceso automático es muy útil porque:
- Simplifica el trabajo del programador, evitándole las tareas más tediosas y repetitivas. De esta forma, el programador se centra en la configuración de los widgets y validadores para que sigan la lógica de la aplicación.
- Además, cada vez que se actualiza el esquema de la base de datos, también se actualizan los formularios generados de forma automática. El único trabajo del programador consiste en ajustar la configuración realizada.
La siguiente sección describe la personalización y configuración de las acciones y plantillas generadas con la tarea propel:generate-crud
.