grithin/ioc-di

带有服务容器的IoC DI

0.10 2021-07-27 12:42 UTC

This package is auto-updated.

Last update: 2024-09-16 19:43:58 UTC


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_withcall(..., ['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]);