此包已废弃,不再维护。未建议替代包。

一个强大的PHP依赖注入库的Container-Interop/PSR-11实现。

2.0.1 2016-07-20 10:04 UTC

This package is not auto-updated.

Last update: 2020-01-24 16:09:47 UTC


README

请使用phoole/di库代替

Build Status Code Quality Code Climate PHP 7 ready HHVM Latest Stable Version License

phossa2/di是一个快速强大Container-InteropPSR-11实现,用于PHP依赖注入库。它建立在功能丰富的phossa2/config库之上,支持自动注入容器委托对象装饰对象作用域等功能。

它需要PHP 5.4,支持PHP 7.0+和HHVM。它符合PSR-1PSR-2PSR-4,以及提议的PSR-5PSR-11

安装

通过composer工具安装。

composer require "phossa2/di=2.*"

或者将以下行添加到您的composer.json

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

用法

  • 使用类的自动注入

    以下是一些简单的类

    // file cache.php
    class MyCache
    {
        private $driver;
    
        public function __construct(MyCacheDriver $driver)
        {
            $this->driver = $driver;
        }
    
        // ...
    }
    
    class MyCacheDriver
    {
        // ...
    }

    而不是手动创建MyCacheDriverMyCache实例,您可以使用DI容器自动获取这两个实例

    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');
    }

    默认情况下,启用自动注入,当容器中没有定义名为'MyCache'的服务时,容器将查找MyCache类,并在创建MyCache实例时自动解析其依赖注入(这里,是MyCacheDriver实例)。

  • 使用set()手动添加服务

    可以使用set()方法手动将服务添加到容器中。

    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

    这里构造函数参数中使用的服务引用'${#driver}'表示它是容器中的driver服务实例。

  • 从文件或数组获取配置

    容器使用Phossa2\Config\Interfaces\ConfigInterface实例作为参数和服务定义的定义解析器。配置实例可以从以下文件中读取配置,或者从数组中获取配置

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

    默认情况下,容器相关配置位于节点di下,服务定义位于di.service节点下。

