Secciones

Inyector de Dependencias / Localizador de Servicios


Resumen

Phalcon\Di es un contenedor que almacena servicios o componentes (clases). Estos servicios están disponibles desde la aplicación y facilitan el desarrollo. Supongamos que estamos desarrollando un componente llamado InvoiceComponent que realiza algunos cálculos para la factura de un cliente. Requiere una conexión de base de datos para obtener el registro Invoice desde la base de datos.

Nuestro componente se puede implementar de la siguiente manera:

<?php

use Phalcon\Db\Adapter\Mysql;

class InvoiceComponent
{
    public function calculate()
    {
        $connection = new Mysql(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );

        $invoice = $connection->exec(
            'SELECT * FROM Invoices WHERE inv_id = 1'
        );

        // ...
    }
}

$invoice = new InvoiceComponent();
$invoice->calculate();

Usamos el método calculate para obtener nuestros datos. Dentro del método, creamos una nueva conexión de base de datos a MySQL con las credenciales y después ejecutamos la consulta. Aunque esta es una implementación perfectamente válida, no es práctica y dificultará el mantenimiento de nuestra aplicación tarde o temprano, debido al hecho de que nuestros parámetros de conexión o el tipo de base de datos están codificados en el propio componente. Si en el futuro necesitamos cambiarlos, tendremos que cambiarlos en este componente y cualquier otro componente diseñado de esta manera.

<?php

use Phalcon\Db\Adapter\Mysql;

class InvoiceComponent
{
    private $connection;

    public function calculate()
    {
        $invoice = $this
            ->connection
            ->exec(
                'SELECT * FROM Invoices WHERE inv_id = 1'
            )
        ;

        // ...
    }

    public function setConnection(
        Mysql $connection
    ): InvoiceComponent {
        $this->connection = $connection;

        return $this;
    }
}

$connection = new Mysql(
    [
        'host'     => 'localhost',
        'username' => 'root',
        'password' => 'secret',
        'dbname'   => 'tutorial',
    ]
);

$invoice = new InvoiceComponent();
$invoice
    ->setConnection($connection)
    ->calculate()
;

Para mejorar la flexibilidad, podríamos crear la conexión a la base de datos fuera del componente, y pasarla a InvoiceComponent usando un setter. Usando este enfoque, podemos inyectar la conexión de base de datos a cualquier componente que lo requiera usando el setter. Una vez más, esta implementación es perfectamente válida pero tiene algunas deficiencias. Por ejemplo, necesitaremos construir la conexión a la base de datos cada vez que necesitemos usar cualquiera de nuestros componentes que requieran conectividad con la base de datos.

Para centralizar esta funcionalidad, podemos implementar una patrón de registro global y almacenar ahí el objeto de la conexión. Después podemos reutilizarlo donde lo necesitemos.

<?php

use Phalcon\Db\Adapter\Mysql;

class Registry
{
    public static function getConnection(): Mysql
    {
        return new Mysql(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    }
}

class InvoiceComponent
{
    private $connection;

    public function calculate()
    {
        $invoice = $this
            ->connection
            ->exec(
                'SELECT * FROM Invoices WHERE inv_id = 1'
            )
        ;

        // ...
    }

    public function setConnection(
        Mysql $connection
    ): InvoiceComponent {
        $this->connection = $connection;

        return $this;
    }
}

$invoice = new InvoiceComponent();
$invoice
    ->setConnection(Registry::getConnection())
    ->calculate()
;

La implementación anterior creará una nueva conexión cada vez que llamemos a getConnection del componente Registry. Para abordar este problema, podemos modificar nuestra clase Registry para almacenar la conexión con la base de datos y reutilizarla.

<?php

use Phalcon\Db\Adapter\Mysql;

class Registry
{
    protected static $connection;

    public static function getNewConnection(): Mysql
    {
        return self::createConnection();
    }

    public static function getSharedConnection(): Mysql
    {
        if (self::$connection === null) {
            self::$connection = self::createConnection();
        }

        return self::$connection;
    }

    protected static function createConnection(): Mysql
    {
        return new Mysql(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tuturial',
            ]
        );
    }
}


class InvoiceComponent
{
    private $connection;

    public function calculate()
    {
        $invoice = $this
            ->connection
            ->exec(
                'SELECT * FROM Invoices WHERE inv_id = 1'
            )
        ;

        // ...
    }

    public function setConnection(
        Mysql $connection
    ): InvoiceComponent {
        $this->connection = $connection;

        return $this;
    }
}

$invoice = new InvoiceComponent();
$invoice
    ->setConnection(Registry::getSharedConnection())
    ->calculate()
