phoole / di
Slim, powerful and full compatible PSR-11 dependency injection library for PHP
Requires
- php: >=7.2.0
- phoole/config: ^1.0.13
- psr/container: ^1.0
Requires (Dev)
- phpunit/phpunit: ^8
Provides
- psr/container-implementation: 1.0.0
README
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.
Installation
Install via the composer
utility.
composer require "phoole/di"
or add the following lines to your composer.json
{ "require": { "phoole/di": "1.*" } }
Usage
-
With configuration from files or definition array
use Phoole\Di\Container; use Phoole\Config\Config; use Phoole\Cache\Cache; use Phoole\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 parameter function($obj) { echo "ok"; }, // will be converted to $obj->setLogger($logger) 'setLogger', ] ]; // inject configurations into container $container = new Container(new Config($configData)); // get service by id 'cache' (di.service.cache) $cache = $container->get('cache');
Container related configurations are under the node
di
and service definitions are under thedi.service
node.
Features
-
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
before
orafter
section into thecache
service definition in the form of[callableOrMethodName, OptionalArgumentArray]
, these methods will be executed right before/aftercache
instantiation.callableOrMethodName
here 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 parameter function($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(...) ], ...
OptionalArgumentArray
here 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'); // same var_dump($cache1 === $cache2); // true // get a NEW cache instance $cache3 = $container->get('cache@'); // different instances var_dump($cache1 !== $cache3); // true // but both share the same cacheDriver dependent service var_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 instances var_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
get
andhas
are reserved.// after container initiated $container = new Container(new Config(...)); // 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
use Phoole\Cache\Cache; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerAwareInterface; class MyClass implements LoggerAwareInterface { use LoggerAwareTrait; public function __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
*AwareInterface
for your own classes' dependency injection.use Psr\Log\LoggerAwareTrait; use Psr\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();
Container
has all the common injection predefined in thedi.after
section$config = [ 'di.after' => [ 'setLogger', // logger aware 'setCache', // cache aware 'setDispatcher', // event aware 'setContainer', // container aware ... ], ]; ...
-
-
Both
ContainerAWareInterface
andContainerAWareTrait
available.
APIs
-
-
get(string $id): object
from ContainerInterface -
has(string $id): bool
from ContainerInterface$id
may have@
or@scope
appended.
-
Testing
$ composer test
Dependencies
-
PHP >= 7.2.0
-
phoole/config >= 1.*