Más con Symfony

10.2. sfContext y las factorías

Antes de adentrarnos más profundamente en este proceso, vamos a hablar de una de las partes más importantes del flujo de trabajo de Symfony: las factorías.

Las factorías en Symfony son una serie de clases o componentes de los que depende tu aplicación, como por ejemplo logger o i18n. Cada factoría se configura en el archivo factories.yml, que después se compila mediante un gestor de configuración (como se explicará más adelante) para convertirlo en el código PHP que realmente instancia los objetos factoría (puedes ver el resultado de este proceso en el archivo cache/frontend/dev/config/config_factories.yml.php de tu cache).

Nota La carga de las factoría se realiza durante la inicialización de sfContext. Puedes ver el código de sfContext::initialize() y sfContext::loadFactories() para obtener más información.

En este punto ya puedes personalizar gran parte del comportamiento de Symfony simplemente editando la configuración del archivo factories.yml. Incluso es posible reemplazar algunas clases internas de Symfony por tus propias clases de tipo factoría.

Nota Si quieres conocer más detalles de las factorías, lo mejor es que leas la Referencia de Symfony así como el contenido del propio archivo factories.yml.

Si has echado un vistazo al archivo config_factories.yml.php, habrás observado que las factorías se instancia en un determinado orden. Este orden es muy importante porque algunas factorías dependen de otras (el componente routing por ejemplo requiere el componente request para obtener la información que necesita).

Seguidamente se va a detallar el funcionamiento de la petición (request). Por defecto, las peticiones se representan mediante clases de tipo sfWebRequest. Al crear su instancia, se invoca el método sfWebRequest::initialize(), que obtiene información relevante como el método HTTP empleado y los parámetros GET y POST enviados. Si quieres modificar el procesamiento de las peticiones, puedes hacer uso del evento request.filter_parameters.

10.2.1. Utilizando el evento request.filter_parameter

Imagina que tu sitio web dispone de una API pública accesible mediante HTTP. Para que la aplicación valide la petición, cada usuario de la API debe proporcionar una clave a través de una cabecera de la petición (llamada por ejemplo X_API_KEY). Todo esto se puede conseguir fácilmente mediante el evento request.filter_parameter:

class apiConfiguration extends sfApplicationConfiguration
{
  public function configure()
  {
    // ...

    $this->dispatcher->connect('request.filter_parameters', array(
      $this, 'requestFilterParameters'
    ));
  }

  public function requestFilterParameters(sfEvent $event, $parameters)
  {
    $request = $event->getSubject();

    $api_key = $request->getHttpHeader('X_API_KEY');

    if (null === $api_key || false === $api_user = Doctrine_Core::getTable('ApiUser')->findOneByToken($api_key))
    {
      throw new RuntimeException(sprintf('Invalid api key "%s"', $api_key));
    }

    $request->setParameter('api_user', $api_user);

    return $parameters;
  }
}

Después de ejecutar el código anterior, se puede obtener el usuario de la API directamente desde la petición:

public function executeFoobar(sfWebRequest $request)
{
  $api_user = $request->getParameter('api_user');
}

Esta técnica se puede utilizar por ejemplo para validar las llamadas a los servicios web de tu aplicación.

Nota El evento request.filter_parameters incluye mucha información sobre la petición, tal y como se puede ver en la documentación de la API sobre el método sfWebRequest::getRequestContext().

La siguiente factoría importante es el enrutamiento. La inicialización del enrutamiento es muy sencilla, ya que sólo consiste en obtener y establecer una serie de opciones específicas. Si quieres también puedes modificar este proceso aprovechando el evento routing.load_configuration.

Nota El evento routing.load_configuration te da acceso a la instancia del objeto con el enrutamiento actual (por defecto es un objeto de tipo sfPatternRouting). Las rutas registradas se pueden manipular mediante diferentes métodos.

10.2.2. Ejemplo de uso del evento routing.load_configuration

Gracias a este evento es muy fácil añadir una nueva ruta en la aplicación:

public function setup()
{
  // ...

  $this->dispatcher->connect('routing.load_configuration', array(
    $this, 'listenToRoutingLoadConfiguration'
  ));
}

public function listenToRoutingLoadConfiguration(sfEvent $event)
{
  $routing = $event->getSubject();

  if (!$routing->hasRouteName('my_route'))
  {
    $routing->prependRoute('my_route', new sfRoute(
      '/my_route', array('module' => 'default', 'action' => 'foo')
    ));
  }
}

El procesamiento de las URL ocurre justo después de la inicialización mediante el método sfPatternRouting::parse(). Aunque durante este proceso intervienen varios métodos, lo único que hay que saber es que para cuando se llega al final del método parse(), se ha encontrado la ruta correcta, se ha instanciado y se le han asociado los parámetros adecuados.

Nota Si quieres conocer más sobre el enrutamiento, puedes leer el capítulo "Enrutamiento avanzado" de este mismo libro.

Una vez que se han cargado y configurado correctamente todas las factorías, se notifica el evento context.load_factories. Este evento es importante porque es el primer evento del framework en el que el programador tiene acceso a todos los objetos de las factorías de Symfony (petición, respuesta, usuario, log, bases de datos, etc.)

Este es también el punto en el que puedes conectarte a otro evento muy útil llamado template.filter_parameters. Este evento se notifica cada vez que sfPHPView procesa y muestra un archivo, por lo que permite al programador controlar los parámetros que se le pasan a la plantilla. sfContext utiliza este evento para añadir algunos parámetros útiles a cada plantilla (en concreto, $sf_context, $sf_request, $sf_params, $sf_response y $sf_user).

Por tanto, puedes hacer uso del evento template.filter_parameters para añadir parámetros globales adicionales en todas las plantillas.

10.2.3. Utilizando el evento template.filter_parameters

Imagina que todas las plantillas de tu aplicación deben tener acceso a un objeto determinado, por ejemplo un helper. En este caso, puedes añadir el siguiente código en ProjectConfiguration:

public function setup()
{
  // ...

  $this->dispatcher->connect('template.filter_parameters', array(
    $this, 'templateFilterParameters'
  ));
}

public function templateFilterParameters(sfEvent $event, $parameters)
{
  $parameters['my_helper_object'] = new MyHelperObject();

  return $parameters;
}

Ahora todas las plantillas tienen acceso a una instancia de MyHelperObject mediante la variable $my_helper_object.

10.2.4. Resumen de sfContext

  1. Inicialización de sfContext
  2. Se cargan las factorías
  3. Se notifican los siguientes eventos:
    1. request.filter_parameters
    2. routing.load_configuration
    3. context.load_factories
  4. Se añaden los parámetros globales en las plantillas