Inyector de Dependencias / Localizador de Servicios

Resumen
Phalcon\Di\Di is a container that stores services or components (classes). 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. Using this approach, we can inject the database connection to any component that requires it, using the setter. Again this is a perfectly valid implementation, but it does have some shortcomings. 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()
;
In the above example we changed the Registry
class, exposing getNewConnection
which creates a brand-new database connection. 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\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\Di is a component implementing Dependency Injection and a Service Locator. Since Phalcon is highly decoupled, Phalcon\Di\Di is essential to integrate the different components of the 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. It also implements the Inversion of Control pattern. 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
Attempts to register a service in the services’ container. 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 según 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 una 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)
Load services from a php config file.
// /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
)
Load services from a yaml file.
// /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á registrando usando la sintaxis vector
public function offsetSet(mixed $name, mixed $definition)
Permite registrar un servicio compartido usando la sintaxis vector
$container["request"] = new \Phalcon\Http\Request();
public function offsetUnset(mixed $name)
Elimina un servicio del contenedor de servicios usando la sintaxis 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)
Removes a service in the services’ container. También elimina cualquier instancia compartida creada del servicio
public static function reset()
Resetea el DI interno predeterminado
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)
Set a default dependency injection container
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
Registers an always shared service in the services container
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.
This approach offers the following advantages:
- We can easily replace a component with one created by ourselves or a third party.
- We have full control of the object initialization, allowing us to set these objects as needed before delivering them to components.
- We can get global instances of components in a structured and unified way.
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
This type expects the name of a valid class, returning an object of the specified class, if the class is not loaded it will be instantiated using an autoloader. 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
This method offers greater freedom to build the dependency as desired, however, it is difficult to change some parameters externally without having to completely change the definition of dependency:
<?php
use Phalcon\Db\Adapter\Pdo\Mysql;
$container->set(
'db',
function () {
return new Mysql(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'tutorial',
]
);
}
);
Some limitations can be overcome by passing additional variables to the closure’s environment:
<?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\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,
]
);
}
);
NOTE: $this
can be used inside a closure
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
In addition, by using the array syntax you can use three types of dependency injection:
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\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.
NOTE: Phalcon\Di\Di offers lazy loading for every service it stores. 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\Config
shared: true
<?php
use Phalcon\Di\Di;
$container = new Di();
$container->loadFromYaml('services.yml');
$container->get('/app/config/services.yml');
NOTE: This approach requires that the module Yaml be installed. Please refer to this document for more information.
PHP
También puede cargar servicios usando un vector PHP:
// /app/config/services.php
use Phalcon\Config\Config;
return [
'config' => [
'className' => Config::class,
'shared' => true,
],
];
<?php
use Phalcon\Di\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\Di is able to send events to an EventsManager if it is present. 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();
NOTE: If a service is not registered as shared, and you want to ensure that a shared instance will be accessed every time the service is retrieved from the DI, you can use the getShared
method
$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;
$container->set('request', 'Phalcon\Http\Request');
$requestService = $container->getService('request');
$requestService->setDefinition(
function () {
return new Request();
}
);
$requestService->setShared(true);
$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');
In the above example we are replacing the IndexController
with another component of our choosing. Also, you can adjust your code to always instantiate your classes using the service container, even if they are not registered as services. 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. To take advantage of this, all you need is to implement the Phalcon\Di\InjectionAwareInterface in your classes:
<?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;
}
}
Then, once the service is resolved, the $container
will be passed to setDi()
automatically:
<?php
$container->set('inv-component', InvoiceComponent::class);
$invoiceComponent = $container->get('inv-component');
NOTE $invoiceComponent->setDi($container)
is automatically called
For your convenience you can also extend the Phalcon\Di\AbstractInjectionAware class which contains the above code and exposes the protected $container
property for you to use.
<?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
The Phalcon\Di\Di offers the convenient getDefault()
static method, which returns the latest container created. Esto le permite acceder al contenedor incluso desde clases estáticas:
<?php
use Phalcon\Di\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. Each file will contain a class or Provider
that implements the 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\Config;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\Di\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\Config;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\Di\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
namespace MyApp\Providers;
use Phalcon\Di\DiInterface;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\Logger\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,
]
);
}
);
}
}
Now we can register all the services with a simple loop:
<?php
use Phalcon\Di\Di;
$services = include('app/config/providers.php');
$container = new Di();
foreach ($services as $service) {
$container->register(new $service());
}
Factory por defecto
For convenience to developers, the Phalcon\Di\FactoryDefault is available with several preset services for you. Nada le impide registrar todos los servicios que necesite su aplicación uno por uno. However, you can use the Phalcon\Di\FactoryDefault, which contains a list of services ready to be used. La lista de servicios registrados le permite tener un contenedor adecuado para una aplicación full stack.
NOTE Since the services are always lazy loaded, instantiating the Phalcon\Di\FactoryDefault container will not consume more memory than a Phalcon\Di\Di one.
<?php
use Phalcon\Di\FactoryDefault;
$container = new FactoryDefault();
The services registered in the Phalcon\Di\FactoryDefault are:
If certain components are registered (such as a database connection) they are used internally with the following names:
Nombre |
Objeto |
Compartido |
Descripción |
db |
Phalcon\Db |
Si |
Conexión de base de datos |
modelsCache |
|
|
Motor de caché para modelos |
session |
|
|
Servicio de Sesiones |
sessionBag |
Phalcon\Session\Bag |
Si |
Servicio de bolsa de sesión |
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
Any exceptions thrown in the DI container will be either Phalcon\Di\Exception or Phalcon\Di\ServiceResolutionException. Puede usar esta excepción para capturar selectivamente sólo las excepciones lanzadas desde este componente.
<?php
use Phalcon\Di\Di;
use Phalcon\Di\Exception;
try {
$container = new Di();
$component = $container->get('unknown-service');
} catch (Exception $ex) {
echo $ex->getMessage();
}
Personalizado
The Phalcon\Di\DiInterface interface must be implemented to create your own DI replacing the one provided by Phalcon or extend the current one. You can also utilize the Phalcon\Di\ServiceInterface to create your own implementations of services and how they resolve in the DI container.