README.md
# di
[![Build Status](https://travis-ci.com/phoole/di.svg?branch=master)](https://travis-ci.com/phoole/di)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phoole/di/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phoole/di/?branch=master)
[![Code Climate](https://codeclimate.com/github/phoole/di/badges/gpa.svg)](https://codeclimate.com/github/phoole/di)
[![PHP 7](https://img.shields.io/packagist/php-v/phoole/di)](https://packagist.org/packages/phoole/di)
[![Latest Stable Version](https://img.shields.io/github/v/release/phoole/di)](https://packagist.org/packages/phoole/di)
[![License](https://img.shields.io/github/license/phoole/di)]()
Slim, powerful and full compatible [PSR-11][PSR-11] dependency injection library for PHP.
It builds upon the versatile [phoole/config][config] library and supports
[object decorating](#decorate), [object scope](#scope) and more. It requires PHP 7.2+. It is
compliant with [PSR-1][PSR-1], [PSR-4][PSR-4], [PSR-11][PSR-11] and [PSR-12][PSR-12].
[PSR-1]: http://www.php-fig.org/psr/psr-1/ "PSR-1: Basic Coding Standard"
[PSR-4]: http://www.php-fig.org/psr/psr-4/ "PSR-4: Autoloader"
[PSR-11]: http://www.php-fig.org/psr/psr-11/ "Container Interface"
[PSR-12]: http://www.php-fig.org/psr/psr-12/ "Extended Coding Style Guide"
[config]: https://github.com/phoole/config "phoole/config"
Installation
---
Install via the `composer` utility.
```
composer require "phoole/di"
```
or add the following lines to your `composer.json`
```json
{
"require": {
"phoole/di": "1.*"
}
}
```
Usage
---
- With configuration from files or definition array
```php
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 the `di.service` node.
Features
---
- <a name="ref"></a>**References**
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.
- <a name="pref"></a>Parameter references like `${system.tempdir}`
```php
$config = [
...
// use predefined 'sytem.tmpdir' in arguments etc.
'di.service.cacheDriver' => [
'class' => FileAdaptor::class,
'args' => ['${system.tmpdir}'],
],
...
];
```
See [phoole/config reference](https://github.com/phoole/config#ref) for
detail. Parameter references are read from configuration files or array.
- <a name="sref"></a>Service references like `${#cache}`
Service object reference in the form of `${#serviceId}` can be used to referring
a service instance in the container.
```php
$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.
```php
$confData = [
// methods executed after ALL object initiation
'di.after' => [
[['${#logger}', 'notice'], ['object created using ${log.facility}']]
]
];
```
- <a name="decorate"></a>**Object decorating**
*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
```php
$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` or `after` section into the `cache` service definition in the
form of `[callableOrMethodName, OptionalArgumentArray]`, these methods will be
executed right before/after `cache` instantiation.
`callableOrMethodName` here can be,
- method name of initiated object
```php
...
'after' => [
// $obj->setLogger($logger), $logger will be injected automatically
'setLogger', // object implementing 'LoggerAwareInterface'
],
...
```
- a valid callable which takes initiated object as parameter
```php
...
'after' => [
// callable takes initiated object as parameter
function($obj) {
},
],
...
```
- a pseudo callable with references (after resolving the references, it is
a valid callable).
```php
...
'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**
```php
$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.
- <a name="scope"></a>**Object scope**
- 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.
```php
// 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
```php
// 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,
```php
$container->set('cache', [
'class' => Cache::class,
'args' => ['${#driver@myScope}'] // use driver of myScope
]);
```
- <a name="static"></a>**Static access**
- Access predefined services statically
Services in the container can also be access through a static way. But `get`
and `has` are reserved.
```php
// 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
```php
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();
```
- <a name="autowiring"></a>**Autowiring** and **auto injection**
- 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.
```php
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 the `di.after` section
```php
$config = [
'di.after' => [
'setLogger', // logger aware
'setCache', // cache aware
'setDispatcher', // event aware
'setContainer', // container aware
...
],
];
...
```
- <a name="aware"></a>**`ContainerAWareInterface`**
Both `ContainerAWareInterface` and `ContainerAWareTrait` available.
APIs
---
- <a name="api"></a>Container related
- `get(string $id): object` from *ContainerInterface*
- `has(string $id): bool` from *ContainerInterface*
`$id` may have `@` or `@scope` appended.
Testing
---
```bash
$ composer test
```
Dependencies
---
- PHP >= 7.2.0
- phoole/config >= 1.*
License
---
- [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)