Search by

jf / container

joaquinfq

Contenedor de dependencias PSR-11

Package info

gitlab.com/jfphp/jfContainer

Homepage

pkg:composer/jf/container

Statistics

Installs: 1 066

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2024-09-12 20:08 UTC

This package is auto-updated.

Last update: 2026-08-12 22:39:58 UTC


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:

PaqueteVersió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

NombreDescripción
📖 jf\Container\ContainerContenedor para inyección de dependencias que implementa la interfaz Psr\Container\ContainerInterface.
📖 jf\Container\Exception\ContainerExcepción genérica del contenedor.
📖 jf\Container\Exception\NotFoundExcepción lanzada cuando no se encuentra el identificador para obtener el valor del contenedor.
📖 jf\Container\Exception\TypeExcepción lanzada cuando se detecta algún valor incorrecto para el perámetro de un método.
📖 jf\Container\ParametersConstruye los valores de los parámetros de una función o método.
📖 jf\Container\Resolver\AResolverClase base para los resolutores de los identificadores usados en el contenedor.
📖 jf\Container\Resolver\ClosureResuelve la instancia usando reflexión.
📖 jf\Container\Resolver\InterfacesConstruye una instancia determinando si es una interface y si la clase sugerida la implementa.
📖 jf\Container\Resolver\ReflectionResuelve la instancia usando reflexión.
📖 jf\Container\Resolver\SingletonVerifica si la clase es un singleton provisto por el paquete jf/base y lo devuelve.
📖 jf\Container\ResolversGestor de los resolutores de nombre.

Interfaces

NombreDescripción
📖 jf\Container\Resolver\IResolverInterface para los resolutores de nombres.

Diagrama

Diagrama

Generado con ❤️ usando jfGeneratorDoc 0.1.0