PHP 的依赖注入库

1.0 2020-02-03 03:11 UTC

This package is auto-updated.

Last update: 2024-09-14 11:00:28 UTC


README

Build Status SensioLabsInsight

DI 是一个用于 PHP 的依赖注入容器库。

注册服务

首先,创建一个 MattFerris\Di\Di 的实例。使用 set() 方法注册服务,传入服务实例,或者使用闭包来返回服务实例并完成任何适当的初始化。您可以通过指定名为 $di 的参数,或者通过指定任何具有 MattFerris\Di\DiMattFerris\Di\ContainerInterface 类型提示的参数来获取容器实例。

use MattFerris\Di\Di;

$di = new Di();

// register 'FooService'
$di->set('FooService', return new \FooService());
});

// register `BarService` using type-hinted argument
$di->set('FooService', function (Di $container) {
    return new \FooService($container);
});

现在可以通过 get() 获取服务实例。

$fooService = $di->get('FooService');

对服务的每次请求都将返回相同的 FooService 实例。默认情况下,所有定义都是单例。可以使用第三个参数来禁用此行为。

$di->set('FooService', function ($di) {
    return new \FooService();
}, Di::NON_SINGLETON);

现在每次调用 $di->get('FooService') 都将返回一个新的 \FooService() 实例;

如果您的服务依赖于另一个服务,可以在定义内部轻松地检索该服务的引用,无论是手动从容器中检索还是通过注入。

// manually retrieve an instance of FooService
$di->set('BarService', function ($di) {
    $fooService = $di->get('FooService');
    return new \BarService($fooService);
});

// inject an instance of FooService
$di->set('BarService', function (\FooService $fooService) {
    return new \BarService($fooService);
});

您可以使用 has() 检查服务是否已定义。

if ($di->has('FooService')) {
    echo 'Yay!';
}

您可以使用 find() 返回匹配前缀的所有服务。如果您有多个类似类型的服务且它们的定义名称相似,这非常有用。例如,如果您有两个远程 FTP 服务器并且想要将文件保存到它们中,您可以将 FTP 服务注册为 FTP.host1FTP.host2。使用 find() 获取这些服务的实例可以使您的应用程序保持对主机的不可知性,让您能够无缝地配置未来的更多主机。

$services = $di->find('FTP.');

foreach ($services as $key => $service) {
    $service->saveFile($file);
}

$services 将看起来像

array(
    'FTP.host1' => [instance of \FtpService],
    'FTP.host2' => [instance of \FtpService]
)

如果您尝试使用已经存在的键设置定义,将会抛出 DuplicateDefinitionException。可以使用 DuplicateDefinitionException::getKey() 方法检索重复的键。

基于类型的注入

在某些情况下,您可能需要为您的服务定义特定的对象类型,但可能不知道该服务定义的名称在容器中是什么。您不需要直接从容器请求,而是可以通过对参数进行类型提示将服务注入到您的服务定义的闭包中。

$di->set('FooService', function (\Bar\Service\Class $bar) {
    return new \FooService($bar);
});

如果存在一个具有 \Bar\Service\Class 实例的定义,它将被注入到闭包中以供您的定义服务使用。同样,您也可以指定接口或特质名称进行注入。

如果无法解决依赖项,将抛出 DependencyResolutionException。无法解决的类型可以使用 DependencyResolutionException::getType() 方法检索。

可选地,Di 可以使用依赖项的深度解析。可以通过调用 $di->setDeepTypeResolution(true) 启用此功能。启用后,对于容器内部无法满足的基于类型的依赖项,Di 将尝试直接实例化该类型并使用新实例来满足依赖项。构造函数参数将尝试使用依赖注入来解决。

服务提供者

您可以使用 提供者 在您的领域内隔离服务配置。服务 提供者 是任何扩展 MattFerris\Di\ServiceProvider 或实现 MattFerris\Provider\ProviderInterface 的类。当通过 register($provider) 注册时,提供者provides() 方法会传入容器实例,然后可以注册服务。

class MyProvider extends MattFerris\Di\ServiceProvider
{
    public function provides($consumer)
    {
        // validate consumer is an instance of \MattFerris\Di\ContainerInterface
        parent::provides($consumer);

        $di->set('MyService', function () { ... });
    }
}

$di->register(new MyProvider());

$myService = $di->get('MyService');

定义参数

您可以通过 setParameter()getParameter() 为任何服务定义设置参数。这允许您创建更动态的服务定义和配置。

$di->setParameter('db', array(
    'user' => 'joe',
    'pass' => 'p4ssw0rd',
    'host' => 'localhost',
    'db' => 'foo'
));

$di->set('DB', function ($di) {
    $cfg = $di->getParameter('db');
    return new \DbConnection(
        $cfg['user'], $cfg['pass'], $cfg['host'], $cfg['db']
    );
}, true);

现在可以动态定义数据库连接设置。请注意,数据库服务被定义为单例。

代理

键前缀可以被代理到其他容器进行查找。这允许您的应用程序的某些部分或第三方组件配置它们自己的本地容器,但将其插入到中央容器中。

$di->delegate('Foo.', $container);

现在,任何以 Foo. 开头的键都将被代理到 $container 进行处理。

自定义注入

在某些情况下,您可能需要动态实例化一个类,或调用一个方法(静态或非静态),闭包或函数。方法 injectConstructor()injectionMethod()injectStaticMethod()injectFunction() 可以以灵活的方式实现这一点。

class FooClass
{
    public function __construct($argA, $argB)
    {
    }
}

$object = $di->injectConstructor(
    'FooClass',
     array('argA' => 'foo', 'argB' => 'bar')
);

上面的例子返回了一个带有注入构造函数参数的 FooClass 实例。请注意,传递给 injectConstructor() 的第二个参数是一个数组,该数组定义了哪些参数对应哪些值。这些值可以以任何顺序指定,该方法将匹配键与构造函数签名中实际的参数名称。以下示例会产生相同的结果。

$object = $di->injectConstructor(
    'FooClass',
    array('blah' => 'bling', 'argB' => 'bar', 'argA' => 'foo')
);

injectConstructor() 将适当的键分配给相应的参数。没有对应参数的键将被忽略。这个原则同样适用于其他 inject 方法。

$result = $di->injectMethod($object, 'someMethod', array('foo' => 'bar'));

$result = $di->injectStaticMethod('FooClass', 'someMethod', array('foo' => 'bar'));

$result = $di->injectFunction('someFunction', array('foo' => 'bar'));

$result = $di->injectFunction($closure, array('foo' => 'bar'));

当然,您也可以注入定义的服务和参数。

$object = $di->injectConstructor(
    'FooClass',
    array('argA' => $di->get('FooService'), 'argB' => $di->getParameter('foo'))
);

为了方便,inject 方法理解服务和参数的占位符。

$object = $di->injectConstructor(
    'FooClass',
    array('argA' => '%FooService', 'argB' => ':foo')
);

可以通过在服务名称前加上百分号 (%) 来引用服务,可以通过在参数名称前加上冒号 (:) 来引用参数。

使用 inject* 方法时,除非在调用方法时提供了参数,否则都会发生基于类型的注入。这样,您可以针对特定参数覆盖基于类型的注入。您还可以通过为参数提供一个类型来强制基于类型的注入。

$object = $di->injectConstructor(
    'FooClass',
    array('argA' => '\Foo\Service\Class')
);

以反斜杠开头的参数值被认为是类型提示参数。