splot / di
Splot 框架的依赖注入容器。
Requires
- php: >=5.3.3
- container-interop/container-interop: ~1.0
- michaldudek/foundation: >=0.9.2
- symfony/yaml: >=2.1,<3.0
Requires (Dev)
- phpunit/phpunit: ~4.0
README
Splot 框架的依赖注入容器。
Splot 依赖注入容器是一个独立的 PHP 库,有助于实现以下控制反转和依赖注入模式。
它是流行的Symfony 依赖注入组件或PHP-DI或其他实现的一个可行替代方案。
功能
- 支持构造函数(
arguments
选项)和setter注入(call
选项)。 - 包含一个“参数容器”,参数可以用于各种选项、作为依赖项或在彼此之间包含。
- 服务可以通知(调用)其他服务(例如,为集合添加处理程序)。
- 支持工厂定义,其中通过调用其他服务的某个方法来创建服务。
- 定义可以相互扩展以避免重复常见的公式(
extends
选项)。 - 允许可选参数。
- 从 YML 文件中读取定义。
- 支持私有、只读和别名服务以及单例服务(默认)和非单例服务(选项)。
- 与PHP 容器互操作性项目兼容。
- 可以将容器状态缓存起来,以防止每次请求都重建它。
TL;DR 文档
通过 Composer 获取
$ composer require splot/di dev-master
在 PHP 中使用
$container = new \Splot\DependencyInjection\Container();
// register services in PHP
$container->setParameter('debug', true);
$container->register('logger', '\Psr\Log\NullLogger');
$container->register('my_service', array(
'class' => 'MyClass',
'arguments' => array('MyService', '1.0', '%debug%'),
'call' => array(
array('setLogger', array('@logger'))
)
));
// load parameters and service definitions from a YML file
$container->loadFromFile(__DIR__ .'/services.yml');
// retrieve a service
$myService = $container->get('my_service');
// retrieve a parameter
$debug = $container->getParameter('debug');
您可以在 coverall 测试文件中查看选项的完整列表和大量示例:tests/fixtures/coverall.yml。
安装
您可以使用Composer安装 Splot DI。
$ composer require splot/di dev-master
开始使用它只需实例化
$container = new \Splot\DependencyInjection\Container();
使用容器
注册服务
$container->register('my_service_name', 'MyClass')
- 如果您的类没有其他依赖项,这是最简单的方法$container->register('my_service_name', array('class' => 'MyClass', ...))
- 如果您想添加一些选项(如下文所述),这是更高级的方法
从 YML 加载
您还可以从 YML 文件中加载服务定义和参数。强烈建议这样做,因为它更干净,也更易于阅读。
$container->loadFromFile(__DIR__ .'/services.yml');
您可以在以下位置查看示例 YML 文件:tests/fixtures/coverall.yml。
加载的 YML 文件必须分为两个部分:parameters
和services
(您可以省略任何一个),例如
parameters:
my_parameter: true
services:
my_service:
class: MyApp\MyService
检索服务
要从容器中检索服务,只需调用
$myService = $container->get('my_service_name');
这将实例化服务,并解决和注入任何依赖项。
使用参数
您可以使用容器参数用于参数化常见参数或甚至类名或其他选项。
要设置参数,请调用
$container->setParameter('debug', true);
要获取参数,请调用
$debug = $container->getParameter('debug');
参数也可以相互链接
$container->setParameter('dev_mode', true);
$container->setParameter('debug', '%dev_mode%');
它们也可以作为字符串连接
$container->setParameter('version', 1);
$container->setParameter('app_name', 'MyApp ver. %version%');
在引用参数时,应在参数名称周围使用%
符号 - 前后各一个(您也可以通过使用两个%%
来转义%%
符号)。
引用其他服务和参数
您可以将其他服务和参数作为依赖项注入到服务中。
在引用服务时,无论是在 arguments
选项还是 call
选项中,您需要在名称前添加一个 @
符号,例如 @my_service
。
在引用参数时,无论是在 arguments
选项还是 call
选项,或者任何其他选项中,您都需要用 %
符号包围它 - 一个在前面,一个在后面,例如 %my_parameter.name%
。
选项
在注册服务时,以下选项在 PHP 和 YML 文件中都是可用的。
当注册已经存在的对象作为服务或使用闭包作为服务配方时,其中一些选项已经过时。
所有示例都使用 YML 表示法,但可以一一对应地转换为 PHP 选项数组。
class
服务的类名。应该是一个完全命名空间化的类,例如。
my_service:
class: MyLib\MyService
如果之前已经定义,也可以是一个参数
my_service:
class: %my_service.class%
这是唯一必需的选项,除了抽象服务或工厂服务。
arguments
服务构造函数参数列表。可以引用其他服务(使用 @
表示法)或参数(使用 %
表示法)
my_service:
class: %my_service.class%
arguments:
- MyServiceTitle
- @logger
- %debug%
当引用其他服务时,它们可以是可选的 - 如果您在引用末尾添加 ?
符号,那么如果未找到此类服务,则将注入 null
my_service:
class: %my_service.class%
arguments:
- @logger?
在上面的示例中,如果 logger
服务不存在,则将 null
放在其位置。
call
应调用服务上方法列表,在服务实例化后立即调用。每个调用条目都是一个数组,其中索引 0 是方法名,索引 1 是方法参数的数组。
您也可以在此处引用参数或其他服务作为方法参数,例如。
my_service:
class: %my_service.class%
call:
- [setName, ["My Service Title"]]
- [setVersion, ["1.0", "stable"]]
- [setLogger, "@logger"]
- [setDebug, "%debug%"]
如果方法只提供了一个参数,则索引 1 或调用数组可以是该参数,而不是数组(如上面示例中的最后两行)。
factory_service, factory_method, factory_arguments
如果您的服务需要是工厂的产物,则可以指定调用哪个服务上的哪个方法以及使用什么参数来获取该服务。
my_factory:
class: MyApp\MyServicesFactory
my_service:
factory_service: my_factory
factory_method: provideService
factory_arguments:
- @logger?
- %debug%
当获取 my_service
时,将调用 my_factory
服务的 ::provideService()
方法,并使用指定的参数,该方法调用的结果将作为服务返回。它也将作为服务实例缓存,除非将 singleton
选项设置为 false
,在这种情况下,每次对 my_service
的请求都将导致上述方法调用。
factory
factory_service
、factory_method
和 factory_arguments
选项的简写选项。
一个数组,其中索引 0 = factory_service
,1 = factory_method
和 2 = factory_arguments
(可选)。
my_service:
factory: ['@my_factory', 'provideService', [@logger?, %debug%]]
notify
Splot DI 的独特功能是服务能够通知其他服务。这是 Symfony DI 组件中标签和编写编译器传递的替代方案。
notify
实质上是另一个服务的调用,该服务可能存在或不存在。
my_services_collection:
class MyApp\MyServicesCollection
my_service.one:
class: MyApp\MyService
arguments: ['one']
notify:
- [my_services_collection, addService, ["service_one", "@"]]
my_services.two:
class: MyApp\MyService
arguments: ['two']
notify:
- [my_services_collection, addService, ["service_two", "@"]]
根据上述定义,检索服务 my_services_collection
也将导致服务 my_service.one
和 my_service.two
被实例化,并通过在 my_services_collection
上调用 ::addService()
方法并将为每个注入的服务指定的参数注入到 my_services_collection
中来实现。在这种情况下,特殊字符 @
指的是服务本身,是 @my_service.one
或 @my_service.two
的简写。
您可以使用 @
符号在通知参数中引用服务本身,或使用 @=
来引用服务名称。
然而,检索服务 my_service.one
本身不会导致 my_services_collection
被实例化。但稍后,如果 my_services_collection
被实例化,则 my_service.one
将适当地注入到其中。
发送者服务(例如,上面的示例中的 my_services.two
)只有在您通过 @
在通知参数中引用它时才会被实例化。否则,它将遵循正常的实例化策略。
一个服务可以通知无限数量的其他服务。它还可以尝试通知不存在的服务 - 在这种情况下,不会抛出异常也不会引发任何错误。
请参阅下方的“常见用法”部分以了解此功能的用例。
抽象
如果要将此服务标记为不可实例化,请将其设置为true
。当您想创建一个将被其他服务重用的通用服务配方时,这很有用。
抽象服务不需要指定一个类。
扩展
指定服务的“父”定义。此服务将从它扩展的服务继承几乎所有选项(那些有意义的选项,例如,abstract
选项不继承)并也可以覆盖它们中的任何一个。
my_service:
class: MyApp\MyService
arguments: ["my_app", "1.0", %debug%, @logger?]
call:
- [setSomething, true]
- [incrementCounter]
your_service:
class: YourApp\YourService
extends: my_service
your_service
服务将被实例化为YourApp\YourService
类的对象,但与my_service
具有相同的构造函数参数和方法调用。
单例
在Splot DI中,所有服务默认为单例。如果希望容器始终返回一个新实例,请将singleton
选项设置为false
。
my_service:
class: %my_service.class%
singleton: false
别名
在注册服务时,您还可以注册一些别名。当检索服务时,您将能够使用它们。
my_service:
class: %my_service.class%
aliases:
- my_service.alias
- my_fake_service
- your_service
别名
您可以将服务注册为另一个服务的别名。它将简单地使容器也可以通过其新名称引用该服务。
my_service:
class: %my_service.class%
my_service.alias:
alias: my_service
私有
如果您不想直接从容器中检索服务,请将其标记为private
。您将无法使用$container->get()
方法获取它,但您可以将其用作其他服务的依赖项。
my_service:
class: %my_service.class%
private: true
只读
如果您想确保您的服务不会被另一个服务覆盖,请将其标记为只读。
my_service:
class: %my_service.class%
read_only: true
技巧
紧凑定义
如果您的服务仅是未获取任何构造函数参数的类的实例,并且您不需要设置任何其他选项,那么这
my_service:
class: MyApp\MyService
可以简化为这样
my_service: MyApp\MyService
紧凑工厂定义
如果您的服务是工厂的产物,并且您不需要设置任何其他选项,那么这
my_service:
factory: ['@my_factory', 'provideService', [@logger?, %debug%]]
可以简化为这样
my_service: ['@my_factory', 'provideService', [@logger?, %debug%]]
常见配方
以下是一些常见的Splot DI重用或理解配方。
使用“通知”选项添加日志处理器
使用notify
选项可以提供很多功能和灵活性。
它的最佳用例是您有一个具有简单API的伪装服务,该API接受动态数量的其他类来以不同的方式处理输入。例如,一个具有许多日志处理器的简单日志记录器(如Monolog)
logger:
class: Monolog\Logger
logger.handler.stream:
class: Monolog\Handler\StreamHandler
arguments:
- "path/to/your.log"
- 300
notify:
- ["logger", "pushHandler", ["@"]]
logger.handler.slack:
class: Monolog\Handler\SlackHandler
arguments: ["%slack.api.token%", "%slack.log_channel%"]
notify:
- ["logger", "pushHandler", ["@"]]
现在,当您检索logger
服务时,它将自动具有StreamHandler
和SlackHandler
注入。而且,您的应用程序的其他部分(例如其他模块或“包”)添加自己的处理器也没有什么限制。
贡献
问题和拉取请求非常受欢迎!在创建拉取请求时,请包括您更改的全量测试覆盖率。