jf / container
Contenedor de dependencias PSR-11
Requires
- jf/assert: ^3.1
- psr/container: ^2.0
Requires (Dev)
None
Suggests
- jf/base: Para detectar las clases usadas como singleton
Provides
None
Conflicts
None
Replaces
None
README
Contenedor de dependencias PSR-11.
Instalación
Control de versiones
Este proyecto puede ser descargado usando git. Para ello se debe ejecutar en una consola:
git clone https://www.gitlab.com/jfphp/container.git
cd container
Composer
Este proyecto usa como gestor de dependencias Composer el cual puede ser instalado siguiendo las instrucciones especificadas en la documentación oficial del proyecto.
Para instalar el paquete jf/container usando este manejador de paquetes se debe ejecutar:
composer require jf/container
Dependencias
Se requiere la versión de PHP >=8.4.
Cuando el proyecto es instalado, adicionalmente se instalan las siguientes dependencias:
| Paquete | Versión |
|---|---|
| psr/container | ^2.0 |
Descripción
El contenedor se usa como singleton ya que no se suele usar más de un contenedor por ejecución, aunque nada impide crear uno y guardar su referencia y luego crear otro, no suele se la práctica habitual.
Para obtener el contenedor hay dos métodos:
Container::i: Crea un contenedor vacío y devuelve siempre la misma instancia.Container::fromConfig: Permite crear el contenedor e inicializarlo a partir de una configuración que puede ser leída desde un archivo y evitar tener que registrar uno por uno los valores a usar. Si se llama más de una vez se obtiene la instancia de la primera vez ignorándose la nueva configuración.
Ambos métodos devolverán la misma instancia una vez creado el contenedor.
Configuración
El contenedor extiende de la clase \ArrayObject por lo que al crearlo se le puede pasar
la configuración inicial. Posteriormente puede ser modificado agregando valores o modificando
los existentes.
Para configurarlo, la clave es un escalar cualquiera mientras que el valor puede ser un escalar o una función. Cuando se usa una función cada vez que se intente recuperar esa clave se ejecutará la función pudiendo devolver o no un nuevo valor.
Algo importante a tener en cuenta en que si no se usan funciones cada vez que se requiera al contenedor un valor con el mismo ID se devolverá siempre el mismo valor.
Configuración de valores
En el contenedor se puede almacenar cualquier tipo de valor que se quiera recuperar posteriormente,
pudiendo usarse valores escalares, array, objetos o funciones que devuelven valores usando la sintáxis
aceptada por \ArrayObject, por ejemplo:
$container['name'] = 'jf/container';
$container['dtfactory'] = fn() => new DateTimeImmutable();
Configuración usando nombres de clases
La configuración del contenedor puede aprovechar una clave llamada classnames con un mapa que tiene
el nombre de la interfaz como clave y como valor la clase que la implementa. Esto convierte al contenedor
en una herramienta más poderosa que un simple almacen de datos.
Usando este mapa se pueden crear instancias que implementan interfaces, clases abstractas y traits. Un uso
podría ser cuando se tienen diversos repositorios que implementan una interfaz, digamos IRepository y en
función de un entorno se quiere escoger uno u otro, MySqlRepository o RedisRepository, las clases que
hagan uso del repositorio pedirán al contenedor la interfaz IRepository pero al momento de configurarlo
se decidirá cual repositorio usar.
Esta manera de configurar el contenedor también permite que se pueda definir una clase abstracta o un trait para gestionar la solicitud al contenedor de un elemento que implemente esa interfaz y se devolverá una instancia mediante una clase anónima que extienda de la clase abstracta o use el trait pero que implemente la interfaz solicitada.
En resumen, usando la opción classnames tenemos un mapa donde los nombres de las clave son interfaces y el
valor pueden ser nombres cualificados de traits, clases abstractas o clases que puedan ser instanciadas donde
se devolverá una instancia y se validará que se implemente dicha interfaz teniendo la seguridad que el objeto
que se devuelva podrá ser usado como se espera.
$container = Container::fromConfig([
'classnames' => [
IApp::class => MyApp::class, // <-- Se devuelve una instancia de `MyApp` la cual debe implementar `IApp`.
IDatabase::class => fn() => new Db(), // <-- Se ejecuta la función que devuelve una instacia de `Db` diferente o no.
IAssign::class => AAsign::class, // <-- Clase anónima que extiende de `AAsign` e implementa `IAssign`.
IId::class => TId::class, // <-- Clase anónima que usa el trait `TId` e implementa la interfaz `IId`.
]
]);
En los dos últimos casos es necesario que la clase abstracta y/o el trait no tenga métodos abstractos e implemente
todos los métodos de la interfaz para evitar un error fatal de PHP ya que no pueden ser interceptados en un
try...catch.
Inyección de parámetros
El contenedor intentará resolver todos los parámetros presentes en los constructores de las clases. Para ello es necesario que los tipos de datos esperados en el constructor o sus nombres estén registrados para que puedan ser inyectados.
Todos los parámetros opcionales son omitidos, solamente se resuelven aquellos que no tienen un valor.
class MyClass
{
public function __construct(IApp $app, IDatabase $db, string $username)
{
// ...
}
}
// ...
// Al contenedor configurado en la sección anterior ahora le agregamos el parámetro `username` para que pueda inyectarlo
// ...
$container['username'] = 'jf';
// ...
$instance = $container->get(MyClass::class); // Deberíamos tener una instancia configurada de MyClass.
El resolutor de los parámetros está abstraído del contenedor para que pueda ser usado independientemente (al igual que el resto de resolutores) para ejecutar funciones en las que se pueda resolver los argumentos requeridos:
function loadUsers(IUserRepository $repository) : UsersCollection
{
return $repository->findAll();
}
// ...
$container[IUserRepository::class] = SqliteUserRepository::class;
// ...
$users = new Parameters($container)->resolve(UsersCollection::class, 'loadUsers');
$users = new Parameters($container)->resolve(UsersCollection::class, loadUsers(...));
El código anterior ejecutará la función loadUsers y verificará que el valor devuelto sea una instancia de
UsersCollection.
Si en algún momento se cambia el tipo de valor devuelto se lanzará una excepción que permitirá encontrar el
problema.
El segundo argumento puede ser cualquier tipo de función siempre que el reflector de PHP pueda devolver la información de los parámetros que requiere.
Archivos
Clases
| Nombre | Descripción |
|---|---|
| 📖 jf\Container\Container | Contenedor para inyección de dependencias que implementa la interfaz Psr\Container\ContainerInterface. |
| 📖 jf\Container\Exception\Container | Excepción genérica del contenedor. |
| 📖 jf\Container\Exception\NotFound | Excepción lanzada cuando no se encuentra el identificador para obtener el valor del contenedor. |
| 📖 jf\Container\Exception\Type | Excepción lanzada cuando se detecta algún valor incorrecto para el perámetro de un método. |
| 📖 jf\Container\Parameters | Construye los valores de los parámetros de una función o método. |
| 📖 jf\Container\Resolver\AResolver | Clase base para los resolutores de los identificadores usados en el contenedor. |
| 📖 jf\Container\Resolver\Closure | Resuelve la instancia usando reflexión. |
| 📖 jf\Container\Resolver\Interfaces | Construye una instancia determinando si es una interface y si la clase sugerida la implementa. |
| 📖 jf\Container\Resolver\Reflection | Resuelve la instancia usando reflexión. |
| 📖 jf\Container\Resolver\Singleton | Verifica si la clase es un singleton provisto por el paquete jf/base y lo devuelve. |
| 📖 jf\Container\Resolvers | Gestor de los resolutores de nombre. |
Interfaces
| Nombre | Descripción |
|---|---|
| 📖 jf\Container\Resolver\IResolver | Interface para los resolutores de nombres. |
Diagrama
Generado con ❤️ usando jfGeneratorDoc 0.1.0