Servicios

Silex no es sólo una microplataforma. También es un microcontenedor de servicios. Esto lo consigue extendiendo a Pimple que ofrece el bondadoso servicio en sólo 44 NCLOC.

Inyección de dependencias

Nota

Puedes omitir esto si ya sabes lo que es la inyección de dependencias.

La inyección de dependencias es un patrón de diseño dónde pasas las dependencias a servicios en lugar de crearlas desde dentro del servicio o depender de variables globales. Esto generalmente lleva a un código disociado, reutilizable, flexible y fácil de probar.

He aquí un ejemplo de una clase que toma un objeto Usuario y lo guarda como un archivo en formato JSON:

class JsonUserPersister
{
    private $basePath;

    public function __construct($basePath)
    {
        $this->basePath = $basePath;
    }

    public function persist(User $user)
    {
        $data = $user->getAttributes();
        $json = json_encode($data);
        $filename = $this->basePath.'/'.$user->id.'.json';
        file_put_contents($filename, $json, LOCK_EX);
    }
}

En este sencillo ejemplo la dependencia es la propiedad basePath. Esta se pasa al constructor. Esto significa que puedes crear varias instancias independientes con diferentes rutas base. Por supuesto, las dependencias no tienen que ser simples cadenas de texto. Muy a menudo estas, de hecho, se encuentran en otros servicios.

Contenedor

Un IDC o contenedor de servicio es responsable de crear y almacenar servicios. Este puede crear dependencias recurrentemente de los servicios solicitados e inyectarlos. Lo hace de manera diferida, lo cual significa que un servicio sólo se crea cuando realmente se necesita.

La mayoría de los contenedores son muy complejos y se configuran a través de archivos XML o YAML.

Pimple es diferente

Pimple

Pimple, probablemente, es el más simple contenedor de servicios que hay. Usa exhaustivamente los cierres que implementan la interfaz ArrayAccess.

Vamos a empezar por crear una nueva instancia de Pimple – y puesto que Silex\application extiende a Pimple todo esto se aplica a Silex también:

$container = new Pimple();

o:

$app = new Silex\Application();

Parámetros

Puedes ajustar los parámetros (que suelen ser cadenas) estableciendo una clave en el arreglo del contenedor:

$app['algun_parametro'] = 'valor';

La clave del arreglo puede ser cualquier cosa, por convención se utilizan puntos para denominar los espacios de nombres:

$app['activo.anfitrion'] = 'http://cdn.misitio.com/';

Es posible leer valores de parámetros con la misma sintaxis:

echo $app['algun_parametro'];

Definiendo servicios

La definición de servicios no es diferente de la definición de parámetros. Sólo tienes que establecer una clave en el arreglo del contenedor a un cierre. No obstante, cuando recuperes el servicio, se ejecuta el cierre. Esto permite la creación diferida de servicios:

$app['algun_servicio'] = function () {
    return new Service();
};

Y para recuperar el servicio, utiliza:

$service = $app['algun_servicio'];

Cada vez que llames a $app['algun_servicio'], se crea una nueva instancia del servicio.

Servicios compartidos

Posiblemente quieras utilizar la misma instancia de un servicio a través de todo tu código. A fin de que puedas hacer compartido un servicio:

$app['algun_servicio'] = $app->share(function () {
    return new Service();
});

Esto creará el servicio en la primera invocación, y luego devolverá la instancia existente en cualquier acceso posterior.

Accediendo al contenedor desde un cierre

En muchos casos, desearás acceder al contenedor de servicios dentro de un cierre en la definición del servicio. Por ejemplo, al recuperar servicios de los que depende el servicio actual.

Debido a esto, el contenedor se pasa al cierre como argumento:

$app['algun_servicio'] = function ($app) {
    return new Service($app['algun_otro_servicio'], $app['algun_servicio.config']);
};

