Pimple es seguramente el contenedor de servicios más sencillo del mundo. Utiliza los closures de PHP de forma intensiva e implementa la interfaz ArrayAccess.

Vamos a empezar a explicar su funcionamiento creando una nueva instancia de Pimple. Ten en cuenta que la clase Silex\Application extiende de Pimple, por lo que todo lo que veamos también se aplica a Silex:

$container = new Pimple();

El siguiente código es equivalente:

$app = new Silex\Application();

5.2.1. Parámetros

Los parámetros (que normalmente son cadenas de texto) se configuran mediante claves del array que representa al contenedor:

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

La clave del array puede ser cualquier cadena de texto, pero por convención se utilizan puntos para conseguir una especie de parámetros con namespace:

$app['asset.host'] = 'http://cdn.mysite.com/';

Para obtener el valor de cualquier parámetro se utiliza la misma sintaxis:

echo $app['mi_parametro'];

5.2.2. Definiendo servicios

Definir servicios es bastante parecido a definir parámetros. Simplemente tienes que asignar un closure de PHP a una clave del array del contenedor. Cuando la aplicación acceda a esa clave, se ejecutará el código del closure. Si no se accede al servicio, no se ejecuta el código y por tanto no se penaliza el rendimiento. Este comportamiento se conoce como "creación de servicios bajo demanda":

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

Para obtener el servicio, y por tanto para ejecutar el closure, utiliza lo siguiente:

$service = $app['mi_servicio'];

Cada vez que haces una llamada a $app['mi_servicio'] la aplicación crea una nueva instancia del servicio.

5.2.3. Servicios compartidos

Resulta muy común poder utilizar la misma instancia de un servicio una y otra vez en la aplicación. Para hacerlo, y así mejorar el rendimiento de tu aplicación, debes definir el servicio como compartido utilizando el siguiente código:

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

Este código hace que se cree el servicio la primera vez que se accede al servicio. Esta misma instancia se reutiliza siempre que la aplicación haga uso de este servicio.

5.2.4. Accediendo al contenedor desde el closure

También resulta muy habitual tener que acceder al propio contenedor de servicios desde el closure que define el código de un servicio. Un ejemplo común es el de tener que obtener los otros servicios de los que depende un servicio.

Por este motivo, el contenedor se pasa a la función del closure como primer argumento:

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

El código anterior es un buen ejemplo de inyección de dependencias. El servicio mi_servicio depende de otro_servicio y utiliza las opciones de configuración guardadas en otro_servicio.config. La dependencia solo se crea cuando se utiliza mi_servicio y se puede modificar fácilmente cualquier dependencia con tan solo cambiar la definición del servicio y de las opciones de configuración.

Nota Esta técnica también funciona para los servicios compartidos.

Volviendo al ejemplo original de este capítulo, esta es la forma de usar el contenedor para manejar las dependencias:

$app['user.persist_path'] = '/tmp/users';
$app['user.persister'] = $app->share(function ($app) {
    return new JsonUserPersister($app['user.persist_path']);
});

5.2.5. Closures protegidos

Como el contenedor trata a los closures como factorías de servicios, siempre ejecuta su código cuando accede a ellos.

No obstante, en algunos casos puede que necesites guardar un closure como si fuera un parámetro normal, para poder acceder después a el y ejecutarlo a mano y con tus propios argumentos.

Este es el motivo por el que Pimple permite proteger los closures para evitar que se ejecuten. Para ello, registra el closure con el método protect():

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

// no se ejecuta el código del closure
$suma = $app['closure_protegido'];

// ahora si que se ejecuta el closure
echo $suma(2, 3);

Los closures protegidos no tienen acceso al contenedor.