Slim, powerful and full compatible PSR-11 dependency injection library for PHP.
It builds upon the versatile phoole/config library and supports object decorating, object scope and more. It requires PHP 7.2+. It is compliant with PSR-1, PSR-4, PSR-11 and PSR-12.
Install via the composer utility.
composer require "phoole/di"
or add the following lines to your composer.json
{
"require": {
"phoole/di": "1.*"
}
}With configuration from files or definition array
usePhoole\Di\Container; usePhoole\Config\Config; usePhoole\Cache\Cache; usePhoole\Cache\Adaptor\FileAdaptor; $configData = [ // service definitions'di.service' => [ // classname & constructor arguments'cache' => [ 'class' => Cache::class, 'args' => ['${#cacheDriver}'] // optional ], // use classname directly'cacheDriver' => FileAdaptor::class ], // methods to run after each object initiation'di.after' => [ // a callable, takes THE object as parameterfunction($obj) { echo"ok"; }, // will be converted to $obj->setLogger($logger)'setLogger', ] ]; // inject configurations into container$container = newContainer(newConfig($configData)); // get service by id 'cache' (di.service.cache)$cache = $container->get('cache');
Container related configurations are under the node
diand service definitions are under thedi.servicenode.
References in the form of '${reference}' can be used to refer to predefined parameters from the config or services in the container.
Characters of
'$', '{', '}', '.'are not allowed in reference name. Characters of'#', '@'have special meanings, such that should not be part of normal service names.Parameter references like
${system.tempdir}$config = [ ... // use predefined 'sytem.tmpdir' in arguments etc.'di.service.cacheDriver' => [ 'class' => FileAdaptor::class, 'args' => ['${system.tmpdir}'], ], ... ];
See phoole/config reference for detail. Parameter references are read from configuration files or array.
Service references like
${#cache}Service object reference in the form of
${#serviceId}can be used to referring a service instance in the container.$configData = [ ... 'di.service' => [ 'cache' => [ 'class' => Cache::class, 'args' => ['${#cacheDriver}'] // object reference ], 'cacheDriver' => ... ...
Two reserved service references are
${#container}and${#config}. These two are referring the container instance itself and the config instance it is using. These two can be used just like other service references.Using references
References can be used anywhere in the configuration.
$confData = [ // methods executed after ALL object initiation'di.after' => [ [['${#logger}', 'notice'], ['object created using ${log.facility}']] ] ];
Object decorating is to apply decorating changes (executing methods etc.) right before or after the instantiation of a service instance.
Decorating methods for individual instance only
$config = [ 'di.service' => [ ... 'cache', [ 'class' => '${cache.class}', 'args' => ['${#cachedriver}'], // constructor arguments'before' => [ [['${#logger}', 'info'], ['before initiating cache']], // $logger->info(...) ], 'after' => [ 'clearCache', // $cache->clearCache() method ['setLogger', ['${#logger}']], // $cache->setLogger($logger), argument is optional [['${#logger}', 'info'], ['just a info']], // $logger->info(...)function($cache) { // a callable takes object in parameter }, ] ], ... ] ];
By adding
beforeoraftersection into thecacheservice definition in the form of[callableOrMethodName, OptionalArgumentArray], these methods will be executed right before/aftercacheinstantiation.callableOrMethodNamehere can be,method name of initiated object
...'after' => [ // $obj->setLogger($logger), $logger will be injected automatically'setLogger', // object implementing 'LoggerAwareInterface' ], ...
a valid callable which takes initiated object as parameter
...'after' => [ // callable takes initiated object as parameterfunction($obj) { }, ], ...
a pseudo callable with references (after resolving the references, it is a valid callable).
...'after' => [ // a pseudo callable with references [['${#logger}', 'info'], ['just a info']], // $logger->info(...) ], ...
OptionalArgumentArrayhere can be,empty
array of values or references
Common decorating methods for all instances
$configData = [ // before all instances initiated'di.before' => [ [['${#logger}', 'info'], ['before create']], ], // after methods for all instances'di.after' => [ ['setLogger', ['${#logger}']], // arguments are optional'setDispatcher', // simple enough, set event dispatcher ], ];
Common methods can be configured in the 'di.before' or 'di.after' node to apply to all the instances right before or after their instantiation.
Shared objects and new objects
By default, service instances in the container are shared inside the container. If users want different instance each time, they may just append '@' to the service id.
// cache service by default is in shared scope$cache1 = $container->get('cache'); // get again$cache2 = $container->get('cache'); // samevar_dump($cache1 === $cache2); // true// get a NEW cache instance$cache3 = $container->get('cache@'); // different instancesvar_dump($cache1 !== $cache3); // true// but both share the same cacheDriver dependent servicevar_dump($cache1->getAdaptor() === $cache3->getAdaptor()); // true
Object scope
You may get an instance in your own scope as follows
// no scope$cache1 = $container->get('cache'); // in `myScope`$cache2 = $container->get('cache@myScope'); // different instancesvar_dump($cache1 !== $cache2); // true// shared in myScope$cache3 = $container->get('cache@myScope'); var_dump($cache2 === $cache2); // true
Service references can also have scope defined as follows,
$container->set('cache', [ 'class' => Cache::class, 'args' => ['${#driver@myScope}'] // use driver of myScope ]);
Access predefined services statically
Services in the container can also be access through a static way. But
getandhasare reserved.// after container initiated$container = newContainer(newConfig(...)); // equals to $cache = $container->get('cache')$cache = Container::cache(); // if myservice defined and invokable$obj = Container::myservice('test');
Initiating object by taking advantage of dependency injection
usePhoole\Cache\Cache; usePsr\Log\LoggerAwareTrait; usePsr\Log\LoggerAwareInterface; class MyClass implements LoggerAwareInterface { use LoggerAwareTrait; publicfunction__construct(Cache$cache) { } } // $cache will be injected automatically$obj = Container::create(MyClass::class); // also 'setLogger' will be executed if defined in 'di.after' section$logger = $obj->getLogger();
Parameter autowiring (resolving)
Parameters of a constructor/callable will be resolved by looking
exists in the classmap (service objects created already) ?
classname known to the script (class defined already) ?
Auto injection
Instead of using 'annotation', we encourage of using
*AwareInterfacefor your own classes' dependency injection.usePsr\Log\LoggerAwareTrait; usePsr\Log\LoggerAwareInterface; class MyOwnClass implements LoggerAwareInterface { use LoggerAwareTrait; ... } // create your object with arguments$obj = Container::create(MyOnwClass::class, [...]); // $logger injected by the container automatically$logger = $obj->getLogger();
Containerhas all the common injection predefined in thedi.aftersection$config = [ 'di.after' => [ 'setLogger', // logger aware'setCache', // cache aware'setDispatcher', // event aware'setContainer', // container aware ... ], ]; ...
Both
ContainerAWareInterfaceandContainerAWareTraitavailable.
get(string $id): objectfrom ContainerInterfacehas(string $id): boolfrom ContainerInterface$idmay have@or@scopeappended.
$ composer testPHP >= 7.2.0
phoole/config >= 1.*
