grithin / ioc-di
带有服务容器的IoC DI
Requires
- php: >=5.3.0
- grithin/phpbase: ^5.0
- psr/container: ^2.0
Requires (Dev)
- grithin/phpunit: ^0.1.0
README
A small IoC Dependency Injector and Service Locator with experimental features, including a Data Locator.
composer require grithin/ioc-di
使用
头部
对于代码示例,使用此头部运行
# HEADER # use \Grithin\{ServiceLocator, DependencyInjector}; use \Grithin\IoC\{NotFound, ContainerException, MissingParam, Datum, Service, Call, Factory}; $sl = new ServiceLocator; $di = $sl->injector();
基本
将"NameInterface"类型参数注入新构造的"Creature"中的服务定位器注入
## INSERT HEADER ## class Creature{ public $name; public function __construct(NameInterface $name){ $this->name = $name; } } interface NameInterface{ } class Name implements NameInterface{ public $text; public function __construct($text='bob'){ $this->text = $text; } } $sl = new ServiceLocator; $di = $sl->injector(); # add "Name" into services $sl->bind('Name'); # construct "Creature" with injected Name instance $bob = $di->call('Creature'); echo $bob->name->text; #> 'bob'
混合服务定位器和提供的参数
有时允许ServiceLocator找到一个参数,但提供另一个参数而不是使用ServiceLocator很有用。
## INSERT HEADER ## class Creature{ public $name; public $age; public function __construct(Name $name, Age $age){ $this->name = $name; $this->age = $age; } } class Name{ public $text; public function __construct($text='bob'){ $this->text = $text; } } class Age{ public $text; public function __construct($text='20'){ $this->text = $text; } } # add "Age" into services $sl->bind('Age'); # pass in the $age parameter by matching the parameter name $bob = $di->call_with('Creature', ['age'=> new Age('30')]); echo $bob->age->text; #> 30 # pass in the $age parameter by matching the position $bob = $di->call_with('Creature', [1=> new Age('30')]); echo $bob->age->text; #> 30
默认值
如果依赖注入器无法解析依赖项,您可以为此参数提供一个回退默认值。
## INSERT HEADER ## class Creature{ public $name; public function __construct(NameInterface $name){ $this->name = $name; } } interface NameInterface{ } class Name implements NameInterface{ public $text; public function __construct($text='bob'){ $this->text = $text; } } # use a default for the "name" parameter $bob = $di->call('Creature', ['defaults'=>['name'=> new Name('Sue')]]); # since "Name" has not been registered as a service, ServiceLocator will fail to find it, and use the default provided echo $bob->name->text; #> sue
由于在它被使用之前首先实例化默认值可能是不理想的,因此有特殊的类可以防止预先实例化。
这些特殊类包括
- \Grithin\IoC\Service
- \Grithin\IoC\Datum
- \Grithin\IoC\Call
- \Grithin\IoC\Factory
参见特殊类型
特殊类型
使用Grithin\IoC\SpecialTypeInterface
服务对象
由:SL, DI使用
- 如果您想指向一个具有选项的服务。
- 如果您想提供参数默认值作为对象,但不想在没有默认值时实例化该对象。
使用Service
作为默认值的示例
## INSERT HEADER ## interface NameInterface{} class Name implements NameInterface { public $text; public function __construct($text = 'bob') { $this->text = $text; } } function sayName(NameInterface $name){ echo $name->text; } # create a service with pre-set construction options $name_service = new Service(Name::class, ['with'=>['text'=>'sue']]); # injector will fall back on the default, $name_service, since no NameInterface is registered $di->call('sayName', ['defaults'=> ['name'=>$name_service]]); #> sue
Datum
由:SL, DI使用
- 如果您想配置一个服务,使其使用可能稍后设置或更改的数据进行实例化。
## INSERT HEADER ## class Person{ public $name; function __construct($name){ $this->name = $name; } function sayName(){ echo $this->name; } } # Bind service id Person to the Person class, with constructor parameter using Datum "name" $sl->bind('Person', Person::class, ['with'=>['name'=>new Datum('name')]]); # define the Datum for the DataLocator # this is one benefit to using Datum - it can be defined after a service is bound to use it $sl->data_locator->set('name', 'sue'); function sayName(Person $person){ return $person->sayName(); } # getName will cause ServiceLocator to create Person instance, which causes DataLocator to pull "name" $di->call('sayName'); #> sue # change Datum resolution $sl->data_locator->set('name', 'bob'); # a new Person instance will be made, and Datum "name" will now resolve to "bob" $di->call('sayName'); #> bob
Call
由:SL, DI, DL使用
- 如果您想指向某个可调用的结果。
## INSERT HEADER ## class Person{ public $name; function __construct($name){ $this->name = $name; } function sayName(){ echo $this->name; } } function getName() { return 'bob'; } # Invoke getName when instantiating "Person" for parameter $name $sl->bind('Person', Person::class, ['with'=>['name'=>new Call('getName')]]); function sayName(Person $person){ return $person->sayName(); } $di->call('sayName'); #> bob
Factory
由:SL, DI, DL使用
- 用于指示DataLocator应使用东西作为工厂。
与Call
相比,使用Factory
时,每次从DataLocator访问数据时都会调用以解析数据。
## INSERT HEADER ## $random = function(){ return rand(0,20000); }; $sl->data_locator->set('rand', new Call($random)); echo $sl->data_locator->get('rand'); #> 1231 # the DataLocator only runs the callable once, so it will return the same value here echo $sl->data_locator->get('rand'); #> 1231 # a factory can be used to get new data every time $sl->data_locator->set('rand', new Factory($random)); echo '|'; echo $sl->data_locator->get('rand'); echo '|'; #> 3421 echo $sl->data_locator->get('rand'); #> 8291
参数解析
对于某些方法定义,注入器如何解析注入值?
- 如果使用
with
变体/选项(call_with
和call(..., ['with'=>[]]))
,则使用with
数组- 按位置
- 按名称
- 按类型声明(服务定位器)
- 如果参数以大写字母开头,则尝试匹配
- 服务
- Datum
- 如果存在
defaults
选项,则使用它 - 使用方法/构造函数定义中提供的默认值
基于参数名称的解析
作为对常用预期参数的快捷方式,注入可以使用名称本身,只要该名称以大写字母开头。在这种情况下,同时检查服务和数据。
## INSERT HEADER ## $sl->data_locator->set('Server', 'localhost'); function printServer($Server){ echo $Server; } $bob = $di->call('printServer'); #> localhost
静态分析和编辑器中的自动完成已使这通常是一个不好的选择。
服务定位器
基础之上
单例
对于像数据库这样的东西,只创建一个主实例是期望的。
## INSERT HEADER ## class Database{ function __construct(){ echo 'connecting... '; # ... expensive connection stuff ... } function query(){ return 'bob'; } } $sl->singleton('Database'); function getUserName(Database $db){ echo $db->query(); } $di->call('getUserName'); #> connecting... bob $di->call('getUserName'); #> bob
您还可以向服务定位器提供现有的单例对象
## INSERT HEADER ## class Database{ public $host; function __construct($host = 'local_899') { $this->host = $host; } } function getDb(Database $db){ echo $db->host; } # Can use the `Service` class to override service config $config = ['i'=>0]; $sl->singleton('Database', new Database('remote')); $di->call('getDb'); #> remote
链接
将一个服务ID链接到另一个服务可能是有用的。
## INSERT HEADER ## interface DatabaseInterface {} class Database implements DatabaseInterface{ function query(){ return 'bob'; } } $sl->bind('DatabaseInterface', 'LinkMiddleMan'); $sl->bind('LinkMiddleMan', 'Database'); function getUserName(DatabaseInterface $db){ echo $db->query(); } # ServiceLocator will resolve DatabaseInterface to LinkMiddleMan, and then LinkMiddleMan to Database $di->call('getUserName'); #> bob
特殊类型
可能有用的是使用特殊类型解析服务。
这里,使用函数来获取服务
## INSERT HEADER ## class Database{ function query(){ return 'bob'; } } function getDatabase(){ return new Database; } function getUserName(Database $db){ echo $db->query(); } # Using the `Call` class $sl->bind('Database', new Call('getDatabase')); $di->call('getUserName'); #> bob # Using a closure $sl->bind('Database', function(){ return getDatabase(); }); $di->call('getUserName'); #> bob
使用Service
进行特殊配置
interface DatabaseInterface {} class Database implements DatabaseInterface { public $host; function __construct($host='local_899'){ $this->host = $host; } } function getDb(DatabaseInterface $db){ echo $db->host; } # Can use the `Service` class to override service config $sl->bind('Database'); $sl->bind('DatabaseInterface', new Service('Database', ['with'=>['host'=>'remote_123']])); $di->call('getDb'); #> remote_123
数据定位器
数据的服务定位器。
# basic $sl->data_locator->set('x', '123'); # lazy loaded (called once) $sl->data_locator->set('y', function(){ return '456'; }); function return_456(){ return '456' } $sl->data_locator->set('y', new Call('return_456')); # factory (called every time) $sl->data_locator->set('y', new Factory('return_456'));
备注
默认情况下,SL不会检查所有类来解析接口或抽象类,它只检查服务(按id)内部或无需进一步文件包含即可获得的类。尽管如此,您仍然可以使其检查所有内容。
$sl = new ServiceLocator(['check_all'=>true]);