README.md

Summary

Maintainability
Test Coverage
# phossa2/di [ABANDONED]

**PLEASE USE [phoole/di](https://github.com/phoole/di) library instead**

[![Build Status](https://travis-ci.org/phossa2/di.svg?branch=master)](https://travis-ci.org/phossa2/di)
[![Code Quality](https://scrutinizer-ci.com/g/phossa2/di/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phossa2/di/)
[![Code Climate](https://codeclimate.com/github/phossa2/di/badges/gpa.svg)](https://codeclimate.com/github/phossa2/di)
[![PHP 7 ready](http://php7ready.timesplinter.ch/phossa2/di/master/badge.svg)](https://travis-ci.org/phossa2/di)
[![HHVM](https://img.shields.io/hhvm/phossa2/di.svg?style=flat)](http://hhvm.h4cc.de/package/phossa2/di)
[![Latest Stable Version](https://img.shields.io/packagist/vpre/phossa2/di.svg?style=flat)](https://packagist.org/packages/phossa2/di)
[![License](https://img.shields.io/:license-mit-blue.svg)](http://mit-license.org/)

**phossa2/di** is a *fast* and *powerful* [Container-Interop][Interop] or
[PSR-11][PSR-11] implementation of dependency injection library for PHP. It
builds upon the versatile [phossa2/config][config] library and supports
[autowiring](#auto), [container delegation](#delegate),
[object decorating](#decorate), [object scope](#scope) and more.

It requires PHP 5.4, supports PHP 7.0+ and HHVM. It is compliant with
[PSR-1][PSR-1], [PSR-2][PSR-2], [PSR-4][PSR-4], and proposed [PSR-5][PSR-5],
[PSR-11][PSR-11].

[PSR-1]: http://www.php-fig.org/psr/psr-1/ "PSR-1: Basic Coding Standard"
[PSR-2]: http://www.php-fig.org/psr/psr-2/ "PSR-2: Coding Style Guide"
[PSR-4]: http://www.php-fig.org/psr/psr-4/ "PSR-4: Autoloader"
[PSR-5]: https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md "PSR-5: PHPDoc"
[PSR-11]: https://github.com/container-interop/fig-standards/blob/master/proposed/container.md "Container interface"
[Interop]: https://github.com/container-interop/container-interop "Container-Interop"
[config]: https://github.com/phossa2/config "phossa2/config"

Installation
---
Install via the `composer` utility.

```
composer require "phossa2/di=2.*"
```

or add the following lines to your `composer.json`

```json
{
    "require": {
       "phossa2/di": "2.*"
    }
}
```

Usage
---

- With [*autowiring*](#auto) of classes.

  A couple of simple classes as follows,

  ```php
  // file cache.php
  class MyCache
  {
      private $driver;

      public function __construct(MyCacheDriver $driver)
      {
          $this->driver = $driver;
      }

      // ...
  }

  class MyCacheDriver
  {
      // ...
  }
  ```

  Instead of creating `MyCacheDriver` and `MyCache` instances manually, you may
  get the both instances automatically using the DI container,

  ```php
  use Phossa2\Di\Container;

  // should be aware of these classes
  require_once __DIR__ . '/cache.php';

  // create the container
  $container = new Container();

   // 'MyCache' classname as the service id
  if ($container->has('MyCache')) {
      $cache = $container->get('MyCache');
  }
  ```

  With [autowiring](#auto) is turned on by default, the container will look for
  the `MyCache` class if no service defined as 'MyCache', and resolves its
  dependency injection (here, is the `MyCacheDriver` instance) automatically
  when creating the `MyCache` instance.

- With manual service addition using `set()`

  Services can be added to the container manually by using `set()` method.

  ```php
  use Phossa2\Di\Container;

  // should be aware of these classes
  require_once __DIR__ . '/cache.php';

  // create the container
  $container = new Container();

  // turn off autowiring
  $container->auto(false);

  // define service with an array
  $container->set('cache', [
      'class' => 'MyCache', // classname
      'args'  => ['${#driver}'] // constructor arguments
  ]);

  // add service 'driver' with a callback
  $container->set('driver', function() {
      return new \MyCacheDriver();
  });

  // get the service
  var_dump($container->get('cache') instanceof \MyCache); // true
  ```

  A [service reference](#sref) `'${#driver}'` used in the constructor arguments
  here indicating it is the `driver` service instance from the container.

- With configuration from files or array

  Container uses a `Phossa2\Config\Interfaces\ConfigInterface` instance as its
  definition resolver for parameter and service definitions. The config instance
  may either read configs from files or get configs from an array as follows,

  ```php
  use Phossa2\Di\Container;
  use Phossa2\Config\Config;

  $configData = [
      // container service definitions
      'di.service' => [
          // cache service
          'cache'  => ['class' => 'MyCache', 'args' => ['${#driver}']],

          // cache driver, classname directly
          'driver' => 'MyCacheDriver',
      ],

      // common methods to run after each instantiation
      'di.common' => [
          [
            function($obj) { return $obj instanceof \MyCacheDriver; }, // tester
            function($obj, $container) { echo "ok"; } // runner
          ],
      ],

      // init methods after container created
      'di.init' => [
            // default section
            'default' => [
                ['setLogger', ['${#logger}']],
                // ...
            ],

            // my own section
            'mystuff' => [
            ],
      ],
  ];

  // create $config instance with provided data
  $config = new Config(null, null, $configData);

  // instantiate container with $config instance and definition base node 'di'
  $container = new Container($config, 'di');

  // get service by id 'cache' (di.service.cache)
  $cache = $container->get('cache');

  // true
  var_dump($cache instanceof \MyCache);
  ```

  By default, 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

    See [phossa2/config reference](https://github.com/phossa2/config#ref) for
    detail. Parameter references are read from configuration files or can be
    defined by container method `param()` as follows,

    ```php
    // define a new parameter for the container
    $container->param('cache.dir', '${system.tmpdir}/cache');

    // use the cache.dir parameter defined above
    $container->set('cache', [
        'class' => '${cache.class}', // predefined in file
        'args'  => ['${cache.dir}']  // just defined above
    ]);
    ```

  - <a name="sref"></a>Service references

    Service reference in the form of '${#service_id}' can be used to referring
    a service instance in the container (or in the [delegator](#delegate)).

    ```php
    $container->set('cache', [
        'class' => '${cache.class}',
        'args'  => ['${#cache_driver}'] // service reference
    ]);
    ```

    **Two reserved service references are '${#container}' and '${#config}'**.
    These two are refering 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 configs or as the arguments for all
    container methods (except for the paramter `$id` of the methods).

    ```php
    // run(callable, arguments) with references
    $container->run(['${#logger}', 'warning'], ['warning from ${log.facility}']);

    // resolve references
    $data = ['${system.dir}', '${#logger}'];
    $container->resolve($data); // all references in $data are now resolved
    ```

- <a name="auto"></a>**Autowiring and mapping**

  *Autowiring* is the ability of container instantiating objects and resolving
  its dependencies automatically by their classname. The base for autowiring is
  the PHP function parameter *type-hinting*.

  By reflecting on class, constructor and methods, *phossa2/di* is able to find
  the right class for the instance (user need to use the classname as the
  service id) and right classes for its dependencies (type-hinted with the
  classnames).

  If interface name is used for dependency type-hint, users may set up the
  mapping of interfaces to the right classnames as follows,

  ```php
  // map an interface to a classname
  $container->set(
      'Phossa2\\Cache\\CachePoolInterface', // MUST NO leading backslash
      'Phossa2\\Cache\\CachePool'
  );

  // map an interface to a service id reference
  $container->set('Phossa2\\Cache\\CachePoolInterface', '${#cache}');

  // map an interface to a parameter reference
  $container->set('Phossa2\\Cache\\CachePoolInterface', '${cache.class}');

  // map an interface to a callback
  $container->set('Phossa2\\Cache\\CachePoolInterface', function() {
      return new \Phossa2\Cache\CachePool();
  });
  ```

  Or define mappings in the config node `di.service` as follows,

  ```php
  $configData = [
      // ...
      'di.service' => [
          'Phossa2\\Cache\\CachePoolInterface' => '${cache.class}',
          // ...
      ],
      // ...
  ];
  ```

  Autowiring can be turned on/off. Turning off autowiring will enable user to
  check any defintion errors without automatic loading.

  ```php
  // turn off auto wiring
  $container->auto(false);

  // turn on auto wiring
  $container->auto(true);
  ```

- <a name="decorate"></a>**Object decorating**

  *Object decorating* is to apply decorating changes (executing methods etc.)
  right after the instantiation of a service instance.

  - Decorating methods for *individual instance* only

    ```php
    $container->set('cache', [
        'class'   => 'Phossa2\\Cache\\Cache',
        'args'    => ['${#cachedriver}'], // constructor arguments
        'methods' => [
            ['clearCache'], // method of $cache
            ['setLogger', ['${#logger}']], // method with arguments
            [[$logger, 'setLabel'], ['cache_label']], // callable with arguments
            [['${#driver}, 'init']], // pseduo callable
            // ...
        ],
    ]);
    ```

    By adding `methods` section into the `cache` service definition in the
    form of `[ callableOrMethodName, OptionalArgumentArray ]`, these methods
    will be executed right after `cache` instantiation.

    `callableOrMethodName` here can be,

    - method name of current instance

    - a valid callable

    - a psedudo callable with references (after resolving the references, it is
      a valid callable).

    `OptionalArgumentArray` here can be,

    - empty

    - array of values or references

  - Common decorating methods for *all instances*

    ```php
    $configData = [
        // common methods for all instances
        'di.common' => [
            // [ tester(): bool, method ]
            [
                function($object, $container) {
                    return $object instanceof 'Psr\\Log\\LoggerAwareInterface'
                },
                ['setLogger', ['${#logger}']]
            ],
            // ...
        ],
    ];
    ```

    Common methods can be configured in the 'di.common' node to apply to all the
    instances right after their instantiation. The definition consists of two
    parts, the first is a tester callable takes current instance and the
    container as parameters and returns a boolean value. The second part is in
    the same method format as in the service definition 'methods'.

    To skip execution of common methods for one service, define it with `skip`
    as follows,

    ```php
    $container->set('logger', [
        'class' => 'Phossa2\Logger\Logger',
        'skip'  => true
    ]);
    ```

- <a name="delegate"></a>**Container delegation**

  According to [Interop Container Delegate Lookup](https://github.com/container-interop/fig-standards/blob/master/proposed/container.md),
  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 the `ContainerInterface`).

  - 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.

  *phossa2/di* fully supports the delegate feature.

  ```php
  use Phossa2\Di\Delegator;

  // create delegator
  $delegator = new Delegator();

  // create container
  $container = new Container();

  // insert container into delegator
  $delegator->addContainer($container);

  // get from delegator now
  $cache = $delegator->get('cache');
  ```

- <a name="scope"></a>**Object scope**

  - Shared or single scope

    By default, service instances in the container are shared inside the
    container. Actually 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.

    ```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

    // a new cache instance with 'one()'
    $cache3 = $container->one('cache');

    // different instances
    var_dump($cache1 !== $cache3); // true

    // but both share the same cacheDriver dependent service
    var_dump($cache1->getDriver() === $cache3->getDriver()); // true
    ```

    Or define the service as `Container::SCOPE_SINGLE`

    ```php
    $container->set('cache', [
        'class' => '\\Phossa2\\Cache\\CachePool'),
        'scope' => Container::SCOPE_SINGLE
    ]);

    // each get() will return a new cache
    $cache1 = $container->get('cache');
    $cache2 = $container->get('cache');

    // different instances
    var_dump($cache1 !== $cache2); // true

    // dependent service are shared
    var_dump($cache1->getDriver() === $cache->getDriver()); // true
    ```

    Set the container's default scope to `Container::SCOPE_SINGLE` will cause
    each `get()` returns a new instance (unless 'scope' is explicitly defined
    as for this service).

    ```php
    // set default scope to SCOPE_SINGLE
    $container->share(false);

    // a new copy of cache service
    $cache1 = $container->get('cache');

    // another new cache service
    $cache2 = $container->get('cache');

    // different instances
    var_dump($cache1 !== $cache2); // true

    // dependencies are different
    var_dump($cache1->getDriver() !== $cache->getDriver()); // true
    ```

  - Use your own scope

    You may get an instance in your own scope as follows, no matter whatever the
    default scope or the defined scope for this instance,

    ```php
    // instance in scope 'myScope'
    $cacheOfMyScope = $container->get('cache@myScope');

    // new instance in single scope, even though you specified one
    $cacheOfSingle = $container->one('cache@myScope');

    // instance in shared scope
    $cache = $container->get('cache');
    ```

    Service references can also have scope defined as follows,

    ```php
    $container->set('cache', [
        'class' => 'Phossa2\\Cache\\Cache',
        'args'  => ['${#driver@myScope}'] // use driver of myScope
    ]);
    ```

  - Share instance only in certain object

    Sometimes, user may want to share one instance only inside certain object.

    ```php
    class A {
        private $b, $c;

        public function __construct(B $b, C $c) {
            $this->b = $b;
            $this->c = $c;
        }

        public function getB() {
            return $this->b;
        }

        public function getC() {
            return $this->c;
        }
    }

    class B {
        private $c;
        public function __construct(C $c) {
            $this->c = $c;
        }
    }

    class C {
    }

    // an instance of A
    $a1 = $container->one('A');

    // another instance of A
    $a2 = $container->one('A');

    // $a1 and $a2 is different
    var_dump($a1 !== $a2); // true

    // C is the same under A
    var_dump($a1->getC() === $a1->getB()->getC()); // true

    // C is also shared among different A
    var_dump($a1->getC() === $a2->getC()); // true
    ```

    In previous code, `C` is not only shared under the `A`, but also shared
    among different instances of `A`. What if user want to share `C` only under
    the `A` but not between `A` ?

    By setting scope of `C` to '#A' as follows,

    ```php
    // this scope only takes effect when under service A
    $container->set('C', [ 'class' => 'C', 'scope' => '#A']);

    // an instance of A
    $a1 = $container->one('A');

    // another instance of A
    $a2 = $container->one('A');

    // C is different among different A
    var_dump($a1->getC() !== $a2->getC()); // true

    // C is same under one A
    var_dump($a1->getC() === $a1->getB()->getC()); // true
    ```

- <a name="array"></a>**Array access and read only**

  Both `Phossa2\Di\Container` and `Phossa2\Di\Delegator` implements
  `\ArrayAccess` interface.

  ```php
  $container = new Container();
  $delegator = new Delegator();

  $delegator->addContainer($container);

  // equals to $delegator->has('A')
  if (isset($delegator['A'])) {
      var_dump($delegator['A'] === $container['A']); // true
  }
  ```

  By default `Phossa2\Di\Container` is writable which means user can add new
  service definitions to the container manually by using `set()`.

  **Note** If container is set readonly, [auto wiring](#auto) is turned off
  automatically.

  To get a readonly container,

  ```php
  $container = new Container();
  $container->setWritable(false);

  var_dump($container->isWritable() === false); // true

  // delegator also
  $delegator = new Delegator();
  $delegator->setWritable(false);

  var_dump($delegator->isWritable() === false); // true
  ```

APIs
---

- <a name="api"></a>Container related

  - `get(string $id): object` from *ContainerInterface*

    If not found, `Phossa2\Di\Exception\NotFoundException` is thrown. `$id`
    may have '@scope' appended. An array can be used as the second parameter
    for object constructor's arguments.

  - `has(string $id): bool` from *ContainerInterface*

    `$id` may have '@scope' appended.

  - `one(string $id, array $args = []): object` from *ExtendedContainerInterface*

    Get a new instance for this `$id`. May provide new arguments for the object
    constructor.

  - `run(mixed $callable, array $args = []): mixed` from *ExtendedContainerInterface*

    Execute a callable or pseduo callable (with references) with provided
    arguments (may contain references also)

  - `param(string $name, mixed $value): bool` from *ExtendedContainerInterface*

    Set a parameter in the container's config tree for later to be used as a
    reference. Returns a bool value to indicate status.

  - `alias(string $id, object|string $object): bool` from *ExtendedContainerInterface*

    Aliasing an object as `$id` in the container. Difference with `set()` method
    is that getting this object will skip the execution of common methods.

    Returns `true` on success and `false` on failure.

  - `auto(bool $flag): $this` from *AutoWiringInterface*

    Set container autowiring `ON` or `OFF`.

  - `isAuto(): bool` from *AutoWiringInterface*

    Test container is auto wiring `ON` or `OFF`.

  - `resolve(mixed &$data): $this` from *ReferenceResolveInterface*

    Recursively resolving references in the `$data`. `$data` might be `string`
    or `array`. Other data type will be untouched.

  - `share(bool $flag = true): $this` from *ScopeInterface*

    Make container default scope to `Container::SCOPE_SHARED` or
    `Container::SCOPE_SINGLE`.

  - `set(string $id, mixed $value): bool` from *WritableInterface*

    Set a service with `$id` into the container. `$value` can be an array in
    the form of array as follows,

    ```php
    $value = [
        'class' => mixed,   // classname/object/callback etc.
        'args'  => array,   // arguments for the constructor or callback
        'scope' => string,  // default scope for this service
        'skip'  => bool,    // skip common methods for the instance
    ];
    ```

    or `$value` can be classname, object, callback etc. Returns `true` on
    success or `false` on failure. Container has to be writable, otherwise
    a `LogicException` will be thrown.

  - `isWritable(): bool` from *WritableInterface*

    Is this container writable ?

  - `setWritable(bool $writable): bool` from *WritableInterface*

    Set this container writable or readonly.

- <a name="apid"></a>Delegator related

  - `get(string $id): object` from *ContainerInterface*

    Get service from the delegator. If not found,
    `Phossa2\Di\Exception\NotFoundException` is thrown. `$id` may have '@scope'
    appended as long as underlying container supports this feature.

  - `has(string $id): bool` from *ContainerInterface*

    `$id` may have '@scope' appended as long as underlying container supports
    this feature.

  - `set(string $id, mixed $value): bool` from *WritableInterface*

    Set a service with `$id` into the delegator as long as the delegator is
    writable.

  - `isWritable(): bool` from *WritableInterface*

    Is this delegator writable ?

  - `setWritable(bool $writable): bool` from *WritableInterface*

    Set this delegator writable or readonly.

  - `addContainer(ContainerInterface $container): $this` from *DelegatorInterface*

    Add a container to the delegator.

Change log
---

Please see [CHANGELOG](CHANGELOG.md) from more information.

Testing
---

```bash
$ composer test
```

Contributing
---

Please see [CONTRIBUTE](CONTRIBUTE.md) for more information.

Dependencies
---

- PHP >= 5.4.0

- phossa2/config >= 2.0.12

- phossa2/shared >= 2.0.21

License
---

[MIT License](http://mit-license.org/)