phossa / phossa-di
A fast and full-fledged dependency injection library for PHP. It supports auto wiring, container delegation, object decorating, definition provider, definition tags, service scope and more.
Requires
- php: >=5.4.0
- container-interop/container-interop: ~1.0
- phossa/phossa-config: ^1.0.7
- phossa/phossa-shared: >=1.0.6
Requires (Dev)
- phpunit/phpunit: 4.*
Suggests
- phossa/phossa-cache: If you want to use config cache
This package is not auto-updated.
Last update: 2024-10-26 19:00:07 UTC
README
See new lib at phoole/di Introduction
Phossa-di is a fast, feature-rich and full-fledged dependency injection library for PHP. It supports auto wiring, container delegation, object decorating, definition provider, definition tagging, object scope and more.
It requires PHP 5.4 and supports PHP 7.0+, HHVM. It is compliant with PSR-1, PSR-2, PSR-4 and coming PSR-5, PSR-11.
Getting started
-
Installation
Install via the
composer
utility.composer require "phossa/phossa-di=1.*"
or add the following lines to your
composer.json
{ "require": { "phossa/phossa-di": "^1.0.6" } }
-
Simple usage
You might have serveral simple classes like these or third party libraries, and want to make avaiable as services.
class MyCache { private $driver; public function __construct(MyCacheDriver $driver) { $this->driver = $driver; } // ... } class MyCacheDriver { // ... }
Now do the following,
use Phossa\Di\Container; $container = new Container(); // use the 'MyCache' classname as the service id if ($container->has('MyCache')) { $cache = $container->get('MyCache'); }
With auto wiring is turned on by default, the container will look for the
MyCache
class and resolves its dependency injection automatically when creating the$cache
instance. -
Use with definitions
Complex situations may need extra configurations. Definition related methods
add()
,set()
,map()
,addMethod()
andsetScope()
etc. can be used to configure services.use Phossa\Di\Container; // turn off auto wiring $container = (new Container())->auto(false); // config service with id, classname and constructor arguments $container->add('cache', 'MyCache', [ '@cacheDriver@' ]); // add initialization methods $container->add('cacheDriver', 'MyCacheDriver') ->addMethod('setRoot', [ '%cache.root%' ]); // set a parameter which is referenced before $container->set('cache.root', '/var/local/tmp'); // get cache service by its id $cache = $container->get('cache');
-
Service definitions
Service
is defined using APIadd($id, $classOrClosure, array $arguments)
and later can be refered in other definition with service reference@service_id@
$container = new Container(); // add the 'cache' service definition $container->add('cache', \Phossa\Cache\CachePool::class, ['@cacheDriver@']); // add the 'cacheDriver' service definition $container->add('cacheDriver', \Phossa\Cache\Driver\FilesystemDriver); // get cache service $cache = $container->get('cache');
Service reference in the format of
@service_id@
can be used anywhere where an object is appropriate, such as in the argument array or construct a pseudo callable,// will resolve this ['@cache@', 'setLogger'] to a real callable $container->run(['@cache@', 'setLogger'], ['@logger@']);
-
Parameter definitions
Parameter can be set with API
set($name, $value)
. Parameter reference is '%parameter.name%'. Parameter reference can point to a string, another parameter or even a service reference.// set system temp directory $container->set('system.tmpdir', '/var/tmp'); // point cache dir to system temp $container->set('cache.dir', '%system.tmpdir%'); // set with array of vales $container->set('logger', [ 'driver' => 'Phossa\Logger\Driver\StreamDriver', 'level' => 'warning' ]); // use parameter $container->add( 'cacheDir', Phossa\Cache\Driver\Filesystem::class, [ '%cache.dir%' ] );
-
-
Callable instead of class name
Callable can be used instead of class name to instantiate a service.
// ... $container->add('cacheDriver', function() { return new \MyCacheDriver(); });
-
Definition files
Instead of configuring
$container
in the code, you may put your service and parameter definitions into one definition file or several seperated files (seperating parameter definitions from service definitions will give you the benefit of loading different parameters base on different situations.).PHP, JSON, XML file formats are supported, and will be detected automatically base on the filename suffixes.
A service definition file
definition.serv.php
<?php /* file name '*.s*.php' indicating SERVICE definitions in PHP format */ use Phossa\Di\Container; return [ 'cache' => [ 'class' => [ 'MyCache', [ '@cacheDriver@' ]], 'scope' => Container::SCOPE_SHARED // default anyway ], 'cacheDriver' => [ 'class' => 'MyCacheDriver', 'methods' => [ [ 'setRoot', [ '%cache.root%' ] ], // ... ] ], 'theDriver' => '@cacheDriver@', // an alias // ... ];
The parameter definition file
definition.param.php
<?php /* file name '*.p*.php' indicating PARAMETER definitions in PHP format */ return [ 'tmp.dir' => '/var/local/tmp', 'cache.root' => '%tmp.dir%', // ... ];
Or you may combine these files into one
definition.php
,<?php /* file name '*.php' indicating definitions in PHP format */ use Phossa\Di\Container; return [ // key 'services' indicating the service definitions 'services' => [ 'cache' => [ 'class' => [ 'MyCache', [ '@cacheDriver@' ]], 'scope' => Container::SCOPE_SHARED // default anyway ], 'cacheDriver' => [ 'class' => 'MyCacheDriver', 'methods' => [ [ 'setRoot', [ '%cache.root%' ] ], // ... ] ], // ... ], // key 'parameters' indicating the parameter definitions 'parameters' => [ 'cache.root' => '/var/local/tmp', // ... ], // key 'mappings' indicating the mapping definitions 'mappings' => [ 'Phossa\\Cache\\CachePoolInterface' => 'Phossa\\Cache\\CachePool', // ... ], ];
You may load definitions from files now,
use Phossa\Di\Container; $container = new Container(); // load service definitions $container->load('./definition.serv.php'); // load parameter definition $container->load('./definition.param.php'); // you may load from one if you want to // $container->load('./definition.php'); // getting what you've already defined $cache = $container->get('cache');
Features
-
Auto wiring is the ability of container instantiating objects and resolving its dependencies automatically. The base for auto wiring is the PHP function parameter type-hinting.
By reflecting on the class, constructor and methods, phossa-di is able to find the right class for the object (user need to use the classname as the service id) and right class for the dependencies (type-hinted with the right classnames).
To fully explore the auto wiring feature, users may map interfaces to classnames or service ids as follows,
// map an interface to a classname $container->map( 'Phossa\\Cache\\CachePoolInterface', // MUST NO leading backslash 'Phossa\\Cache\\CachePool' // leading backslash is optional ); // map an interface to a service id, MUST NO leading backslash $container->map('Phossa\\Cache\\CachePoolInterface', '@cache@'); // map an interface to a parameter, no leading backslash //$container->map('Phossa\\Cache\\CachePoolInterface', '%cache.class%');
Or load mapping files,
$container->load('./defintion.map.php');
Auto wiring can be turned on/off. Turn off auto wiring will enable user to check any defintion errors without automatically loading.
// turn off auto wiring $container->auto(false); // turn on auto wiring $container->auto(true);
-
According to Interop Container Delegate Lookup, container may register a delegate container (the delegator), and
-
Calls to the
get()
method should only return an entry if the entry is part of the container. If the entry is not part of the container, an exception should be thrown (as requested by theContainerInterface
). -
Calls to the
has()
method should only return true if the entry is part of the container. If the entry is not part of the container, false should be returned. -
If the fetched entry has dependencies, instead of performing the dependency lookup in the container, the lookup is performed on the delegate container (delegator).
-
Important By default, the lookup SHOULD be performed on the delegate container only, not on the container itself.
phossa-di fully supports the delegate feature.
use Phossa\Di\Delegator; // create delegator $delegator = new Delegator(); // insert different containers $delegator->addContainer($otherContainer); // $contaner register with the delegator $container->setDelegate($delegator); // cacheDriver is now looked up through the $delegator $cache = $container->get('cache');
-
-
Object decorating is to apply decorating changes (run methods etc.) right after the instantiation of a service object base on certain criteria such as it implements an interface.
// any object implementing 'LoggerAwareInterface' should be decorated $container->addDecorate( 'setlogger', // rule name 'Psr\\Log\\LoggerAwareInterface', // NO leading backslash ['setLogger', ['@logger@']] // run this method );
Object decorating saves user a lot of definition duplications and will apply to future service definitions. Phossa-di also supports a tester callable and a decorate callable as follows,
$container->addDecorate('setlogger', function($object) { return $object instanceof \Psr\Log\LoggerAwareInterface; }, function($object) use($container) { $object->setLogger($container->get('logger')); } );
-
Most developers use different defintions or configurations for development or production environment. This is achieved by put definitions in different files and load these files base on the container tags.
Tag is also used in definition provider.
// SYSTEM_CONST can be 'PRODUCTION' or 'DEVELOPMENT' $container->setTag(SYSTEM_CONST); // load different defintion base on container tags if ($container->hasTag('PRODUCTION')) { $container->load('./productDefinitions.php'); } else { $container->load('./developDefinitions.php'); }
-
Definition provider is used to wrap logic related definitions into one entity. These definitions will be loaded into container automaitcally if a call to container's
has()
orget()
and found the definition in this provider.<?php use Phossa\Di\Extension\Provider\ProviderAbstract; // Production related DB definitions here class ProductionDbProvider extends ProviderAbstract { // list of service ids we provide protected $provides = [ 'DbServer' ]; // tags this provide has protected $tags = [ 'PRODUCTION' ]; // the only method we need to implement protected function merge() { $container = $this->getContainer(); $container->add('DbServer', '\\DbClass', [ '192.168.0.12', 'myDbusername', 'thisIsApassword' ]); } }
The provider
ProductionDbProvider
should be added into container before any calls tohas()
orget()
.// SYSTEM_CONST is now 'PRODUCTION' $container->setTag(SYSTEM_CONST); // the provider will be loaded only if SYSTEM_CONST is PRODUCTION $container->addProvider(new ProductionDbProvider()); // another provider will be loaded only if SYSTEM_CONST is TEST $container->addProvider(new TestDbProvider()); // DB related definitions will be loaded here $db = $container->get('DbServer');
Or during the container instantiation
$container = new Container('./defintions.php', [ ProductionDbProvider::class, TestDbProvider::class ]);
-
By default, service objects in the container is shared inside the container, namely they have the scope of
Container::SCOPE_SHARED
. If users want different instance each time, they may either use the methodone()
or define the service withContainer::SCOPE_SINGLE
scope.// a shared copy of cache service $cache1 = $container->get('cache'); // a new cache instance $cache2 = $container->one('cache'); // different instances var_dump($cache1 === $cache2); // but both share the same cacheDriver var_dump($cache1->getDriver() === $cache2->getDriver()); // true
Or define it as
Container::SCOPE_SINGLE
$container->add('cache', '\\Phossa\\Cache\\CachePool') ->setScope(Container::SCOPE_SINGLE); // each get() will return a new cache $cache1 = $container->get('cache'); $cache2 = $container->get('cache'); // different instances var_dump($cache1 === $cache2); // false // dependencies are shared var_dump($cache1->getDriver() === $cache->getDriver()); // true
To make all service objects non-shared, set the container's default scope to
Container::SCOPE_SINGLE
as follows,// make everything non-shareable, set default scope to SCOPE_SINGLE $container->share(false); // this will return a new copy of cache service $cache1 = $container->get('cache'); // this will return a new copy also $cache2 = $container->get('cache'); // FALSE var_dump($cache1 === $cache2); // dependencies are different var_dump($cache1->getDriver() === $cache->getDriver()); // false
Public APIs
-
PSR-11 compliant APIs
-
get(string $id): object
Getting the named service from the container.
-
has(string $id): bool
Check for the named service's existence in the container.
-
-
Extended APIs by phossa-di
-
__construct(string|array $definitionArrayOrFile = '', array $definitionProviders = [])
$defintionArrayOrFile
can be a defintion file or definition array.$definitionProviders
can be array ofProviderAbstract
objects or provider classnames. -
get(string $id, array $constructorArguments = [], string $inThisScope = ''): object
If extra arguments are provided, new instance will be generated even if it was configured with a
Container::SCOPE_SHARED
scope.If
$inThisScope
is not empty, new instance will be specificly shared in the provided scope.Arguments may contain references like
@service_id@
or%parameter%
. -
has(string $id, bool $withAutowiring = CONTAINER_DEFAULT_VALUE)
If
$withAutowiring
is explicitly set totrue
orfalse
, auto registering of this service $id if classname matches will be turned ON or OFF for this specific checking. Otherwise, use the container's auto wiring setting. -
one(string $id, array $constructorArguments = []): object
Get a new instance even if it is configured as a shared service with or without new arguments.
-
run(callable|array $callable, array $callableArguments = []): mixed
Execute a callable with the provided arguments. Pseudo callable like
['@cacheDriver@', '%cache.setroot.method%']
is supported.
-
-
Definition related APIs
-
add(string|array $id, string|callable $classOrClosure, array $constructorArguments = []): this
Add a service definition or definitions(array) into the container. Callable can be used instead of classname to create an instance.
$constructorArguments
is for the constructor.Aliasing can be achieved by define
$classOrClosure
as a service reference, namely@serviceId@
. -
set(string|array $nameOrArray, string|array $valueStringOrArray = ''): this
Set a parameter or parameters(array) into the container. Parameter name can be the format of 'parameter.name.string', it will be converted into multi-dimention array.
-
map(string|array $nameOrArray, string $toName = ''): this
Map an interface name to a classname. Also mapping of a classname to another classname (child class), map to a service reference or to a parameter reference is ok. Batch mode if
$nameOrArray
is an array.Note No leading backslash for the
$nameOrArray
, if it is a classname or interface name. -
load(string|array $fileOrArray): this
Load a definition array or definition file into the container. Definition filename with the format of
*.s*.php
will be considered as a service definition file in PHP format.*.p*.php
is a parameter file in PHP format.*.m*.php
is a mapping file.File suffixes '.php|.json|.xml' are known to this library.
-
share(bool $status = true): this
Set container-wide default scope.
true
to set toContainer::SCOPE_SHARED
andfalse
set toContainer::SCOPE_SINGLE
-
auto(bool $switchOn): this
Turn on (true) or turn off (false) auto wiring.
-
addMethod(string $methodName, array $methodArguments = []): this
Execute this
$methodName
right after the service instantiation. ThisaddMethod()
has to follow aadd()
or anotheraddMethod()
orsetScope()
call. MultipleaddMethod()
s can be chained together.$methodName
can be a parameter reference.$methodArguments
can have parameter or service references. -
setScope(string $scope): this
Set scope for the previous added service in the chain of
add()
oraddMethod()
. There are two predefined scope contants, shared scopeContainer::SCOPE_SHARED
and single scopeContainer::SCOPE_SINGLE
. -
dump(bool $toScreen = true): true|string
Will print out all the definitions and mappings or return the output.
-
-
Extension related APIs
-
addExtension(ExtensionAbstract $extension): this
Explicitly load an extension into the container.
Note Calling extension related methods will automatically load corresponding extensions.
-
setTag(string|array $tagOrTagArray): this
TaggableExtension set/replace container tag(s). Tags can be used to selectly load definition files or definition providers.
-
hasTag(string|array $tagOrTagArray): bool
TaggableExtension check the existence of tag(s) in the container. One tag match will return
true
, otherwise returnfalse
if ($container->hasTag('PRODUCTION')) { $container->load('./productDefinitions.php'); } else { $container->load('./developDefinitions.php'); }
-
setDelegate(DelegatorInterface $delegator): this
DelegateExtension set the delegator. Dependency will be looked up in the delegator instead of in the container. The container itself will be injected into delegator's container pool.
Since auto wiring is conflict with the delegation design, auto wiring will be turned off automatically for containers in the pool except for the last one.
use Phossa\Di\Delegator; // create the delegator $delegator = new Delegator(); // other container register with the delegator $delegator->addContainer($otherContainer); /* * register $container with its auotwiring status unchanged (last container) * but $otherContainer's autowiring will be forced off */ $container->setDelegate($delegator); // dependency will be resolved in the order of $otherContainer, $container // ...
-
addDecorate(string $ruleName, string|callable $interfaceOrClosure, array|callable $decorateCallable): this
DecorateExtension adding object decorating rules to the container.
-
addProvider(string|ProviderAbstract $providerOrClass): this
ProviderExtension add definition provider to the container either by provider classname or a provider object.
-
Dependencies
-
PHP >= 5.4.0
-
phossa/phossa-shared >= 1.0.6
-
container-interop/container-interop ~1.0