wlib/dibox

DiBox is a Dependency Injection Container.

v1.2.0 2023-12-17 23:12 UTC

This package is auto-updated.

Last update: 2024-05-18 00:08:52 UTC


README

DiBox est un conteneur d'injection de dépendances. Il suit la directive PSR-11.

Installation

composer require wlib/dibox

Utilisation

use wlib\Di\DiBox;

$box = new DiBox();

Instancier vos objets

Le conteneur peut servir pour simplement instancier vos objets :

// Créer une instance de Foo\Bar
$bar = $box->make(Foo\Bar::class);

// Passer des arguments au constructeur
$bar = $box->make(Foo\Bar::class, ['a', 'b']); // indexés
$bar = $box->make(Foo\Bar::class, ['sGreetings' => 'Hello']); // nommés, ici, le constructeur attend l'entrée `$sGreetings`

L'avantage de passer par le conteneur est de disposer de la résolution automatique des classes que vos constructeurs attendent :

class Bar { public function __construct(public Baz $baz); }
class Baz {}

$bar = $box->make(Bar::class);

// $bar->baz sera une instance de Baz

Déclarer vos dépendances

Instancier c'est déjà pas mal, mais définir des dépendances c'est encore mieux, surtout pour construire votre application :

Dépendance directe

Pas forcément le plus utile, mais ça fonctionne :

$box->bind(Foo\Bar::class);
$bar = $box->get(Foo\Bar::class); // instance de Foo\Bar

Créer un alias

Pour mettre des surnoms à tout le monde (ou mettre de l'ordre dans votre conteneur, c'est selon) :

$box->bind('MyBar', Foo\Bar::class);
$bar = $box->get('MyBar'); // toujours une instance de Foo\Bar mais depuis son p'tit nom

Instance existante

Si besoin :

$bar = new Foo\Bar();
$box->bind('MyBar', $bar);
$samebar = $box->get('MyBar'); // toujours pareil !

Fermeture de création

Ou Closure pour les initiés ;-). C'est là que les choses sérieuses commencent et que ça devient intéressant :

$box->bind('MyBaz', function($box, $args)
{
	return new Foo\Baz($box->get('MyBar'));
});

$baz = $box->get('MyBaz'); // instance de Foo\Baz prête à bosser, classe non ?

Vous voyez donc l'étendue des possibles qui s'offre devant vos yeux ébahis !

Valeurs scalaires

Qui peut le plus, peut le moins, vous pouvez aussi stocker de simples valeurs dans le conteneur :

$box->bind('one.string', 'DiBox has all of a great one !');
$box->bind('one.integer', 2023);
$box->bind('one.array', [1, 2, 3]);

echo $box->get('one.string'));  // Affiche "DiBox has ...')
echo $box->get('one.integer')); // Affiche "2023"
echo $box->get('one.array'));   // Attention piège, ça affiche quoi ?

Mode "ArrayAccess"

Le conteneur implémente l'interface ArrayAccess :

$box['make.b']   = function ($box, $args) { return new B(new A()); };
$box['integer']  = 4046;
$box['list']     = ['a', 'b', 'c'];
$box['class.a']  = A::class;

$b        = $box['make.b'];  // instance de B
$iInteger = $box['integer']; // 4096
$aList    = $box['list'];    // cf. 5 lignes au dessus
$a        = $box['class.a']; // bon, vous maîtrisez normalement

Du coup, vous pouvez faire usage de isset() et unset() :

unset($box['class.a']);
isset($box['class.a']); // >> false

Dépendances partagées (singletons)

Vous pouvez, si besoin, partager des dépendances : le mode "singleton" (mais en mieux, car, comme vous le savez faire des vrais singletons n'est pas toujours une bonne pratique même si c'est pratique) :

class Counter
{
    protected $count = 0;
    
    public function increment(): int
    {
        return $this->count++;
    }
}

$box->singleton(Counter::class);

echo $box->get(Counter::class)->increment(); // 0
echo $box->get(Counter::class)->increment(); // 1
echo $box->get(Counter::class)->increment(); //	2

Autres méthodes

Ci-après, à connaître :

$box->has('MyBar');    // pour vérifier la présense d'une dépendance
$box->remove('MyBar'); // pour retirer cette dépendance que vous ne voulez plus voir
$box->empty();         // pour vider le conteneur

Fournisseurs de dépendances

Vous êtes encore là ? C'est bien beau, on peut déclarer plein de dépendances et alimenter comme on veut le conteneur mais vous êtes probablement en train de développer une application qui ne connaît peut être pas encore toutes les dépendances qu'elle aura à gérer, ou, vous voulez simplement organiser tout ça dans vos différents namespaces / packages.

Vous avez donc besoin de créer des fournisseurs, qui viendront injecter les dépendances dont ils ont la responsabilité dans l'un de vos conteneurs.

Il est donc temps d'implémenter le contrat wlib\Di\DiBoxProvider :

// Exemple (classique ?) d'un fournisseur des services HTTP d'une application
class HttpProvider implements DiBoxProvider
{
	public function provide(DiBox $box)
	{
		$box->bind('http.request', function($box, $args)
		{
			return MyApp\Http\Request();
		});

		$box->bind('http.response', function($box, $args)
		{
			return MyApp\Http\Response($box['http.request']);
		});
	}
}

// Et pour le fournir au conteneur, rien de plus simple :
$box->register(HttpProvider::class);

$response = $box->get('http.response'); // Et vous avez une réponse HTTP prête à servir vos applications / API

Voilà, vous maîtrisez ! À vous de jouer.

Exceptions

Il est possible, en cas de malfonction, volontaire ou fortuite, que DiBox lève les exceptions suivantes :

  • wlib\Di\DiException : levée au moindre truc qui chagrine le conteneur,
  • wlib\Di\DiNotFoundException : conforme à PSR-11, levée si vous tentez d'accèder à une dépendance qui n'existe pas.

Tests unitaires

N'hésitez pas à prendre connaissance du fichier /tests/Unit/DiBoxTest.php qui vous donnera quelques détails techniques supplémentaires sur l'utilisation de DiBox.

Les tests unitaires font usage de la libraire Pest.