Este foro ya no está activo, así que no puedes publicar nuevas preguntas ni responder a las preguntas existentes.

Detener servicios usando Pimple como contenedor

11 de marzo de 2015

Hola,

Bueno, creo que es difícil plantear la pregunta, vamos allá, (a ver si alguien se atreve a leerlo).

Estoy usando Pimple como contenedor de servicios en un micro-framework de creación propia donde existen proveedores de servicios (sesiones, base de datos,...), los cuales implementan la interfaz Pimple\ServiceProviderInterface, y se registran como es habitual en Pimple. Hasta aquí todo según lo habitual o según las especificaciones conocidas.

Ahora bien, tengo dudas de cómo implementar una acción (un método Kernel->terminate()) que sepa qué servicios están arrancados y detener cada uno de ellos, con un método común o $provider::shutdown() método que he añadido a la interfaz Pimple\ServiceProviderInterface para que se implemente en cada proveedor de servicios.

El problema es que en el método Pimple->register, el proveedor de servicio se consume una vez se registra y nada vuelve a saberse de él en todo el Framework. Recordemos:

# Pimple\Container.php

/**
     * Registers a service provider.
     *
     * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
     * @param array                    $values   An array of values that customizes the provider
     *
     * @return static
     */
    public function register(ServiceProviderInterface $provider, array $values = array())
    {
        $provider->register($this);
        foreach ($values as $key => $value) {
            $this[$key] = $value;
        }
        // (1) Añadimos lo siguiente para mantener los providers de servicios que han sido arrancados:
        $this['providers'][] = get_class($provider) 
        // Una vez salimos de aquí, el provider es consumido y nada más vuelve a saberse
        return $this;
    }

Lo que se me ocurre para poder llamar al método shutdown() de cada proveedor de servicio es almacenarlo en el contenedor una vez se ha registrado: comentario (1), de esa manera puedo saber en cualquier momento la lista de servicios arrancados y además tener acceso a su proveedor.

Para hacerlo más ligero, el método shutdown() puede ser estático y no necesitamos almacenar en la lista de providers referencias al objeto (basta su nombre), para luego en el método Kernel->terminate() simplemente hacer:

#Kernel->terminate

foreach ($this->container['providers'] as $provider ) {
    $provider::shutdown($this->container);
}

Pero me parece una solución poco elegante (sobre todo porque hemos modificado el componente Pimple). Me gustaría saber si alguien hacer esto de manera más apropiada.

Gracias y un saludo.


Respuestas

#1

Buenas, por curiosidad ¿a qué te refieres con detener servicios?

Por otro lado, ¿Conoces Silex? Creo que es una exelente base para realizar proyectos más a medida de cada quien.

Aun así, creo que podrías intentar algo como extender la clase Pimple/Container por una clase Propia:

Class MyContainer extends Pimple\Container
{
    private $providers = array();
 
    public function register(MyServiceProviderWithShutDownMethodInterface $provider, array $values = array())
    {
        $this->providers[] = $provider;
 
        return parent::register($provider, $values);
    }
 
    public function terminate()
    {
        foreach($this->providers as $provider)
        {
            $provider->shutdown($this);
        }
    }
}

Espero que te sirva, Saludos!

@manuel_j555

11 marzo 2015, 18:39
#2

Hola Manuel, gracias por tu respuesta

Por detener servicios me refiero a por ejemplo, cerrar la conexión con base de datos, enviar emails en cola, almacenar valores de sesión en bbdd etc. Sí conozco Silex, y lo uso. También Symfony2. Pero para algunas cosas me gusta todavía tener proyectos más pequeños, con menos componentes y menos dependencias/actualizaciones que controlar.

Tu respuesta me parece tan buena como sencilla, muchas veces lo más sencillo es lo más difícil de ver. Extender la clase del contenedor para llevar la lista y definir ahí el método terminate().

Otra opción también puede ser que la lista de proveedores sea un servicio del contenedor, y que en cada proveedor_servicio->register() obligatoriamente se inserte su nombre de clase a la lista. (No hace falta almacenar el objeto del proveedor porque en realidad shutdown() es de naturaleza estática, basta con el nombre de la clase)

Un saludo y gracias de nuevo por tu tiempo.

@mabuitragor

11 marzo 2015, 19:04
#3

No se que componentes estás implementando en el framework que construyes, pero si usas el componente HttpKernel, el cual hace uso del EventDispatcher, puede que no necesites que los providers ejecuten código al terminar la petición, sino que más bien dejarle ese trabajo al dispatcher y a listeners específicos.

Algo como:

public function register(Pimple/Container $pimple)
{
    $pimple->extend('dispatcher', function($dispatcher, $container){
        $dispatcher->addEventListener(
            KernelEvents::TERMINATE, 
            array($container['mailer_listener'], 'sendEmails')
        );
 
        return $dispatcher;
    }
}

Así puedes quitarle a los providers la responsabilidad de ejecutar código que no tiene que ver con proveer servicios al contenedor.

Saludos!

@manuel_j555

12 marzo 2015, 3:58
#4

Hola!

Como componentes incluye sólo HttpFoundation y Pimple. Y clases que he creado muy básicas: Kernel, Controller, Template, Router, Config, Bundle (fácilmente te puedes imaginar la responsabilidad de cada una). Los proveedores de servicio típicos. Y ya está.

No hay programación de eventos, no llega a ese nivel de complejidad. Si necesito llegar ahí lo que hago es usar Symfony.

Gracias por el aporte de como incluirlo en un servicio despachador de eventos. Quien sabe, si tengo tiempo suficiente a lo mejor trato de incluirlo.

Gracias de nuevo y un saludo.

@mabuitragor

12 marzo 2015, 9:49