mithridatem / autowiring
Constructor autowiring container with an optional Mithridatem Router bridge.
Requires
- php: ^8.1
- psr/container: ^2.0
Requires (Dev)
- mithridatem/router: ^1.2
- phpunit/phpunit: ^10.5
Suggests
- mithridatem/router: Required only for the routing bridge and routing example (^1.2).
Provides
None
Conflicts
None
Replaces
None
README
Librairie PHP d'injection de dépendances par constructeur, compatible PSR-11. Elle construit automatiquement les classes concrètes et leurs dépendances, avec un adaptateur optionnel pour Mithridatem Router.
Prérequis : PHP 8.1 ou supérieur et Composer.
Première intégration ? Commencer par le démarrage rapide : config/service.php et public/index.php. Il présente les deux fichiers à adapter, la connexion PDO et les vérifications de base. Le guide d'utilisation détaille ensuite les options et le dépannage.
Depuis v1.1.0, #[Inject] sélectionne un service dans un constructeur et
Container::load() centralise les définitions dans config/services.php. Consulter le
guide attributs et configuration
pour les exemples, l'ordre de priorité et le dépannage.
Installation
composer require mithridatem/autowiring:^1.2
Le cœur requiert seulement psr/container. Le routeur et PHPUnit sont des
dépendances de développement de ce dépôt ; ils ne sont pas imposés aux applications.
Résolution automatique
require __DIR__ . '/vendor/autoload.php'; use Mithridatem\Autowiring\Container; final class BookRepository {} final class BookService { public function __construct(public BookRepository $books) {} } $container = new Container(); $service = $container->get(BookService::class);
Le conteneur lit le constructeur avec la réflexion PHP, résout BookRepository,
puis crée BookService. Aucun bind() n'est nécessaire pour une classe concrète.
Les classes doivent être chargées ou accessibles via l'autoload de l'application.
Associer une interface
$container->bind(BookRepositoryInterface::class, SqlBookRepository::class);
L'implémentation doit être une classe instanciable respectant le contrat demandé. Une classe parente peut également être associée à une sous-classe.
Découverte optionnelle des implémentations (depuis 1.2.0)
La version 1.2.0 ajoute cette API optionnelle. Dans config/services.php :
return static function (\Mithridatem\Autowiring\Container $container): void { $container->discover(dirname(__DIR__) . '/src/Repository', 'App\\Repository'); };
Le namespace doit correspondre à l'autoload PSR-4 du répertoire. Une interface
avec une seule implémentation découverte est associée automatiquement, sans
instanciation pendant le parcours. En cas d'ambiguïté, la résolution indique les
classes candidates et demande un choix explicite. bind(), set() et factory()
restent prioritaires, quel que soit leur ordre par rapport à discover().
Voir le guide de découverte
pour les limites, la résolution des ambiguïtés et les exemples.
Fournir des paramètres comme $path
final class FileLogger { public function __construct(public string $path) {} } $container = (new Container())->configure(FileLogger::class, [ 'path' => __DIR__ . '/var/log/application.log', ]); $logger = $container->get(FileLogger::class);
Les clés correspondent aux noms exacts des paramètres du constructeur. Une faute
de frappe provoque une exception. La configuration s'applique à la classe concrète,
y compris lorsqu'elle est obtenue par un bind() d'interface. Une valeur configurée
peut aussi être un objet ou null si le type du paramètre l'accepte.
Enregistrer un objet ou une fabrique
use Psr\Container\ContainerInterface; $container = new Container(); $container->set(PDO::class, $pdo); // Connexion déjà créée par l'application. $container->factory(BookRepositoryInterface::class, fn (ContainerInterface $c) => new SqlBookRepository($c->get(PDO::class)) );
Une fabrique est appelée lors du premier besoin et reçoit le conteneur. Elle
retourne un objet. Pour un identifiant de classe ou d'interface, le résultat doit
respecter ce type. On peut aussi utiliser un identifiant libre comme logger.file.
Les classes internes PHP, comme PDO, doivent être fournies par set() ou factory().
Une factory avancée peut implémenter Mithridatem\\Autowiring\\Factory\\FactoryInterface :
use Mithridatem\\Autowiring\\Factory\\FactoryInterface; use Psr\\Container\\ContainerInterface; final class ConnectionFactory implements FactoryInterface { public function create(ContainerInterface $container): object { return new PDO('sqlite::memory:'); } } $container->factory(PDO::class, new ConnectionFactory());
La syntaxe factory($id, callable) reste intégralement compatible. Les factories
sont exécutées à la demande, leurs résultats sont validés puis partagés par le
conteneur. Les factories objets sont enregistrées explicitement et ne sont pas
découvertes automatiquement. Voir le guide des factories.
Cycle de vie et règles de résolution
- Effectuer tous les
bind(),set(),factory()etconfigure()avant le premierget(). Ce premier appel fige la configuration, même s'il échoue. Les fabriques utilisentget()pour leurs dépendances, sans modifier les définitions. - Les objets sont créés à la demande et partagés pendant la durée de vie du conteneur. Une interface associée et sa classe concrète retournent le même objet. Deux définitions de fabrique distinctes restent deux services distincts.
- Une valeur explicitement configurée est prioritaire. Ensuite, un paramètre de
constructeur peut sélectionner un service avec
#[Inject]depuis v1.1.0 (identifiant objet et type vérifiés). Sans attribut, le comportement reste inchangé : un paramètre de type objet résoluble est injecté. À défaut, la valeur par défaut du paramètre est utilisée si elle existe. Sinon une exception explique le paramètre manquant. - Un paramètre nullable sans valeur par défaut reste obligatoire. Un objet
résoluble qui échoue pendant sa construction ne devient pas silencieusement
null. has()ne construit rien et ne vérifie pas tout le graphe :truesignifie qu'une entrée ou une classe instanciable existe, pas que sa construction réussira.- Les types union et intersection nécessitent une valeur par défaut, une
configuration explicite ou une fabrique. Les paramètres variadiques et les
références nécessitent une fabrique. Les types
selfetparentsont résolus dans la classe déclarant le constructeur. - L'injection de propriétés, l'injection dans les méthodes, les services non partagés et la compilation ne font pas partie de cette première version.
bind(), set() et factory() remplacent une définition précédente pour le même
identifiant avant le premier get(). Plusieurs configure() fusionnent les paramètres.
Le partage d'instances n'est pas un singleton global. Dans un serveur persistant,
créer un conteneur par requête si des services conservent un état propre à la requête.
Intégration avec le routeur
Installer les deux paquets dans l'application :
composer require mithridatem/autowiring:^1.2 mithridatem/router:^1.2
Aucun changement de la
librairie de routing n'est nécessaire : son ControllerResolverInterface est le
point d'intégration.
use Mithridatem\Autowiring\Bridge\Routing\ContainerControllerResolver; use Mithridatem\Autowiring\Container; use Mithridatem\Routing\Loader\AttributeRouteLoader; use Mithridatem\Routing\Router; $container = (new Container())->bind( BookRepositoryInterface::class, InMemoryBookRepository::class ); $router = new Router(); $router->setControllerResolver(new ContainerControllerResolver($container)); (new AttributeRouteLoader())->loadDirectory( router: $router, directory: __DIR__ . '/../src/Controller', namespace: 'App\\Controller' ); echo $router->dispatch();
Le resolver accepte tout Psr\Container\ContainerInterface, et pas seulement ce
conteneur. Il retourne un callable au routeur et conserve la cause d'une erreur
de construction dans une RouterException.
Les dépendances de constructeur sont fournies par le conteneur. Les paramètres
d'URL, comme $id dans show(string $id), sont fournis par le routeur. L'adaptateur
n'injecte ni services ni objets de requête dans les paramètres des actions.
Les déclarations mapController() continuent également à fonctionner.
Exemple complet à exécuter
L'exemple examples/routing/ contient un contrôleur avec #[Route], un service,
une interface de repository et un repository en mémoire, sans base de données.
Cet exemple utilise discover() dans sa configuration et nécessite la version
1.2.0 ou supérieure. Depuis la racine du dépôt après composer install :
php -S 127.0.0.1:8080 -t examples/routing/public examples/routing/public/index.php
Ouvrir http://127.0.0.1:8080/books/1 : la réponse est Le Petit Prince.
Les classes de l'exemple sont chargées par l'autoload de développement du dépôt.
Dans une application consommatrice, utiliser ses propres classes et son propre
autoload PSR-4 : les classes Example\ ne sont pas exportées par le paquet.
Erreurs et tests
NotFoundExceptionimplémenteNotFoundExceptionInterfacepour une entrée demandée inexistante ou une interface non associée.ContainerExceptionimplémenteContainerExceptionInterfacepour les cycles, les paramètres manquants et les erreurs de construction. La cause est conservée.- Une dépendance manquante à l'intérieur d'un service existant est une erreur de construction de ce service, conformément au contrat PSR-11.
composer validate --strict composer check
PHPUnit couvre la résolution, les interfaces, le partage, les paramètres, les fabriques, les erreurs, la reprise après échec et le dispatch du routeur avec attributs, découverte de répertoire et paramètres d'URL. La CI exécute ces contrôles sur PHP 8.1, 8.3 et 8.4.
Les services métier reçoivent leurs dépendances dans leurs constructeurs. Réserver l'usage du conteneur au démarrage, aux fabriques et au resolver ; les repositories et services n'ont pas besoin de connaître le conteneur.
Organisation et distribution
| Emplacement | Contenu | Archive de distribution |
|---|---|---|
src/ |
Conteneur, exceptions PSR-11, adaptateur optionnel | Inclus |
composer.json, LICENSE, README.md, CHANGELOG.md |
Métadonnées, licence, usage et historique | Inclus |
tests/, examples/, scripts/, phpunit.xml |
Développement, exemples et vérifications | Exclus |
.github/, docs/ |
CI et procédure de publication | Exclus |
L'autoload de production expose uniquement Mithridatem\Autowiring\ depuis
src/. Les tests et les classes Example\ appartiennent à l'autoload de
développement. Pour exécuter les exemples ou les tests, cloner le dépôt et
installer les dépendances de développement plutôt que travailler dans vendor/.
La procédure complète figure dans docs/PUBLISHING.md. Les changements sont consignés dans CHANGELOG.md.