;

$invoice = new InvoiceComponent();
$invoice
    ->setConnection(Registry::getNewConnection())
    ->calculate()
;

En el ejemplo anterior cambiamos la clase Registry, exponiendo getNewConnection que crea una nueva conexión con la base de datos. También expone getSharedConnection que almacenará la conexión internamente y la reutilizará para cualquier otro componente que la requiera.

Inyectar dependencias a nuestros componentes soluciona los problemas descritos anteriormente. Pasar dependientes como argumento en vez de crearlas internamente en los métodos hace nuestro código más mantenible y desacoplado. Sin embargo, a largo plazo, esta forma de inyección de dependencias tiene algunas desventajas. Si por ejemplo el componente tiene muchas dependencias, necesitaremos crear múltiples argumentos setter para pasar las dependencias o crear un constructor que se usará para pasar todas las dependencias requeridas como argumentos. También necesitaríamos crear esas dependencias antes de usar el componente. Esto hace que nuestro código no sea tan mantenible como nos gustaría:

<?php

$connection = new Connection();
$fileSystem = new FileSystem();
$filter     = new Filter();
$selector   = new Selector();
$session    = new Session();

$invoice =  new InvoiceComponent(
    $connection, 
    $session, 
    $fileSystem, 
    $filter, 
    $selector
);

$invoice
    ->setConnection($connection)
    ->setFileSystem($fileSystem)
    ->setFilter($filter)
    ->setSelector($selector)
    ->setSession($session)
;

El problema de la mantenibilidad sigue estando. Si tenemos que crear este objeto en muchas partes de la aplicación, necesitamos realizar la misma inicialización, inyectando todas las dependencias. Si en el futuro necesitamos cambiar cualquiera de nuestros componentes para añadir nuevas dependencias tenemos que recorrer todas los sitios donde hayamos usado este componente u otros para ajustar nuestro código. Para solventar este problema, usaremos la clase de registro global para crear el componente. Sin embargo, este enfoque añade una capa más de abstracción antes de crear el objeto:

<?php

class InvoiceComponent
{
    private $connection;
    private $fileSystem;
    private $filter;
    private $selector;
    private $session;

    public function __construct(
        Connection $connection,
        FileSystem $fileSystem,
        Filter $filter,
        Selector $selector,
        Session $session

    ) {
        $this->connection = $connection;
        $this->fileSystem = $fileSystem;
        $this->filter     = $filter;
        $this->selector   = $selector;
        $this->session    = $session;
    }

    public static function factory()
    {
        $connection = new Connection();
        $fileSystem = new FileSystem();
        $filter     = new Filter();
        $selector   = new Selector();
        $session    = new Session();

        return new self(
            $connection, 
            $fileSystem, 
            $filter, 
            $selector,
            $session 
        );
    }
}

Ahora volvemos donde empezamos, instanciando las dependencias dentro del componente. Para solucionar este problema usaremos un contenedor que pueda almacenar todas nuestras dependencias. Es una forma práctica y elegante. El contenedor actuará como el registro global que investigamos anteriormente. Usando este contenedor como puente para recuperar cualquier dependencia, nos permite reducir la complejidad de nuestro componente:

<?php

use Phalcon\Db\Adapter\Mysql;
use Phalcon\Di;
use Phalcon\Di\DiInterface;

class InvoiceComponent
{
    protected $container;

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

    public function calculate()
    {
        $connection = $this
            ->container
            ->get('db')
        ;
    }

    public function view($id)
    {
        $filter = $this
            ->container
            ->get('filter')
        ;

        $id = $filter->sanitize($id, null, 'int');

        $connection = $this
            ->container
            ->getShared('db')
        ;
    }
}

