Más con Symfony

14.8. Haciendo uso de formularios embebidos

Todavía no hemos tenido en cuenta una de las características más importantes: los formularios embebidos. Si se embebe una instancia de CommentForm dentro de otro formulario, no se le aplicarán las mejoras realizadas en forms.yml. Esto es muy fácil de comprobar mediante las pruebas unitarias:

$t = new lime_test(6);

// ...

$form = new BaseForm();
$form->embedForm('comment', new CommentForm());
$form->bind();
$enhancer->enhance($form);
$t->like($form['comment']['body']->renderLabel(),
  '/Please enter your comment/',
  '->enhance() enhances embedded forms');

La nueva comprobación demuestra que las mejoras no se están aplicando en los formularios embebidos:

Las pruebas fallan

Figura 14.5 Las pruebas fallan

Para solucionar este problema es imprescindible crear un gestor de configuración más avanzado. Este nuevo gestor aplica las mejoras de forms.yml de forma modular para tener en cuenta los formularios embebidos. Así que se va a generar un método específico para cada clase de formulario configurada. Estos nuevos métodos se van a generar en una nueva clase de tipo worker de nuestro gestor propio de configuración.

class sfFormYamlEnhancementsConfigHandler extends sfYamlConfigHandler
{
  // ...

  protected function getEnhancerCode($fields)
  {
    $code = array();
    foreach ($fields as $field => $config)
    {
      $code[] = sprintf('if (isset($fields[%s]))', var_export($field, true));
      $code[] = '{';

      if (isset($config['label']))
      {
        $code[] = sprintf('  $fields[%s]->getWidget()->setLabel(%s);',
          var_export($config['label'], true));
      }

      if (isset($config['attributes']))
      {
        $code[] = '  $fields[%s]->getWidget()->setAttributes(array_merge(';
        $code[] = '    $fields[%s]->getWidget()->getAttributes(),';
        $code[] = '    '.var_export($config['attributes'], true);
        $code[] = '  ));';
      }

      if (isset($config['errors']))
      {
        $code[] = sprintf('  if ($error = $fields[%s]->getError())',
          var_export($field, true));
        $code[] = '  {';
        $code[] = '    $error->getValidator()->setMessages(array_merge(';
        $code[] = '      $error->getValidator()->getMessages(),';
        $code[] = '      '.var_export($config['errors'], true);
        $code[] = '    ));';
        $code[] = '  }';
      }

      $code[] = '}';
    }

    return implode(PHP_EOL.'    ', $code);
  }
}

Durante la generación del código se comprueba si existen determinadas claves en el array de configuración, en vez de realizar la comprobación en tiempo de ejecución. Se trata de un pequelo detalle que permite aumentar el rendimiento de la aplicación.

Nota Como regla general, la lógica que comprueba las condiciones de la configuración se debería realizar en el propio gestor de configuración y no en el código generado. La lógica que comprueba las condiciones en tiempo de ejecución, como por ejemplo el tipo de formulario que se está mejorando, deben realizarse en tiempo de ejecución.

Este código generado se incluye dentro de la definición de una clase y luego se guarda en el directorio de la cache.

class sfFormYamlEnhancementsConfigHandler extends sfYamlConfigHandler
{
  public function execute($configFiles)
  {
    $forms = self::getConfiguration($configFiles);

    $code = array();
    $code[] = '<?php';
    $code[] = '// auto-generated by '.__CLASS__;
    $code[] = '// date: '.date('Y/m/d H:is');
    $code[] = 'class sfFormYamlEnhancementsWorker';
    $code[] = '{';
    $code[] = '  static public $enhancable = '.var_export(array_keys($forms), true).';';

    foreach ($forms as $class => $fields)
    {
      $code[] = '  static public function enhance'.$class.'(sfFormFieldSchema $fields)';
      $code[] = '  {';
      $code[] = '    '.$this->getEnhancerCode($fields);
      $code[] = '  }';
    }

    $code[] = '}';

    return implode(PHP_EOL, $code);
  }

  // ...
}

La clase sfFormYamlEnhancer ahora deriva el trabajo de manipulación de los formularios a la clase de tipo worker generada, pero debe controlar la recursión de los formularios embebidos. Para ello es necesario procesar en paralelo el esquema de los campos del formulario (iterándolo de forma recursiva) y el objeto del formulario (que incluye los formularios embebidos).

class sfFormYamlEnhancer
{
  // ...

  public function enhance(sfForm $form)
  {
    require_once $this->configCache->checkConfig('config/forms.yml');
    $this->doEnhance($form->getFormFieldSchema(), $form);
  }

  protected function doEnhance(sfFormFieldSchema $fieldSchema, sfForm $form)
  {
    if ($enhancer = $this->getEnhancer(get_class($form)))
    {
      call_user_func($enhancer, $fieldSchema);
    }

    foreach ($form->getEmbeddedForms() as $name => $form)
    {
      if (isset($fieldSchema[$name]))
      {
        $this->doEnhance($fieldSchema[$name], $form);
      }
    }
  }

  public function getEnhancer($class)
  {
    if (in_array($class, sfFormYamlEnhancementsWorker::$enhancable))
    {
      return array('sfFormYamlEnhancementsWorker', 'enhance'.$class);
    }
    else if ($overlap = array_intersect(class_parents($class),
      sfFormYamlEnhancementsWorker::$enhancable))
    {
      return array('sfFormYamlEnhancementsWorker', 'enhance'.current($overlap));
    }
  }
}

Nota Los campos de los formularios embebidos no se deben modificar después de haber sido embebidos. Los formularios embebidos se guardan en su formulario padre simplemente por razones de procesamiento, ya que no afectan a la forma en la que se muestra el formulario padre.

Ahora que ya está disponible el soporte de los formularios embebidos, las pruebas deberían ejecutarse correctamente. Si ejecutas las pruebas, verás el siguiente resultado:

Las pruebas pasan correctamente

Figura 14.6 Las pruebas pasan correctamente