-
Installation, (*5)
Install via the composer
utility., (*6)
composer require "phossa/phossa-di=1.*"
or add the following lines to your composer.json
, (*7)
{
"require": {
"phossa/phossa-di": "^1.0.6"
}
}
-
Simple usage, (*8)
You might have serveral simple classes like these or third party libraries,
and want to make avaiable as services., (*9)
class MyCache
{
private $driver;
public function __construct(MyCacheDriver $driver)
{
$this->driver = $driver;
}
// ...
}
class MyCacheDriver
{
// ...
}
Now do the following,, (*10)
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., (*11)
-
Use with definitions, (*12)
Complex situations may need extra configurations. Definition related methods
add()
, set()
, map()
, addMethod()
and setScope()
etc. can be used to
configure services., (*13)
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
is defined using API add($id, $classOrClosure, array $arguments)
and later can be refered in other definition with service reference
@service_id@
, (*14)
$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,, (*15)
// will resolve this ['@cache@', 'setLogger'] to a real callable
$container->run(['@cache@', 'setLogger'], ['@logger@']);
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., (*16)
// 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, (*17)
Callable can be used instead of class name to instantiate a service., (*18)
// ...
$container->add('cacheDriver', function() {
return new \MyCacheDriver();
});
-
Definition files, (*19)
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.)., (*20)
PHP, JSON, XML file formats are supported, and will be detected automatically
base on the filename suffixes., (*21)
A service definition file definition.serv.php
, (*22)
<?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
, (*23)
<?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
,, (*24)
<?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,, (*25)
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');
-
Auto wiring, (*26)
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., (*27)
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)., (*28)
To fully explore the auto wiring feature, users may map interfaces to
classnames or service ids as follows,, (*29)
// 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,, (*30)
$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., (*31)
// turn off auto wiring
$container->auto(false);
// turn on auto wiring
$container->auto(true);
-
Container delegation, (*32)
According to Interop Container Delegate Lookup, container may register a delegate
container (the delegator), and, (*33)
-
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 the ContainerInterface
)., (*34)
-
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., (*35)
-
If the fetched entry has dependencies, instead of performing the
dependency lookup in the container, the lookup is performed on the
delegate container (delegator)., (*36)
-
Important By default, the lookup SHOULD be performed on the delegate
container only, not on the container itself., (*37)
phossa-di fully supports the delegate feature., (*38)
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, (*39)
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., (*40)
// 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,, (*41)
$container->addDecorate('setlogger',
function($object) {
return $object instanceof \Psr\Log\LoggerAwareInterface;
},
function($object) use($container) {
$object->setLogger($container->get('logger'));
}
);
-
Definition tagging, (*42)
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., (*43)
Tag is also used in definition provider., (*44)
// 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, (*45)
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()
or get()
and found the definition in this
provider., (*46)
<?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 to has()
or get()
., (*47)
// 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, (*48)
$container = new Container('./defintions.php', [
ProductionDbProvider::class,
TestDbProvider::class
]);
-
Object scope, (*49)
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 method one()
or
define the service with Container::SCOPE_SINGLE
scope., (*50)
// 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
, (*51)
$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,, (*52)
// 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
-
PSR-11 compliant APIs, (*53)
Getting the named service from the container., (*54)
Check for the named service's existence in the container., (*55)
-
Extended APIs by phossa-di, (*56)
__construct(string|array $definitionArrayOrFile = '', array $definitionProviders = [])
$defintionArrayOrFile
can be a defintion file or definition array., (*57)
$definitionProviders
can be array of ProviderAbstract
objects or
provider classnames., (*58)
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., (*59)
If $inThisScope
is not empty, new instance will be specificly shared in
the provided scope., (*60)
Arguments may contain references like @service_id@
or %parameter%
., (*61)
has(string $id, bool $withAutowiring = CONTAINER_DEFAULT_VALUE)
If $withAutowiring
is explicitly set to true
or false
, 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., (*62)
one(string $id, array $constructorArguments = []): object
Get a new instance even if it is configured as a shared service with or
without new arguments., (*63)
run(callable|array $callable, array $callableArguments = []): mixed
Execute a callable with the provided arguments. Pseudo callable like
['@cacheDriver@', '%cache.setroot.method%']
is supported., (*64)
-
Definition related APIs, (*65)
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., (*66)
$constructorArguments
is for the constructor., (*67)
Aliasing can be achieved by define $classOrClosure
as a service reference,
namely @serviceId@
., (*68)
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., (*69)
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., (*70)
Note No leading backslash for the $nameOrArray
, if it is a classname
or interface name., (*71)
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., (*72)
File suffixes '.php|.json|.xml' are known to this library., (*73)
share(bool $status = true): this
Set container-wide default scope. true
to set to Container::SCOPE_SHARED
and false
set to Container::SCOPE_SINGLE
, (*74)
auto(bool $switchOn): this
Turn on (true) or turn off (false) auto wiring., (*75)
addMethod(string $methodName, array $methodArguments = []): this
Execute this $methodName
right after the service instantiation. This
addMethod()
has to follow a add()
or another addMethod()
or
setScope()
call. Multiple addMethod()
s can be chained together., (*76)
$methodName
can be a parameter reference. $methodArguments
can have
parameter or service references., (*77)
setScope(string $scope): this
Set scope for the previous added service in the chain of add()
or
addMethod()
. There are two predefined scope contants, shared scope
Container::SCOPE_SHARED
and single scope Container::SCOPE_SINGLE
., (*78)
dump(bool $toScreen = true): true|string
Will print out all the definitions and mappings or return the output., (*79)
-
Extension related APIs, (*80)
addExtension(ExtensionAbstract $extension): this
Explicitly load an extension into the container., (*81)
Note Calling extension related methods will automatically load
corresponding extensions., (*82)
setTag(string|array $tagOrTagArray): this
TaggableExtension set/replace container tag(s). Tags can be used to
selectly load definition files or definition providers., (*83)
hasTag(string|array $tagOrTagArray): bool
TaggableExtension check the existence of tag(s) in the container. One
tag match will return true
, otherwise return false
, (*84)
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., (*85)
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., (*86)
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., (*87)
addProvider(string|ProviderAbstract $providerOrClass): this
ProviderExtension add definition provider to the container either by
provider classname or a provider object., (*88)