$container = new Di();
$container->set(
    'db',
    function () {
        return new Mysql(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    }
);

$container->set(
    'filter',
    function () {
        return new Filter();
    }
);

$container->set(
    'session',
    function () {
        return new Session();
    }
);

$invoice =  new InvoiceComponent($container);
$invoice->calculate();

El componente ahora puede simplemente acceder a las dependencias cuando las necesite, si lo requiere. Si una dependencia no se necesita, no será inicializada asegurando un mínimo uso de memoria. Ahora nuestro componente es altamente desacoplado. Por ejemplo, si cambiamos la conexión con la base de datos de alguna manera, no afectará al componente, en lo que se refiere al mantenimiento, sólo necesitamos cambiar el código en un lugar.

Phalcon\Di es un componente que implementa la Inyección de Dependencias y un Localizador de Servicio. Ya que Phalcon es altamente desacoplado, Phalcon\Di es esencial para integrar los diferentes componentes del framework. El desarrollador puede usar también este componente para inyectar dependencias y gestionar instancias globales de las diferentes clases usadas en la aplicación. También implementa el patrón Inversión de Control. Debido a esto, los objetos no reciben sus dependencias usando setters o constructores, sino solicitando un inyector de dependencias de servicio. Esto reduce la complejidad general, ya que sólo hay una manera de obtener las dependencias requeridas desde un componente.

Además, este patrón incrementa la testabilidad en el código, ya que lo hace menos propenso a errores.

Métodos

public function __call(
    string $method, 
    array $arguments = []
): mixed | null

Método mágico para obtener o establecer servicios usando setters/getters

public function attempt(
    string $name, 
    mixed definition, 
    bool shared = false
): ServiceInterface | bool

Intenta registrar un servicio en el contenedor de servicios. Sólo será correcto si no ha sido registrado previamente ningún servicio con el mismo nombre

public function get(
    string $name, 
    mixed parameters = null
): mixed

Resuelve el servicio basado en su configuración

public static function getDefault(): DiInterface | null

Devuelve el último DI creado

public function getInternalEventsManager(): ManagerInterface

Devuelve el Gestor de Eventos interno

public function getRaw(string $name): mixed

Devuelve la definición de servicio sin resolver

public function getService(string $name): ServiceInterface

Devuelve una instancia Phalcon\Di\Service

public function getServices(): ServiceInterface[]

Devuelve los servicios registrados en el DI

public function getShared( 
    string $name, 
    mixed parameters = null
): mixed

Devuelve un servicio compartido. El servicio primero se resuelve, luego el servicio resuelto se almacena en el DI. Las siguientes peticiones de este servicio devolverán la misma instancia

public function loadFromPhp(string $filePath)

Carga servicios desde un fichero de configuración php.

// /app/config/services.php
return [
     'myComponent' => [
         'className' => '\Acme\Components\MyComponent',
         'shared'    => true,
     ],
     'group'       => [
         'className' => '\Acme\Group',
         'arguments' => [
             [
                 'type'    => 'service',
                 'service' => 'myComponent',
             ],
         ],
     ],
     'user'        => [
         'className' => '\Acme\User',
     ],
];

$container->loadFromPhp("/app/config/services.php");
public function loadFromYaml(
    string $filePath, 
    array $callbacks = null
)

Carga servicios desde un fichero yaml.

// /app/config/services.yml
myComponent:
    className: \Acme\Components\MyComponent
    shared: true

group:
    className: \Acme\Group
    arguments:
        - type: service
          name: myComponent

user:
   className: \Acme\User


$container->loadFromYaml(
    "/app/config/services.yaml",
    [
        "!approot" => function ($value) {
            return dirname(__DIR__) . $value;
        }
    ]
);
public function has(string $name): bool

Comprueba si el DI contiene un servicio por un nombre

public function offsetGet(mixed $name): mixed

Obtiene un servicio compartido usando la sintaxis vector

var_dump($container["request"]);
public function offsetExists(mixed $name): bool

Comprueba si un servicio está registrado usando la sintaxis de vector

public function offsetSet(mixed $name, mixed $definition)

Permite registrar un servicio compartido usando la sintaxis de vector

$container["request"] = new \Phalcon\Http\Request();
public function offsetUnset(mixed $name)

Elimina un servicio del contenedor de servicios usando la sintaxis de vector

public function register(ServiceProviderInterface $provider)

Registra un proveedor de servicios.

use Phalcon\Di\DiInterface;
use Phalcon\Di\ServiceProviderInterface;

class SomeServiceProvider implements ServiceProviderInterface
{
    public function register(DiInterface $container)
    {
        $container->setShared(
            'service',
            function () {
                // ...
            }
        );
    }
}
public function remove(string $name)

Elimina un servicio del contenedor de servicios. También elimina cualquier instancia compartida creada del servicio

public static function reset()

Resetea el DI interno por defecto

public function set(
    string $name, 
    mixed $definition, 
    bool $shared = false
): ServiceInterface

Registra un servicio en el contenedor de servicios

public static function setDefault(<DiInterface> container)
* Establece un contenedor de inyección de dependencias por defecto para ser obtenido estáticamente
* métodos
public function setInternalEventsManager(
    ManagerInterface $eventsManager
)

Establece el gestor de eventos interno

public function setService(
    string $name, 
    ServiceInterface $rawDefinition
): ServiceInterface

Establece un servicio usando una definición Phalcon\Di\Service sin procesar

public function setShared(
    string $name, 
    mixed $definition
): ServiceInterface

Registra un servicio siempre compartido en el contenedor de servicios

Registro de Servicios

El propio framework o el desarrollador puede registrar servicios. Cuando un componente A requiere un componente B (o una instancia de su clase) para operar, puede solicitar el componente B desde el contenedor, en lugar de crear una nueva instancia del componente B.

Este enfoque ofrece las siguientes ventajas: * Podemos reemplazar fácilmente un componente por otro creado por nosotros o un tercero. * Tenemos control total de la inicialización del objeto, lo que nos permite establecer estos objetos como necesitemos antes de entregarlos a los componentes. * Podemos obtener instancias globales de los componentes de una forma estructurada y unificada.

Los servicios se pueden registrar usando varios tipos de definiciones. A continuación exploraremos las diferentes formas en las que se pueden registrar los servicios:

Cadena

Este tipo espera el nombre de una clase válida, devolviendo un objeto de la clase especificada, si la clase no está cargada será instanciada usando un cargador automático (auto-loader). Este tipo de definición no permite especificar argumentos para el constructor de la clase o parámetros:

<?php

use Phalcon\Http\Request;

$container->set(
    'request',
    Request::class
);

Instancias de Clase

Este tipo espera un objeto. Debido al hecho de que el objeto no necesita ser resuelto ya que ya es un objeto, podríamos decir que no es realmente una inyección de dependencias, sin embargo es útil si quiere forzar la dependencia devuelta para que siempre sea el mismo objeto/valor:

<?php

use Phalcon\Http\Request;

$container->set(
    'request',
    new Request()
);

Funciones Anónimas

Éste método ofrece mayor libertad para construir la dependencia como se desee, sin embargo, es difícil cambiar alguno de los parámetros externamente sin tener que cambiar completamente la definición de la dependencia:

<?php

use Phalcon\Db\Adapter\Pdo\Mysql;

$container->set(
    'db',
    function () {
        return new Mysql(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    }
);

Algunas de las limitaciones se pueden superar pasando variables adicionales al entorno de la función anónima:

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql;

$config = new Config(
    [
        'host'     => 'localhost',
        'username' => 'user',
        'password' => 'pass',
        'dbname'   => 'tutorial',
    ]
);

$container->set(
    'db',
    function () use ($config) {
        return new Mysql(
            [
                'host'     => $config->host,
                'username' => $config->username,
                'password' => $config->password,
                'dbname'   => $config->name,
            ]
        );
    }
);

También puede acceder a otros servicios del DI usando el método get():

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql;

$container->set(
    'config',
    function () {
        return new Config(
            [
                'host'     => 'localhost',
                'username' => 'user',
                'password' => 'pass',
                'dbname'   => 'tutorial',
            ]
        );
    }
);

$container->set(
    'db',
    function () {
        $config = $this->get('config');

        return new Mysql(
            [
                'host'     => $config->host,
                'username' => $config->username,
                'password' => $config->password,
                'dbname'   => $config->name,
            ]
        );
    }
);

NOTA: $this se puede usar dentro de una función anónima

Registro Complejo

Si es necesario cambiar la definición de un servicio sin instanciar/resolver el servicio, entonces, necesitamos definir los servicios usando la sintaxis de vector. Definir un servicio usando la definición de vector puede ser un poco más detallado:

<?php

use Phalcon\Annotations\Adapter\Apcu;

$container->set(
    'annotations',
    [
        'className' => Apcu::class,
        'arguments' => [
            [
                'type'  => 'parameter',
                'name'  => 'prefix',
                'value' => 'my-prefix',
            ],
            [
                'type'  => 'parameter',
                'name'  => 'lifetime',
                'value' => 3600,
            ],
        ],
    ]
);


$container->set(
    'annotations',
    function () {
        return new Apcu(
            [
                'prefix'   => 'my-prefix',
                'lifetime' => 3600,
            ]
        );
    }
);

Ambos registros de servicio anteriores producen el mismo resultado. Sin embargo, la definición de vector permite cambiar los parámetros del servicio si se necesita:

<?php

use Phalcon\Annotations\Adapter\Memory;

$container
    ->getService('annotations')
    ->setClassName(Memory::class)
;

$container
    ->getService('annotations')
    ->setParameter(
        1,
        [
            'type'  => 'parameter',
            'name'  => 'lifetime',
            'value' => 7200,
        ]
    );

Inyecciones

Además usando la sintaxis de vector puede usar tres tipos de inyección de dependencia:

Inyección de Constructor

Este tipo de inyección pasa las dependencias/argumentos a la clase constructor. Supongamos que tenemos el siguiente componente:

<?php

namespace MyApp\Http;

use Phalcon\Http\Response;

class Responder
{
    /**
     * @var Response
     */
    protected $response;

    /**
     * @var string
     */
    protected $contentType;

    public function __construct(Response $response, string $contentType)
    {
        $this->response    = $response;
        $this->contentType = $contentType;
    }
}

El servicio se puede registrar de la siguiente manera:

<?php

use MyApp\Http\Responder;
use Phalcon\Http\Response;

$container->set(
    'response',
    [
        'className' => Response::class
    ]
);

$container->set(
    'my-responder',
    [
        'className' => Responder::class,
        'arguments' => [
            [
                'type' => 'service',
                'name' => 'response',
            ],
            [
                'type'  => 'parameter',
                'value' => 'application/json',
            ],
        ]
    ]
);

El servicio response (Phalcon\Http\Response se resuelve para pasarlo como primer argumento del constructor, mientras que el segundo es un valor string que se pasa tal cual.

Inyección de Setters

Las clases pueden tener setters para inyectar dependencias opcionales, nuestra clase previa se puede cambiar para aceptar las dependencias con setters:

<?php

namespace MyApp\Http;

use Phalcon\Http\Response;

class Responder
{
    /**
     * @var Response
     */
    protected $response;

    /**
     * @var string
     */
    protected $contentType;

    public function setResponse(Response $response)
    {
        $this->response = $response;
    }

    public function setContentType($contentType)
    {
        $this->contentType = $contentType;
    }
}

La clase anterior se puede registrar como un servicio usando el getter y setter:

<?php

use MyApp\Http\Responder;
use Phalcon\Http\Response;

$container->set(
    'response',
    [
        'className' => Response::class,
    ]
);

$container->set(
    'my-responder',
    [
        'className' => Responder::class,
        'calls'     => [
            [
                'method'    => 'setResponse',
                'arguments' => [
                    [
                        'type' => 'service',
                        'name' => 'response',
                    ]
                ]
            ],
            [
                'method'    => 'setContentType',
                'arguments' => [
                    [
                        'type'  => 'parameter',
                        'value' => 'application/json',
                    ]
                ]
            ]
        ]
    ]
);

Inyección de Propiedades

Una estrategia menos común es inyectar dependencias o parámetros directamente en atributos públicos de la clase:

<?php

namespace MyApp\Http;

use Phalcon\Http\Response;

class Responder
{
    /**
     * @var Response
     */
    public $response;

    /**
     * @var string
     */
    public $contentType;
}

Un servicio con inyección de propiedades se pueden registrar de la siguiente manera:

<?php

use MyApp\Http\Responder;
use Phalcon\Http\Response;

$container->set(
    'response',
    [
        'className' => Response::class,
    ]
);

$container->set(
    'my-responder',
    [
        'className'  => Responder::class,
        'properties' => [
            [
                'name'  => 'response',
                'value' => [
                    'type' => 'service',
                    'name' => 'response',
                ],
            ],
            [
                'name'  => 'contentType',
                'value' => [
                    'type'  => 'parameter',
                    'value' => 'application/json',
                ],
            ]
        ]
    ]
);

Los tipos de parámetros soportados incluyen los siguientes:

Tipo Descripción Ejemplo
instancia Representa un objeto que se debe construir dinámicamente ['type' => 'instance', 'className' => \DateTime::class, 'arguments' => ['now']]
parámetro Representa un valor literal pasado como parámetro ['type' => 'parameter', 'value' => 1234]
servicio Representa otro servicio en el contenedor de servicios ['type' => 'service', 'name' => 'request']

Resolver un servicio cuya definición es compleja puede ser un poco más lento que las definiciones simples vistas previamente. Sin embargo, proporcionan un enfoque más robusto para definir e inyectar servicios. Se permite mezclar diferentes tipos de definiciones, y puede decidir que forma es la más apropiada para registrar los servicios de acuerdo a las necesidades de la aplicación.

Sintaxis de Vector

La sintaxis de vector también está disponible para registrar servicios:

<?php

use Phalcon\Di;
use Phalcon\Http\Request;

$container = new Di();

$container['request'] = Request::class;

$container['request'] = function () {
    return new Request();
};

$container['request'] = new Request();

$container['request'] = [
    'className' => Request::class,
];

En los ejemplos anteriores, cuando el framework necesita acceder a los datos de la petición, preguntará por el servicio identificado como request en el contenedor. El contenedor devolverá como respuesta una instancia del servicio solicitado. El componente se puede reemplazar fácilmente con una clase diferente si surge la necesidad.

Como se muestra en los ejemplos anteriores, cada una de las formas usadas para establecer/registrar un servicio tiene ventajas e inconvenientes. El desarrollador y los requisitos particulares son los que determinarán qué opción usar. Establecer un servicio mediante una cadena es simple, pero carece de flexibilidad. Establecer servicios usando un vector ofrece mucha más flexibilidad, pero hace el código más complicado. La función lambda es un buen equilibrio entre los dos, pero podría conducir a un mantenimiento mayor del que uno podría esperar.

NOTA: Phalcon\Di ofrece carga perezosa para cada servicio almacenado. A no ser que el desarrollador decida instanciar un objeto directamente y almacenarlo en el contenedor, cualquier objeto almacenado en él (vía vector, cadena, etc.) se cargará perezosamente, es decir, se instanciará sólo cuando se solicite.

Carga Desde Configuración

YAML

Esta característica cargará los servicios analizando un fichero YAML:

; /app/config/services.yml
config:
  className: \Phalcon\Config
  shared: true
<?php

use Phalcon\Di;

$container = new Di();
$container->loadFromYaml('services.yml');
$container->get('/app/config/services.yml');

NOTA: Este enfoque requiere que esté instalado el módulo Yaml. Por favor, para más información consulte este documento.

PHP

También puede cargar servicios usando un vector PHP:

// /app/config/services.php

use Phalcon\Config;

return [
    'config' => [
        'className' => Config::class,
        'shared'    => true,
    ],
];
<?php

use Phalcon\Di;

$container = new Di();
$container->loadFromPhp('/app/config/services.php');
$container->get('config');

Resolución de Servicios

Obtener un servicio del contenedor es una cuestión de llamar simplemente al método get. Se devolverá una nueva instancia del servicio:

$request = $container->get('request');

O llamando a través del método mágico:

$request = $container->getRequest();

O usando la sintaxis de acceso de vector:

$request = $container['request'];

Los argumentos se pueden pasar al constructor añadiendo un vector de parámetros al método get:

<?php

use Phalcon\Annotations\Adapter\Stream;

$annotations = $container->get(
    Stream::class,
    [
        ['annotationsDir' => 'storage/cache/annotations'],
    ]
);

Eventos

Phalcon\Di es capaz de enviar eventos a un EventsManager si está presente. Los eventos se disparan usando el tipo di.

Nombre de Evento Disparado
afterServiceResolve Disparado después de resolver el servicio. Los oyentes reciben el nombre del servicio, la instancia y los parámetros que se les pasa.
beforeServiceResolve Disparado antes de resolver el servicio. Los oyentes reciben el nombre del servicio y los parámetros que se les pasa.

Servicios Compartidos

Los servicios se pueden registrar como servicios compartidos lo que significa que siempre actuarán como [singletons][singletons]. Una vez que el servicio se resuelve por primera vez se devolverá la misma instancia de él cada vez que se recupere este servicio desde el contenedor:

<?php

use Phalcon\Session\Manager;
use Phalcon\Session\Adapter\Stream;

$container->setShared(
    'session',
    function () {
        $session = new Manager();
        $files = new Stream(
            [
                'savePath' => '/tmp',
            ]
        );
        $session->setAdapter($files);
        $session->start();

        return $session;
    }
);

$session = $container->get('session');

$session = $container->getSession();

La primera llamada a get en el contenedor resuelve el servicio y devuelve el objeto de vuelta. La llamada posterior a getSession devolverá el mismo objeto.

Una forma alternativa de registrar servicios compartidos es pasar true como tercer parámetro de set:

<?php

use Phalcon\Session\Manager;
use Phalcon\Session\Adapter\Stream;

$container->set(
    'session',
    function () {
        $session = new Manager();
        $files = new Stream(
            [
                'savePath' => '/tmp',
            ]
        );
        $session->setAdapter($files);
        $session->start();

        return $session;
    },
    true
);

$session = $container->get('session');

$session = $container->getSession();

NOTA: Si un servicio no está registrado como compartido y se quiere asegurar de que se accederá a una instancia compartida cada vez que se recupere el servicio desde el DI, puede usar el método getShared

$request = $container->getShared('request');

Manipulación de Servicios

Una vez que se registra un servicio en el contenedor de servicios, lo puede recuperar para manipularlo individualmente:

    <?php

    use Phalcon\Http\Request;

    // Register the 'request' service
    $container->set('request', 'Phalcon\Http\Request');

    // Get the service
    $requestService = $container->getService('request');

    // Change its definition
    $requestService->setDefinition(
        function () {
            return new Request();
        }
    );

    // Change it to shared
    $requestService->setShared(true);

    // Resolve the service (return a Phalcon\Http\Request instance)
    $request = $requestService->resolve();

Instanciado de Clases

Cuando solicita un servicio del contenedor, si no se puede encontrar usando el mismo nombre, intentará cargar una clase con el mismo nombre. Este comportamiento le permite reemplazar cualquier servicio por otro, simplemente registrando un servicio con el nombre común:

<?php

$container->set(
    'IndexController',
    function () {
        return new Component();
    },
    true
);

$container->set(
    'IndexController',
    function () {
        return new AnotherComponent();
    }
);

$component = $container->get('IndexController');

En el ejemplo anterior estamos reemplazando el IndexController con otro componente de nuestra elección. También puede ajustar su código para instanciar siempre sus clases usando el servicio contenedor, incluso si no están registradas como servicios. El contenedor recurrirá al autocargador que ha definido para cargar la propia clase. Al usar esta técnica, puede sustituir cualquier clase en el futuro implementando una definición diferente para ella.

Inyección Automática

Si una clase o componente necesita el propio DI para localizar servicios, el DI puede inyectarse automáticamente a las instancias que crea. Para aprovechar esto, todo lo que necesita es implementar Phalcon\Di\InjectionAwareInterface en sus clases:

<?php

use Phalcon\Di\DiInterface;
use Phalcon\Di\InjectionAwareInterface;

class InvoiceComponent implements InjectionAwareInterface
{
    /**
     * @var DiInterface
     */
    protected $container;

    public function setDi(DiInterface $container)
    {
        $this->container = $container;
    }

    public function getDi(): DiInterface
    {
        return $this->container;
    }
}

Luego, una vez que el servicio se resuelve, el $container se pasará automáticamente a setDi():

<?php

$container->set('inv-component', InvoiceComponent::class);

$invoiceComponent = $container->get('inv-component');

NOTA `: $invoiceComponent->setDi($container) se llama automáticamente)

Si le conviene también puede extender la clase Phalcon\Di\AbstractInjectionAware que contiene el código anterior y expone la propiedad protegida $container para su uso.

<?php

use Phalcon\Di\DiInterface;
use Phalcon\Di\AbstractInjectionAware;

class InvoiceComponent extends AbstractInjectionAware
{

}

Organización de Servicios en Ficheros

Puede organizar mejor su aplicación moviendo el registro de servicio a ficheros individuales en lugar de registrar todo en el arranque de la aplicación:

<?php

$container->set(
    'router',
    function () {
        return include '/app/config/routes.php';
    }
);

Entonces en el fichero ('/app/config/routes.php') devuelve el objeto resuelto:

<?php

use Phalcon\Mvc\Router();

$router = new Router(false);

$router->post('/login');

return $router;

Acceso Estático

Phalcon\Di ofrece el método estático getDefault() conveniente, que devuelve el último contenedor creado. Esto le permite acceder al contenedor incluso desde clases estáticas:

<?php

use Phalcon\Di;

class InvoicesComponent
{
    public static function calculate()
    {
        $connection = Di::getDefault()->getDb();
    }
}

Proveedores de Servicio

Otro método de registro de servicios es poniendo cada servicio en su propio fichero, y registrar todos los servicios, uno detrás de otro, con un simple bucle. Cada fichero contendrá una clase o Provider que implementa Phalcon\Di\ServiceProviderInterface. La razón por la que podría querer hacer esto es para tener ficheros pequeños, cada uno gestionando un registro de servicio que ofrecerá una gran flexibilidad, código corto y finalmente la capacidad de añadir/quitar servicios cuando lo desee, sin tener que examinar un fichero grande como el de arranque.

Ejemplo

app/config/providers.php

<?php

return [
    MyApp\Providers\ConfigProvider::class,
    MyApp\Providers\RegistryProvider::class,
    MyApp\Providers\LoggerProvider::class,
];    

app/library/Providers/ConfigProvider.php

<?php

namespace MyApp\Providers;

use Phalcon\Config;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\DiInterface;

class ConfigProvider implements ServiceProviderInterface
{
    /**
     * @param DiInterface $container
     */
    public function register(DiInterface $container)
    {
        $container->setShared(
            'config',
            function () {
                $data = require 'app/config/config.php';

                return new Config($data);
            }
        );
    }
}

app/library/Providers/RegistryProvider.php

<?php

namespace MyApp\Providers;

use Phalcon\Config;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\DiInterface;
use Phalcon\Registry;
use function microtime;

class RegistryProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     *
     * @param DiInterface $container
     */
    public function register(DiInterface $container)
    {
        /** @var Config $config */
        $config  = $container->getShared('config');
        $devMode = $config->path('app.devMode', false);

        $container->setShared(
            'registry',
            function () use ($devMode) {
                $registry = new Registry();
                $registry->offsetSet('devMode', $devMode);
                $registry->offsetSet('execution', microtime(true));

                return $registry;
            }
        );
    }
}

app/library/Providers/LoggerProvider.php ```php <?php

namespace MyApp\Providers;

use Phalcon\Di\ServiceProviderInterface; use Phalcon\DiInterface; use Phalcon\Logger; use Phalcon\Logger\Adapter\Stream;

class LoggerProvider implements ServiceProviderInterface { use LoggerTrait;

/**
 * @param DiInterface $container
 *
 * @throws \Exception
 */
public function register(DiInterface $container)
{
    $container->setShared(
        'logger', 
        function () {
            $adapter = new Stream('/storage/logs/main.log');

            return new Logger(
                'messages',
                [
                    'main' => $adapter,
                ]
            );
        }
    );
}

}

<br />Ahora podemos registrar todos los servicios con un simple bucle:

```php
<?php

use Phalcon\Di;

$services = include('app/config/providers.php');

$container = new Di();

foreach ($services as $service) {
    $container->register(new $service());
}

Factory Default

Para comodidad de los desarrolladores, está disponible Phalcon\Di\FactoryDefault con varios servicios preestablecidos para usted. Nada le impide registrar todos los servicios que necesite su aplicación uno por uno. Sin embargo, puede usar Phalcon\Di\FactoryDefault, que contiene una lista de servicios listos para usar. La lista de servicios registrados le permite tener un contenedor adecuado para una aplicación full stack.

NOTA Ya que los servicios siempre se cargan perezosamente, instanciar el contenedor Phalcon\Di\FactoryDefault no consumirá más memoria que Phalcon\Di.

<?php

use Phalcon\Di\FactoryDefault;

$container = new FactoryDefault();

Los servicios registrados en Phalcon\Di\FactoryDefault son:

Nombre Objeto Compartido Descripción
annotations Phalcon\Annotations\Adapter\Memory Si Analizador de anotaciones
assets Phalcon\Assets\Manager Si Gestor de recursos
crypt Phalcon\Crypt Si Encriptar/Desencriptar
cookies Phalcon\Http\Response\Cookies Si Gestor de Cookies HTTP
db Phalcon\Db Si Conexión de base de datos
dispatcher Phalcon\Mvc\Dispatcher Si Despachador
escaper Phalcon\Escaper Si Escapador
eventsManager Phalcon\Events\Manager Si Gestor de Eventos
flash Phalcon\Flash\Direct Si Mensajería Flash
flashSession Phalcon\Flash\Session Si Mensajería de Sesión Flash
filter Phalcon\Filter Si Filtrar / Sanear
modelsCache     Motor de caché para modelos
modelsManager Phalcon\Mvc\Model\Manager Si Gestor de Modelos
modelsMetadata Phalcon\Mvc\Model\MetaData\Memory No MetaDatos de Modelos
request Phalcon\Http\Request Si Petición HTTP
response Phalcon\Http\Response Si Respuesta HTTP
router Phalcon\Mvc\Router Si Enrutador
security Phalcon\Security Si Seguridad
session     Servicio de Sesiones
sessionBag Phalcon\Session\Bag Si Servicio de bolsa de sesión
tag Phalcon\Tag Si Ayudantes de Etiquetas HTML
transactionManager Phalcon\Mvc\Model\Transaction\Manager Si Gestor de Transacciones de Base de Datos
url Phalcon\Url Si Generación de URL

Los nombres anteriores se usan en todo el framework. Por ejemplo, el servicio db se usa desde el servicio transactionManager. Puede sustituir estos componentes por los que prefiera, simplemente registrando su componente con el mismo nombre que los listados anteriormente.

Excepciones

Cualquier excepción lanzada en el contenedor DI será Phalcon\Di\Exception o Phalcon\Di\ServiceResolutionException. Puede usar esta excepción para capturar selectivamente sólo las excepciones lanzadas desde este componente.

<?php

use Phalcon\Di;
use Phalcon\Di\Exception;

try {
    $container = new Di();
    $component = $container->get('unknown-service');
} catch (Exception $ex) {
    echo $ex->getMessage();
}

Personalización

Se debe implementar la interfaz Phalcon\Di\DiInterface para crear su propio DI que reemplace el provisto por Phalcon o extienda el actual. También puede utilizar Phalcon\Di\ServiceInterface para crear sus propias implementaciones de servicios y cómo se resuelven en el contenedor DI.