Más con Symfony

6.6. Diseccionando el objeto sfForm

En esencia, un formulario web es una colección de campos que se muestran y se vuelven a enviar al servidor. Igualmente, el objeto sfForm es básicamente un array de campos de formulario. sfForm gestiona el proceso completo, pero los campos individuales son los responsables de mostrarse y validarse.

En Symfony, cada campo de formulario se define mediante dos objetos diferentes:

  • Un widget que muestra el código XHTML del campo de formulario
  • Un validador que limpia y valida los datos enviados en ese campo

Nota En Symfony, un widget se define como cualquier objeto cuya única tarea consiste en generar código XHTML. Aunque normalmente se utilizan en los formularios, se puede crear un objeto de tipo widget para generar cualquier tipo de código.

6.6.1. Un formulario es un array

Recuerda que un objeto sfForm es básicamente un array de campos de formulario. De forma más precisa, sfForm incluye un array de widgets y un array de validadores para todos los campos del formularios. Estos dos arrays, llamados widgetSchema y validatorSchema son propiedades de la clase sfForm. Para añadir un nuevo campo al formulario, simplemente se añade el widget del campo en el array widgetSchema y el validador del campo en el array validatorSchema. El siguiente código por ejemplo añade un campo email en el formulario:

public function configure()
{
  $this->widgetSchema['email'] = new sfWidgetFormInputText();
  $this->validatorSchema['email'] = new sfValidatorEmail();
}

Nota Los arrays widgetSchema y validatorSchema en realidad son clases llamadas sfWidgetFormSchema y sfValidatorSchema que implementan la interfaz ArrayAccess.

6.6.2. Diseccionando el formulario ProductForm

Como la clase ProductForm hereda de sfForm, también incluye todos sus widgets y validadores en los arrays widgetSchema y validatorSchema. A continuación se muestra cómo se organiza cada array en el objeto ProductForm final.

widgetSchema    => array
(
  [id]          => sfWidgetFormInputHidden,
  [name]        => sfWidgetFormInputText,
  [price]       => sfWidgetFormInputText,
  [newPhotos]   => array(
    [0]           => array(
      [id]          => sfWidgetFormInputHidden,
      [filename]    => sfWidgetFormInputFile,
      [caption]     => sfWidgetFormInputText,
    ),
    [1]           => array(
      [id]          => sfWidgetFormInputHidden,
      [filename]    => sfWidgetFormInputFile,
      [caption]     => sfWidgetFormInputText,
    ),
  ),
)

validatorSchema => array
(
  [id]          => sfValidatorDoctrineChoice,
  [name]        => sfValidatorString,
  [price]       => sfValidatorNumber,
  [newPhotos]   => array(
    [0]           => array(
      [id]          => sfValidatorDoctrineChoice,
      [filename]    => sfValidatorFile,
      [caption]     => sfValidatorString,
    ),
    [1]           => array(
      [id]          => sfValidatorDoctrineChoice,
      [filename]    => sfValidatorFile,
      [caption]     => sfValidatorString,
    ),
  ),
)

Nota Al igual que widgetSchema y validatorSchema son realmente objetos que se comportan como arrays, los arrays anteriores definidos mediante las claves newPhotos, 0 y 1 son objetos sfWidgetSchema y sfValidatorSchema.

Como era de esperar, los campos básicos (id, name y price) se representan en el primer nivel de cada array. En los formularios que no embeben otros formularios, tanto widgetSchema como validatorSchema solamente tienen un nivel, que representa los campos básicos del formulario. Los widgets y validadores de cualquier formulario embebido se representan como subarrays de widgetSchema y validatorSchema. A continuación se explica el método que se encarga de este proceso.

6.6.3. Entendiendo el método sfForm::embedForm()

Como un formulario está compuesto por un array de widgets y otro de validadores, embeber un formulario en otro consiste fundamentalmente en añadir los arrays de widgets y validadores de un formulario dentro de los arrays de widgets y validadores del formulario principal. Este proceso lo realiza completamente el método sfForm::embedForm(). El resultado siempre es la creación de unos arrays widgetSchema y validatorSchema multidimensionales, tal y como se mostró anteriormente.

A continuación se explica la configuración de ProductPhotoCollectionForm, que asocia objetos ProductPhotoForm individuales consigo mismo. Este formulario intermedio actúa como un formulario contenedor y facilita la organización de todos los formularios. En primer lugar, veamos el siguiente código extraído de ProductPhotoCollectionForm::configure():

$form = new ProductPhotoForm($productPhoto);
$this->embedForm($i, $form);

El propio formulario ProductPhotoCollectionForm comienza como un nuevo objeto de tipo sfForm. Por tanto, sus arrays widgetSchema y validatorSchema están vacíos.

widgetSchema    => array()
validatorSchema => array()

Por su parte, cada formulario ProductPhotoForm ya contiene tres campos (id, filename y caption) y sus correspondientes tres elementos en los arrays widgetSchema y validatorSchema.

widgetSchema    => array
(
  [id]            => sfWidgetFormInputHidden,
  [filename]      => sfWidgetFormInputFile,
  [caption]       => sfWidgetFormInputText,
)

validatorSchema => array
(
  [id]            => sfValidatorDoctrineChoice,
  [filename]      => sfValidatorFile,
  [caption]       => sfValidatorString,
)

El método sfForm::embedForm() simplemente añade los arrays widgetSchema y validatorSchema de cada ProductPhotoForm dentro de los arrays widgetSchema y validatorSchema del objeto ProductPhotoCollectionForm vacío.

Al finalizar, los arrays widgetSchema y validatorSchema del formulario contenedor (ProductPhotoCollectionForm) son arrays multidimensionales que contienen los widgets y validadores de los dos formularios ProductPhotoForm.

widgetSchema    => array
(
  [0]             => array
  (
    [id]            => sfWidgetFormInputHidden,
    [filename]      => sfWidgetFormInputFile,
    [caption]       => sfWidgetFormInputText,
  ),
  [1]             => array
  (
    [id]            => sfWidgetFormInputHidden,
    [filename]      => sfWidgetFormInputFile,
    [caption]       => sfWidgetFormInputText,
  ),
)

validatorSchema => array
(
  [0]             => array
  (
    [id]            => sfValidatorDoctrineChoice,
    [filename]      => sfValidatorFile,
    [caption]       => sfValidatorString,
  ),
  [1]             => array
  (
    [id]            => sfValidatorDoctrineChoice,
    [filename]      => sfValidatorFile,
    [caption]       => sfValidatorString,
  ),
)

En el último paso de este proceso, el formulario contenedor resultante (ProductPhotoCollectionForm) se embebe directamente en ProductForm. Esto se realiza dentro del método ProductForm::configure(), que aprovecha todo el trabajo realizado dentro de ProductPhotoCollectionForm:

$form = new ProductPhotoCollectionForm(null, array(
  'product' => $this->getObject(),
  'size'    => 2,
));

$this->embedForm('newPhotos', $form);

El código anterior produce la estructura definitiva de los arrays widgetSchema y validatorSchema que se mostró anteriormente. En realidad, el método embedForm() es similar a combinar manualmente los arrays widgetSchema y validatorSchema:

$this->widgetSchema['newPhotos'] = $form->getWidgetSchema();
$this->validatorSchema['newPhotos'] = $form->getValidatorSchema();