ob-ivan / sd-dependency-injection
Requires (Dev)
- phpunit/phpunit: ^6.2
README
注册服务并将它们注入消费者。
安装
composer require ob-ivan/sd-dependency-injection
用法
术语
在大型应用程序中,某些类实例需要被许多其他类共享的情况很常见。例如,一个数据库连接实例在各个控制器类之间共享。
这些共享的类被称为 服务(它们为其他类提供一些有用的功能),而共享它们的类被称为 消费者(它们消费提供的服务)。
让消费者知道它需要访问的所有服务的整个过程称为 注入。
消费者可以正式声明它需要的服务的列表。这被称为列出其 依赖项。将消费者的依赖项列表取出来并将相应的服务注入其中的过程称为,不出所料,依赖注入。
依赖注入容器(DIC)是实现依赖注入的一种方式。它要求每个服务都在容器中以某种 公共名称 注册,并且消费者将它们所需服务的公共名称作为正式的依赖项列表列出。
依赖项可以通过几种不同的方式注入消费者。在这个库中,支持以下三种类型的依赖注入:
- 设置器注入(也称为 接口注入)- 对于实现 DeclarerInterface 的消费者实例,调用每个声明依赖项的设置器。
- 构造函数注入 - 对于消费者的 ClassName,解析其构造函数参数作为依赖项名称,并使用适当的服务调用构造函数。然后在创建的实例上调用设置器注入。
- 参数注入 - 对于消费者 Function,解析其参数作为依赖项名称,并使用适当的参数调用该 Function。
您可以使用容器来注册服务、生成消费者或将依赖项注入任意代码。
- 消费者 = 函数 | 实例
- 消费者初始化器 = 类名称 | 消费者
- 服务初始化器 = 消费者初始化器 | 值
构造
容器用原始值初始化,不允许使用初始化器
$container = new SD\DependencyInjection\Container([ 'name' => 'Chewbaka', // not interpreted as a class name ]);
如果您有多个容器,您可以通过合并它们来创建一个新的容器
$mergedContainer = SD\DependencyInjection\Container::merge($container1, $container2);
注册服务
使用服务初始化器注册服务
// Setter injection - use when constructing a Service is cheap and doesn't involve resource allocating. $container->register('helloWorld', new HelloWorldService('Anakin')); // Constructor injection + Setter injection - use when Service does not require constructor arguments. $container->register('helloWorld', HelloWorldService::class); // Argument injection + Setter injection - use when Service requires a constructor argument or // Service construction is anyhow a complicated process. $container->register('helloWorld', function ($name) { if ($name === strtoupper($name)) { return new HelloShouterService($name); } return new HelloWorldService($name); }); // No injection - use for setting parameters. $container->register('name', $container->value('Skywalker'));
您不必在容器内部注册容器,因为它默认是自我感知的
// The 'container' common name is reserved to refer to the container itself. $container === $container->get('container');
您可以扩展已注册的服务
// Initial registering: $container->register('currency', SD\Currency\Repository::class); // Later on: $container->extend('currency', function ($container, $currency) { $store = $container->produce(SD\CurrencyStore\Wpdb::class); $currency->setStore($store); return $currency; });
生成消费者
使用消费者初始化器生成消费者(不支持值)
$controller = $container->produce(HelloWorldController::class); // or: $controller = $container->produce(new HelloWorldController('Luke')); // or: $controller = $container->produce(function ($catchPhrase) { return new HelloWorldController($catchPhrase); });
您可以将服务注入任何消费者
// Inject into an already instantiated consumer: $serviceAwareCalculator = $container->inject($serviceUnawareCalculator); // Inject into a callable: $response = $container->inject(function ($helloWorldService) use ($name) { return $helloWorldService->greet($name); });
尽管不鼓励这样做,但您可以将容器用作服务定位器,如果执行上下文不允许您使用依赖注入的力量
$legacyConsumer = new LegacyConsumer($container->get('brand_new_service'));
定义消费者
类消费者(与可调用消费者相对)可以在以下任何一种方式中声明它们的依赖项——或两者的组合
- 通过实现
DeclarerInterface并从declareDependencies方法返回一个公共名称列表。 - 通过实现
AutoDeclarerInterface并导入其相应依赖项的AutoDeclarerTrait和AwareTrait。
由于在 declareDependencies 方法中列出公共名称会导致大量重复,我们建议为每个定义的服务定义一个 AwareTrait。
以下是一个示例代码
trait ExampleAwareTrait { // Here's the magic. Create a field named starting with $autoDeclare and containing the service's // common name as a value. Don't forget to import AutoDeclarerTrait! protected $autoDeclareExample = 'example'; private $example; // Setter name must match the common name. public function setExample(ExampleService $example) { $this->example = $example; } // An example way to access an example service instance. protected function getExample() { return $this->example; } }
这种方式定义消费者,会告诉容器将其注入 ExampleService —— 如果不可用,则会抛出异常。
use SD\DependencyInjection\AutoDeclarerInterface; use SD\DependencyInjection\AutoDeclarerTrait; class ExampleConsumer implements AutoDeclarerInterface { use AutoDeclarerTrait; use ExampleAwareTrait; public function run() { return $this->getExample()->doSomething(); } }
服务提供者
您可以通过将服务名放入提供者中进一步封装服务的通用名称。
use SD\DependencyInjection\ProviderInterface; // This provides a correspondence between the service instance and its common name. class ExampleProvider implements ProviderInterface { public function getServiceName(): string { return 'example'; // the common name } public function provide() { return new ExampleService(); } }
然后,将服务与其提供者的实例注册
$container->connect(new ExampleProvider());
这种方式,注册代码和消费者都不需要知道通用名称。他们只需引用提供者实例和 AwareTrait,它就正常工作了。嘿,这就是魔法!
开发
要运行测试
composer install vendor/bin/phpunit