Aquí puedes ver un ejemplo de Inyección de dependencias. algun_servicio depende de algun_otro_servicio y toma algun_servicio.config como opciones de configuración. La dependencia sólo se crea cuando accedes a algun_servicio, y es posible sustituir cualquier dependencia simplemente sustituyendo esas definiciones.

Nota

Esto también trabaja para los servicios compartidos.

Cierres protegidos

Debido a que el contenedor ve los cierres cómo fábricas de servicios, siempre se deben ejecutar cuando los leas.

En algunos casos, sin embargo, deseas almacenar un cierre como un parámetro, de modo que lo puedas recuperar y ejecutar tú mismo – con tus propios argumentos.

Esta es la razón por la cual Pimple te permite proteger tus cierres de ser ejecutados, usando el método protect:

$app['closure_parameter'] = $app->protect(function ($a, $b) {
    return $a + $b;
});

// no debe ejecutar el cierre
$add = $app['closure_parameter'];

// llamándolo ahora
echo $add(2, 3);

Ten en cuenta que los cierres protegidos no tienen acceso al contenedor.

Servicios básicos

Silex define una serie de servicios que puedes utilizar o reemplazar. Probablemente no quieras meterte con la mayoría de ellos.

  • request: Contiene el objeto Petición actual, el cual es una instancia de Request. ¡Este proporciona acceso a los parámetros GET, POST y muchos más!

    Ejemplo de uso:

    $id = $app['request']->get('id');
    

    Este sólo está disponible cuando se está sirviendo la petición, sólo puedes acceder a él desde dentro de un controlador, un semiware before/after de la aplicación, o al manejar algún error.

  • routes: El RouteCollection utilizado internamente. Puedes agregar, modificar y leer rutas.

  • controllers: La Silex\ControllerCollection utilizada internamente. Consulta el capítulo Funcionamiento interno para más información.

  • dispatcher: El EventDispatcher utilizado internamente. Es el núcleo del sistema Symfony2 y se utiliza un poco en Silex.

  • resolver: El ControllerResolver utilizado internamente. Se encarga de ejecutar el controlador con los argumentos adecuados.

  • kernel: El HttpKernel utilizado internamente. El HttpKernel es el corazón de Symfony2, este toma una Petición como entrada y devuelve una Respuesta como salida.

  • request_context: El contexto de la petición es una representación simplificada de la petición que utilizan el Router y el UrlGenerator.

  • exception_handler: El controlador de excepciones es el controlador predeterminado usado cuando no registras uno a través del método error() o si el controlador no devuelve una Respuesta. Desactivalo con $app['exception_handler']->disable().

  • logger: Una instancia de LoggerInterface. De forma predeterminada, la anotación cronológica de eventos está desactivada ya que el valor se establece en null. Cuando el puente Monolog de Symfony2 está instalado, se utiliza Monolog automáticamente como el registro cronológico predeterminado.

Nota

Todos estos servicios básicos de Silex son compartidos.

Parámetros básicos

  • request.http_port (opcional): Te permite redefinir el puerto predeterminado para direcciones que no sean HTTPS. Si la petición actual es HTTP, siempre utiliza el puerto actual.

    El predeterminado es 80.

    Este parámetro lo puede utilizar el UrlGeneratorProvider.

  • request.https_port (opcional): Te permite redefinir el puerto predeterminado para direcciones que no sean HTTPS. Si la petición actual es HTTPS, siempre usará el puerto actual.

    Predeterminado a 443.

    Este parámetro lo puede utilizar el UrlGeneratorProvider.

  • locale (opcional): La región del usuario. Cuando se establece antes de manipular cualquier petición, esta define el entorno regional predeterminado (en por omisión). Cuando se está manejando la petición, automáticamente se ajusta de acuerdo al atributo _locale de la ruta actual.

  • debug (opcional): Indica si o no se ejecuta la aplicación en modo de depuración.

    El valor predeterminado es false.

  • charset (opcional): El juego de caracteres a usar para las Respuestas.

    Por omisión es UTF-8.

Bifúrcame en GitHub