特性

  • 参考

    可以使用形如 '${reference}' 的引用来引用配置或容器中的预定义参数。

    引用名称中不允许包含 '$', '{', '}', '.' 字符。字符 '#', '@' 有特殊含义,因此不应包含在 普通 服务名称中。

    • 参数引用

      请参阅 phossa2/config 引用 获取详细信息。参数引用可以从配置文件中读取,也可以通过容器的 param() 方法定义,如下所示:

      // 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
      ]);
    • 服务引用

      可以使用形如 '${#service_id}' 的服务引用来引用容器中的服务实例(或在 委托者 中)。

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

      两个保留的服务引用是 '${#container}' 和 '${#config}'。这两个引用分别指向容器实例本身以及它使用的配置实例。这两个引用可以像其他服务引用一样使用。

    • 使用引用

      引用可以在配置文件中的任何位置使用,也可以作为所有容器方法(除方法的参数 $id 外)的参数。

      // 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
  • 自动装配和映射

    自动装配 是容器自动实例化对象并解决其依赖项的能力。自动装配的基础是 PHP 函数参数 类型提示

    通过反射类、构造函数和方法,phossa2/di 能够找到实例的正确类(用户需要使用类名作为服务 ID)以及其依赖项的正确类(使用类名类型提示)。

    如果使用接口名称进行依赖类型提示,用户可以设置接口到正确类名的映射,如下所示:

    // 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();
    });

    或者,在配置节点 di.service 中定义映射,如下所示:

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

    可以开启或关闭自动装配。关闭自动装配将允许用户在自动加载之前检查任何定义错误。

    // turn off auto wiring
    $container->auto(false);
    
    // turn on auto wiring
    $container->auto(true);
  • 对象装饰

    对象装饰 是在服务实例化后立即应用装饰更改(执行方法等)。

    • 仅对 单个实例 的装饰方法

      $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
              // ...
          ],
      ]);

      通过将 methods 部分添加到 cache 服务定义中,形式为 [ callableOrMethodName, OptionalArgumentArray ],这些方法将在 cache 实例化后立即执行。

      这里的 callableOrMethodName 可以是:

      • 当前实例的方法名

      • 一个有效的可调用函数

      • 一个带有引用的伪可调用函数(在解析引用后,它是一个有效的可调用函数)。

      这里的 OptionalArgumentArray 可以是:

      • 空的

      • 值或引用的数组

    • 对所有 实例 的通用装饰方法

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

      通用方法可以在 'di.common' 节点中配置,以在实例化后立即应用于所有实例。定义由两部分组成,第一部分是一个测试可调用函数,它接受当前实例和容器作为参数,并返回一个布尔值。第二部分与服务定义中的 'methods' 部分具有相同的方法格式。

      要跳过某个服务的通用方法执行,可以将其定义为如下所示:

      $container->set('logger', [
          'class' => 'Phossa2\Logger\Logger',
          'skip'  => true
      ]);
  • 容器委托

    根据 Interop Container Delegate Lookup,容器可以注册一个委托容器(委托者),并且

    • get() 方法的调用应该只返回容器的一部分。如果条目不是容器的一部分,应抛出异常(如 ContainerInterface 所请求的)。

    • has() 方法的调用应该只返回 true,如果条目是容器的一部分。如果条目不是容器的一部分,则应该返回 false。

    • 如果获取的条目有依赖项,而不是在容器中执行依赖项查找,而是在委托容器(委托者)中执行查找。

    • 重要 默认情况下,查找应该在委托容器上执行,而不是在容器本身上。

    phossa2/di 完全支持委托功能。

    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');
  • 对象作用域

    • 共享或单例作用域

      默认情况下,容器中的服务实例在容器内是共享的。实际上,它们具有 Container::SCOPE_SHARED 的作用域。如果用户希望每次都使用不同的实例,他们可以使用 one() 方法或以 Container::SCOPE_SINGLE 作用域定义服务。

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

      或将服务定义为 Container::SCOPE_SINGLE

      $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

      将容器的默认作用域设置为 Container::SCOPE_SINGLE 将导致每次 get() 都返回一个新实例(除非明确为该服务定义了 'scope')。

      // 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
    • 使用自己的作用域

      无论默认作用域或该实例定义的作用域如何,您可以如下获取自己的作用域实例:

      // 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');

      服务引用也可以按以下方式定义作用域:

      $container->set('cache', [
          'class' => 'Phossa2\\Cache\\Cache',
          'args'  => ['${#driver@myScope}'] // use driver of myScope
      ]);
    • 仅在特定对象中共享实例

      有时,用户可能只想在特定对象内部共享一个实例。

      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

      在前面的代码中,C 不仅在 A 下共享,还在 A 的不同实例之间共享。如果用户只想在 A 下共享 C 而不是在 A 之间共享,怎么办?

      如下设置 C 的作用域为 '#A',

      // 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
  • 数组访问和只读

    Phossa2\Di\ContainerPhossa2\Di\Delegator 都实现了 \ArrayAccess 接口。

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

    默认情况下,Phossa2\Di\Container 是可写的,这意味着用户可以通过使用 set() 手动将新服务定义添加到容器中。

    注意 如果容器设置为只读,则自动连接将自动关闭。

    要获取只读容器,

    $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

  • 容器相关

    • get(string $id): object 来自 ContainerInterface

      如果未找到,将抛出 Phossa2\Di\Exception\NotFoundException。可以附加 '@scope' 到 $id。可以将数组用作对象构造函数参数的第二个参数。

    • has(string $id): bool 来自 ContainerInterface

      $id 可以附加 '@scope'。

    • one(string $id, array $args = []): object 来自 ExtendedContainerInterface

      为这个 $id 获取一个新实例。可以为对象构造函数提供新参数。

    • run(mixed $callable, array $args = []): mixed 来自 ExtendedContainerInterface

      使用提供的参数执行可调用或伪可调用(带有引用)(参数可能包含引用)

    • param(string $name, mixed $value): bool 来自 ExtendedContainerInterface

      在容器的配置树中设置一个参数,以便稍后用作引用。返回一个布尔值以指示状态。

    • alias(string $id, object|string $object): bool 来自 ExtendedContainerInterface

      在容器中将对象别名为 $id。与 set() 方法的区别在于获取此对象将跳过公共方法的执行。

      成功时返回 true,失败时返回 false

    • auto(bool $flag): $this 来自 AutoWiringInterface

      设置容器自动装配为 ONOFF

    • isAuto(): bool 来自 AutoWiringInterface

      测试容器是否自动装配为 ONOFF

    • resolve(mixed &$data): $this 来自 ReferenceResolveInterface

      递归解析 $data 中的引用。 $data 可能是 stringarray。其他数据类型将保持不变。

    • share(bool $flag = true): $this 来自 ScopeInterface

      将容器默认作用域设置为 Container::SCOPE_SHAREDContainer::SCOPE_SINGLE

    • set(string $id, mixed $value): bool 来自 WritableInterface

      将服务 $id 设置到容器中。 $value 可以是以下形式的数组:

      $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
      ];

      或者 $value 可以是类名、对象、回调函数等。成功时返回 true,失败时返回 false。容器必须是可写的,否则将抛出 LogicException

    • isWritable(): bool 来自 WritableInterface

      这个容器是可写的吗?

    • setWritable(bool $writable): bool 来自 WritableInterface

      设置这个容器为可写或只读。

  • 委托相关

    • get(string $id): object 来自 ContainerInterface

      从委托中获取服务。如果未找到,将抛出 Phossa2\Di\Exception\NotFoundException。如果底层容器支持此功能,则 $id 可以附加 '@scope'。

    • has(string $id): bool 来自 ContainerInterface

      $id 可以附加 '@scope',前提是底层容器支持此功能。

    • set(string $id, mixed $value): bool 来自 WritableInterface

      如果委托是可写的,则将服务 $id 设置到委托中。

    • isWritable(): bool 来自 WritableInterface

      这个委托是可写的吗?

    • setWritable(bool $writable): bool 来自 WritableInterface

      设置这个委托为可写或只读。

    • addContainer(ContainerInterface $container): $this 来自 DelegatorInterface

      将容器添加到委托中。

变更日志

请参阅 CHANGELOG 以获取更多信息。

测试

$ composer test

贡献

有关更多信息,请参阅 CONTRIBUTE

依赖

  • PHP >= 5.4.0

  • phossa2/config >= 2.0.12

  • phossa2/shared >= 2.0.21

许可

MIT 许可