Más con Symfony

14.7. Gestores de configuración propios

El código mostrado anteriormente recorre, para cada variable que se pasa a la plantilla, todas las clases de formulario configuradas en forms.yml. Aunque es una solución que funciona bien, penaliza mucho el rendimiento de la aplicación cuando se pasan muchos formularios a la plantilla o si se dispone de una lista muy larga de formularios configurados en el archivo YAML. Se trata de una buena oportunidad para crear un gestor propio de configuración que permita optimizar este proceso.

La mayor parte de la magia de los gestores de configuración se produce internamente. La cache de configuración se encarga de la lógica de la cache antes de ejecutar el gestor de configuración apropiado, por lo que nos podemos centrar exclusivamente en generar el código necesario para aplicar la configuración YAML.

Todos los gestores de configuración deben implementar los siguientes dos métodos:

  • static public function getConfiguration(array $configFiles)
  • public function execute($configFiles)

Al primer método, ::getConfiguration(), se le pasa un array de rutas de archivos, para que los procese y junte sus contenidos en un único gran archivo. En la sfSimpleYamlConfigHandler utilizada anteriormente este método sólo utiliza una línea:

static public function getConfiguration(array $configFiles)
{
  return self::parseYamls($configFiles);
}

La clase sfSimpleYamlConfigHandler extiende la clase abstracta sfYamlConfigHandler, que incluye varios métodos útiles para procesar los archivos de configuración YAML:

  • ::parseYamls($configFiles)
  • ::parseYaml($configFile)
  • ::flattenConfiguration($config)
  • ::flattenConfigurationWithEnvironment($config)

Los dos primeros métodos implementan la configuración en cascada. de Symfony. El segundo implementa el mecanismo de configuración basada en entornos.

El método ::getConfiguration() de nuestro gestor de configuración necesita un método propio para unir toda la configuración en un único archivo. Crea un método llamado ::applyInheritance() para encapsular toda esta lógica:

// lib/config/sfFormYamlEnhancementsConfigHandler.class.php
class sfFormYamlEnhancementsConfigHandler extends sfYamlConfigHandler
{
  public function execute($configFiles)
  {
    $config = self::getConfiguration($configFiles);

    // compile data
    $retval = "<?php\n".
              "// auto-generated by %s\n".
              "// date: %s\nreturn %s;\n";
    $retval = sprintf($retval, __CLASS__, date('Y/m/d H:i:s'),
      var_export($config, true));

    return $retval;
  }

  static public function getConfiguration(array $configFiles)
  {
    return self::applyInheritance(self::parseYamls($configFiles));
  }

  static public function applyInheritance($config)
  {
    $classes = array_keys($config);

    $merged = array();
    foreach ($classes as $class)
    {
      if (class_exists($class))
      {
        $merged[$class] = $config[$class];
        foreach (array_intersect(class_parents($class), $classes) as $parent)
        {
          $merged[$class] = sfToolkit::arrayDeepMerge(
            $config[$parent],
            $merged[$class]
          );
        }
      }
    }

    return $merged;
  }
}

Ahora ya se dispone de un array cuyos valores se han unido mediante la herencia de clases. De esta forma se evita tener que filtrar la configuración mediante una instrucción instanceof para cada objeto de formulario. Además, esta unión de archivos se realiza en el gestor de configuración, por lo que sólo se realiza una vez y después se guarda en la cache.

Aplicando una lógica muy sencilla, ya es posible aplicar este array a un objeto de formulario:

class sfFormYamlEnhancer
{
  protected
    $configCache = null;

  public function __construct(sfConfigCache $configCache)
  {
    $this->configCache = $configCache;
    $this->configCache->registerConfigHandler('config/forms.yml',
      'sfFormYamlEnhancementsConfigHandler');
  }

  // ...

  public function enhance(sfForm $form)
  {
    $config = include $this->configCache->checkConfig('config/forms.yml');

    $class = get_class($form);
    if (isset($config[$class]))
    {
      $fieldConfigs = $config[$class];
    }
    else if ($overlap = array_intersect(class_parents($class),
      array_keys($config)))
    {
      $fieldConfigs = $config[current($overlap)];
    }
    else
    {
      return;
    }

    foreach ($fieldConfigs as $fieldName => $fieldConfig)
    {
      // ...
    }
  }
}

Antes de volver a ejecutar el script de las pruebas, se añade una comprobación para la nueva lógica de herencia de clases.

# config/forms.yml

# ...

BaseForm:
  body:
    errors:
      min_length: A base min_length message
      required:   A base required message

A continuación se comprueba mediante las pruebas unitarias que se está aplicando un mensaje de tipo required al formulario y que también se está aplicando ese mensaje a todos los formularios que heredan de un formulario padre, aunque ellos mismos no tengan configurado ese mensaje.

$t = new lime_test(5);

// ...

$form = new CommentForm();
$form->bind();
$enhancer->enhance($form);
$t->like($form['body']->renderError(), '/A base required message/',
  '->enhance() considers inheritance');

class SpecialCommentForm extends CommentForm { }
$form = new SpecialCommentForm();
$form->bind();
$enhancer->enhance($form);
$t->like($form['body']->renderLabel(), '/Please enter your comment/',
  '->enhance() applies parent config');

Ejecuta de nuevo las pruebas unitarias para comprobar que el sistema de mejora de formularios sigue funcionando correctamente.

Las pruebas pasan correctamente

Figura 14.4 Las pruebas pasan correctamente