Las tareas doctrine:build-forms
y doctrine:generate-crud
permiten crear módulos de Symfony totalmente funcionales para listar, crear, editar y borrar objetos del modelo de datos. Estos módulos tienen en cuenta las reglas de validación y las relaciones entre tablas. Además, todo esto se consigue sin escribir ni una sola línea de código.
El siguiente paso consiste en personalizar el código generado automáticamente. Como los formularios incluyen muchos elementos, algunos aspectos de cada formulario deben ser modificados.
11.4.1. Configurando los widgets y validadores
En primer lugar se configuran los widgets y validadores generados automáticamente. El formulario ArticleForm
incluye un campo llamado slug
. Un slug es una cadena de caracteres que representan de forma única a cada artículo dentro de la URL.
Si se considera por ejemplo un artículo titulado "Optimiza las aplicaciones creadas con Symfony", su slug sería 12-optimiza-las-aplicaciones-creadas-con-symfony
, siendo 12
el valor del campo id
de ese artículo. Normalmente el slug se genera automáticamente a partir del título del objeto cada vez que se guarda el objeto en la base de datos.
En el siguiente ejemplo se considera que el usuario también puede indicar de forma explícita el valor del slug de un artículo. En este caso, aunque es obligatorio que el slug tenga un valor válido, no puede ser un campo obligatorio del formulario. Por este motivo se modificar su validador para que sea opcional, tal y como muestra el listado 11-8. Además, se modifica el campo content
para aumentar su tamaño en pantalla y para forzar al usuario a escribir al menos cinco caracteres.
Listado 11-8 - Personalizando widgets y validadores
class ArticleForm extends BaseArticleForm
{
public function configure()
{
// ...
$this->validatorSchema['slug']->setOption('required', false);
$this->validatorSchema['content']->setOption('min_length', 5);
$this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40));
}
}
El código anterior utiliza los objetos validatorSchema
y widgetSchema
como si fueran arrays de PHP. Estos arrays asociativos admiten como clave el nombre de un campo del formulario y devuelven respectivamente el objeto validador y el objeto del widget. Mediante estos objetos se personalizan los campos y widgets individualmente.
Nota Para poder utilizar los objetos como si fueran arrays de PHP, las clases sfValidatorSchema
y sfWidgetFormSchema
implementan la interfaz ArrayAccess
, disponible en PHP desde la versión 5.
Para asegurar que dos artículos no tengan el mismo valor en su campo slug
, la definición del esquema incluye una restricción que exige que el valor del campo sea único. Esta restricción de la base de datos se refleja en el formulario ArticleForm
mediante el validador sfValidatorDoctrineUnique
. Este validador permite asegurar que el valor de un campo del formulario sea único en la base de datos. Se puede emplear este validador, entre otras cosas, para asegurar que una dirección de email o un login sean únicos. El listado 11-9 muestra como lo utiliza el formulario ArticleForm
.
Listado 11-9 - Utilizando el validador sfValidatorDoctrineUnique
para asegurar que un valor sea único
class BaseArticleForm extends BaseFormDoctrine
{
public function setup()
{
// ...
$this->validatorSchema->setPostValidator(
new sfValidatorDoctrineUnique(array('model' => 'Article', 'column' => array('slug')))
);
}
}
El validador sfValidatorDoctrineUnique
es de tipo postValidator
, ya que se ejecuta sobre los datos completos del formulario después de que cada campo individual haya sido validado. De hecho, para validar que el valor del campo slug
sea único, el validador no sólo debe acceder al propio valor del campo slug
, sino que también debe conocer el valor de la clave primaria. Además, las reglas de validación son diferentes en la fase de creación de datos y en la de modificación, ya que el slug puede permanecer invariante mientras se modifica un artículo.
A continuación se modifica el campo active
de la tabla author
, que indica si un autor se encuentra activo. El listado 11-10 muestra cómo excluir a los autores inactivos en el formulario ArticleForm
, modificando la opción query
del widget sfWidgetDoctrineSelect
asociado con el campo author_id
. La opción query
acepta un objeto de tipo consulta de Doctrine, lo que permite restringir los valores mostrados en la lista desplegable asociada.
Listado 11-10 - Personalizando el widget sfWidgetDoctrineSelect
class ArticleForm extends BaseArticleForm
{
public function configure()
{
// ...
$query = Doctrine_Query::create()
->from('Author a')
->where('a.active = ?', true);
$this->widgetSchema['author_id']->setOption('query', $query);
}
}
Personalizar el widget permite restringir las opciones que se muestran en la lista desplegable, pero no olvides que también es necesario realizar esta modificación en el validador, tal y como muestra el listado 11-11. Al igual que el widget sfWidgetProperSelect
, el validador sfValidatorDoctrineChoice
acepta una opción llamada query
que permite restringir los valores válidos para un campo del formulario.
Listado 11-11 - Personalizando el validador sfValidatorDoctrineChoice
class ArticleForm extends BaseArticleForm
{
public function configure()
{
// ...
$query = Doctrine_Query::create()
->from('Author a')
->where('a.active = ?', true);
$this->widgetSchema['author_id']->setOption('query', $query);
$this->validatorSchema['author_id']->setOption('query', $query);
}
}
En el ejemplo anterior, se define el objeto Query
directamente en el método configure()
. En el proyecto que se está realizando, esta consulta puede ser útil en muchas otras circunstancias, por lo que es mejor crear un método llamado getActiveAuthorsQuery()
en la clase AuthorPeer
e invocar este método desde ArticleForm
, tal y como muestra el listado 11-12.
Listado 11-12 - Refactorizando la consulta con Query
en el modelo
class AuthorTable extends Doctrine_Table
{
public function getActiveAuthorsQuery()
{
$query = Doctrine_Query::create()
->from('Author a')
->where('a.active = ?', true);
return $query;
}
}
class ArticleForm extends BaseArticleForm
{
public function configure()
{
$authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery();
$this->widgetSchema['author_id']->setOption('query', $authorQuery);
$this->validatorSchema['author_id']->setOption('query', $authorQuery);
}
}
Nota De la misma forma que el widget sfWidgetDoctrineSelect
y el validador sfValidatorDoctrineChoice
representan una relación 1-n entre dos tablas, el widget sfWidgetDoctrineSelectMany
y el validador sfValidatorDoctrineChoiceMany
representan una relación n-n con las mismas opciones que los anteriores. El formulario ArticleForm
utiliza estas clases para representar la relación entre las tablas article
y tag
.
11.4.2. Modificando el validador
El esquema de datos define el campo email
como un dato string(255)
, por lo que Symfony crea un validador de tipo sfValidatorString()
que restringe su longitud máxima a 255
caracteres. Como este campo también debe cumplir la condición de que sea una dirección de correo electrónico válida, el listado 11-14 reemplaza el validador generado automáticamente por un validador de tipo sfValidatorEmail
.
Listado 11-13 - Modificando el validador del campo email
en la clase AuthorForm
class AuthorForm extends BaseAuthorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorEmail();
}
}
11.4.3. Añadiendo un validador
En la sección anterior se modificó un validador generado automáticamente. Sin embargo, en el caso del campo email
, lo ideal sería mantener el validador que controla su longitud máxima. En el listado 11-14 se emplea el validador sfValidatorAnd
para asegurar que el email proporcionado sea válido y que su longitud no sea mayor que la longitud máxima permitida en ese campo.
Listado 11-14 - Utilizando un validador múltiple
class AuthorForm extends BaseAuthorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorAnd(array(
new sfValidatorString(array('max_length' => 255)),
new sfValidatorEmail(),
));
}
}
El código del ejemplo anterior no es ideal porque si más adelante se modifica el tamaño del campo email
en el esquema de la base de datos, es necesario modificarlo también en el formulario. Por lo tanto, en vez de reemplazar el validador generado automáticamente, es mejor añadir uno nuevo, tal y como muestra el listado 11-15.
Listado 11-15 - Añadiendo un validador
class AuthorForm extends BaseAuthorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorAnd(array(
$this->validatorSchema['email'],
new sfValidatorEmail(),
));
}
}
11.4.4. Modificando un widget
En el esquema de la base de datos, la tabla article
almacena el estado de cada artículo en forma de cadena de caracteres en el campo status
. Los posibles valores del estado se definen en la clase ArticePeer
, tal y como muestra el listado 11-16.
Listado 11-16 - Definiendo los posibles estados en la clase ArticlePeer
class ArticlePeer extends BaseArticlePeer
{
static protected $statuses = array('draft', 'online', 'offline');
static public function getStatuses()
{
return self::$statuses;
}
// ...
}
Cuando se editan los datos de un artículo, el campo status
se debería representar en forma de lista desplegable en vez de como un cuadro de texto. Para ello, se modifica el widget utilizado hasta el momento mediante el código mostrado en el listado 11-17.
Listado 11-17 - Modificando el widget del campo status
class ArticleForm extends BaseArticleForm
{
public function configure()
{
$this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses()));
}
}
Para completar la modificación, también se debe cambiar el validador para asegurar que el estado seleccionado pertenece a alguna de las posibles opciones de la lista (ver listado 11-18).
Listado 11-18 - Modificando el validador del campo status
class ArticleForm extends BaseArticleForm
{
public function configure()
{
$statuses = ArticlePeer::getStatuses();
$this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => $statuses));
$this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys($statuses)));
}
}
11.4.5. Eliminando un campo
La tabla article
dispone de dos columnas especiales llamadas created_at
y updated_at
, para las cuales Doctrine actualiza automáticamente sus valores. Para evitar que el usuario las modifique, el listado 11-19 muestra cómo se pueden eliminar del formulario.
Listado 11-19 - Eliminando un campo
class ArticleForm extends BaseArticleForm
{
public function configure()
{
unset($this->validatorSchema['created_at']);
unset($this->widgetSchema['created_at']);
unset($this->validatorSchema['updated_at']);
unset($this->widgetSchema['updated_at']);
}
}
Para eliminar un campo es preciso eliminar su validador y su widget. El listado 11-20 muestra cómo borrar los dos con una única instrucción accediendo al formulario como si fuera un array de PHP.
Listado 11-20 - Eliminando un campo accediendo al formulario como si fuera un array de PHP
class ArticleForm extends BaseArticleForm
{
public function configure()
{
unset($this['created_at'], $this['updated_at']);
}
}
11.4.6. Resumiendo
Los listados 11-21 y 11-22 muestran el código definitivo de los formularios ArticleForm
y AuthorForm
después de personalizarlos.
Listado 11-21 - Formulario ArticleForm
class ArticleForm extends BaseArticleForm
{
public function configure()
{
$authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery();
// widgets
$this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40));
$this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses()));
$this->widgetSchema['author_id']->setOption('query', $authorQuery);
// validators
$this->validatorSchema['slug']->setOption('required', false);
$this->validatorSchema['content']->setOption('min_length', 5);
$this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticlePeer::getStatuses())));
$this->validatorSchema['author_id']->setOption('query', $authorQuery);
unset($this['created_at']);
unset($this['updated_at']);
}
}
Listado 11-22 - Formulario AuthorForm
class AuthorForm extends BaseAuthorForm
{
public function configure()
{
$this->validatorSchema['email'] = new sfValidatorAnd(array(
$this->validatorSchema['email'],
new sfValidatorEmail(),
));
}
}
La tarea doctrine:build-forms
permite generar automáticamente la mayoría de elementos de los formularios mediante la introspección del modelo de objetos. Las principales ventajas de esta automatización son:
- Mejora la productividad del programador, evitándole todo el trabajo repetitivo y redundante. De esta forma el programador sólo se encarga de personalizar los validadores y los widgets en función de la lógica de negocio de la aplicación.
- Si se actualiza el esquema de datos, los formularios generados también se actualizan automáticamente. Una vez más, el programador sólo se encarga de ajustar la personalización realizada anteriormente.
La siguiente sección trata de la modificación de las acciones y plantillas generadas por la tarea doctrine:generate-crud
.