/di

Slim, powerful and full compatible PSR-11 dependency injection library for PHP

Primary LanguagePHPApache License 2.0Apache-2.0

di

Build Status Scrutinizer Code Quality Code Climate PHP 7 Latest Stable Version License

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 the di.service node.

Features

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

    • 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

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

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

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

      // 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
      ]);
  • Static access

    • Access predefined services statically

      Services in the container can also be access through a static way. But get and has 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();
  • 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.

      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

      $config = [
      
          'di.after' => [
              'setLogger',        // logger aware
              'setCache',         // cache aware
              'setDispatcher',    // event aware
              'setContainer',     // container aware
              ...
          ],
      ];
      ...
  • ContainerAWareInterface

    Both ContainerAWareInterface and ContainerAWareTrait available.

APIs

  • Container related

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

License