Horde\Injector is a lightweight PSR-11 dependency injection container for managing object dependencies and wiring. It allows loose coupling between application components. You can inject dependencies into the constructor or setter methods and don't need to use global state or singletons. The injector supports autowiring via reflection, declarative configuration through PHP 8 attributes and explicit binding for complex scenarios. For legacy scenarios it supports using annotations rather than attributes.
The injector resolves dependencies in order of precedence: 1) Explicit bindings override attributes. 2) Attributes override autowiring. Choose which approach matches your use case.
Autowiring means zero configuration. All necessary facts can be derived from type signatures in the constructor.
The injector automatically creates instances of classes with typed constructor parameters.
The code example is fictional - a class UserInterface? might exist in your code but not in Horde 6.
<?php use Horde\Db\Adapter as DbAdapter; use Psr\Logger\LoggerInterface; class UserService { public function __construct( private DbAdapter $db, private LoggerInterface $logger ) {} } $injector = new Horde\Injector\Injector(new Horde\Injector\TopLevel()); $service = $injector->get(UserService::class); // Dependencies auto-resolved ?>
Works when:
Use PHP 8 #[Factory] attributes to specify factory classes and methods. No manual binding calls are required:
<?php use Horde\Injector\Attribute\Factory; class UserFactory { public function createUser(Horde\Injector\Injector $injector): User { return new User( $injector->get(Database::class), $injector->get('config.user.default_role') ); } } #[Factory(factory: UserFactory::class, method: 'createUser')] class User { public function __construct( private Database $db, private string $defaultRole ) {} } // No binding needed - factory auto-discovered $user = $injector->get(User::class); ?>
Use attributes when:
Mark optional dependencies for setter injection using @inject doc blocks (H5 compatibility) or #[Inject] attributes (H6):
<?php use Horde\Injector\Attribute\Inject; class Service { private ?CacheInterface $cache = null; #[Inject] public function setCache(CacheInterface $cache): void { $this->cache = $cache; } } $injector->bindImplementation(Service::class, Service::class); ?>
Note: Setter injection happens after constructor. This means injected properties are null during __construct().
This is a pattern for retrofitting code which cannot fix constructors for BC reasons. It's generally something to avoid otherwise.
For complex scenarios you can explicitly bind interfaces to implementations, factories, or closures:
Factory binding:Implementation binding:<?php use Horde\Cache\Storage as CacheStorage; $injector->bindFactory(CacheStorage::class', 'CacheFactory', 'create'); class CacheFactory { public function create(Horde\Injector\Injector $injector) { $conf = $injector->getInstance('Horde_Registry')->get('cache'); return new Horde\Cache\Storage\File($conf['dir']); } } ?>
Closure binding:<?php $injector->bindImplementation('Psr\Log\LoggerInterface', 'Horde\Log\Logger'); ?>
<?php $injector->bindClosure('DatabaseConfig', function($inj) { return new DatabaseConfig($inj->getInstance('Horde_Registry')->get('sql')); }); ?>
Explicit bindings always override attributes and autowiring.
Only use setInstance() for pre-constructed objects or the injector itself:
<?php $injector->setInstance('Horde\Injector\Injector', $injector); $injector->setInstance('legacy_db', $existingDbConnection); ?>
Warning: Avoid setInstance() unless you really need it objects. Use factories instead to maintain lazy instantiation.
Retrieve objects from the container. Instances are cached (singleton per injector scope):
<?php // PSR-11 interface (preferred in H6) $service = $injector->get('ServiceInterface'); // H5 compatibility $service = $injector->getInstance('ServiceInterface'); ?>
Force creation of a new instance (not cached):
<?php $newService = $injector->createInstance('ServiceClass'); ?>
Dependencies may still be reused from the instance cache.
Create isolated scopes for modules or request handling without polluting global scope:
<?php $appInjector->bindFactory('Logger', 'LoggerFactory', 'create'); $moduleInjector = $appInjector->createChildInjector(); $moduleInjector->bindImplementation('Logger', 'ModuleSpecificLogger'); // Child sees parent bindings $x = $moduleInjector->get('Logger'); // Uses ModuleSpecificLogger // Parent doesn't see child bindings $y = $appInjector->get('Logger'); // Uses LoggerFactory ?>
Use child injectors for:
Test if the injector can provide an interface:
<?php if ($injector->has('OptionalService')) { $service = $injector->get('OptionalService'); } ?>
has() returns true if:
H6 (Horde\Injector with attributes):<?php $injector = new Horde_Injector(new Horde_Injector_TopLevel()); $injector->bindFactory('User', 'UserFactory', 'create'); $user = $injector->getInstance('User'); ?>
<?php use Horde\Injector\Attribute\Factory; #[Factory(factory: UserFactory::class, method: 'create')] class User { } $injector = new Horde\Injector\Injector(new Horde\Injector\TopLevel()); $user = $injector->get(User::class); // PSR-11, factory auto-discovered ?>
Key changes: