​​​​ PHP依赖注入 | 苏生不惑的博客

PHP依赖注入

本文转自laravel-china

依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
class Car
{
//汽车可以跑,定义一个方法 run()
public function run(){
return '滴滴滴,车开了';
}
}

class Person
{
private $car = null;//保存某人的车,暂时还没车
public function drive(){
$this->car = new Car();//要开车先造车,造了一辆车保存在某人的 $car 里
return $this->car->run();//调用车的 run() 方法
}
}

$xiaoming = new Person();
echo $xiaoming->drive();//输出 滴滴滴,车开了
class Car
{
//汽车可以跑,定义一个方法 run()
public function run()
{
return '滴滴滴,车开了';
}
}

class Person
{
private $car = null;//保存某人的车,暂时还没车

//new 某人的时候,就给他注入一辆车,通过 $a 传入构造方法,并保存在 $car 里
public function __construct($a)
{
$this->car = $a;
}

public function drive()
{
return $this->car->run();//调用车的 run() 方法
}
}
$car = new Car();//买一辆车
$xiaoming = new Person($car);//new 小明的时候,把刚才买的车注入
echo $xiaoming->drive();//输出 滴滴滴,车开了
class Car
{
//汽车可以跑,定义一个方法 run()
public function run()
{
return '滴滴滴,车开了';
}
}

class Person
{
private $car = null;//保存某人的车,暂时还没车

//new 某人的时候,就给他注入一辆车,通过 $a 传入构造方法
public function __construct(Car $a)// <----这里做了类型限定
{
$this->car = $a;
}

public function drive()
{
return $this->car->run();//调用车的 run() 方法
}
}

IoC 容器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//写一个简单的 IoC 容器类
class Container{
private static $objArr = [];//定义一个静态的空数组

public static function set($flag, Callable $func){
self::$objArr[$flag] = $func;//存入键值对,键是一个字符串,作为标识符,值是一个匿名函数
}

public static function get($flag){
$tmp = self::$objArr[$flag];//取出标识符对应的匿名函数,用$tmp临时保存一下
return $tmp();//在$tmp后名加上括号,表示执行这个函数,并返回
}
}

//下面这条语句执行完毕后,会在 $objArr 里存入一个键值对,键是 car ,值是这个匿名函数,该匿名函数返回的是创建 Car 对象的语句
Container::set('Car', function(){
return new Car();
});

//下面这条语句执行完毕后,会在 $objArr 里存入一个键值对,键是 person ,值是这个匿名函数,该匿名函数返回的是创建 Person 对象的语句
Container::set('Person', function(){
return new Person(Container::get('Car'));//直接去容器中取一辆车出来,并作为参数传给 Person 类的构造函数
});

$xiaomin = Container::get('Person');//直接去容器中取一个人出来,取名叫小明
echo $xiaomin->drive();//输出 滴滴滴,车开了

class Container
{
protected $binds;

protected $instances;

public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}

public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}

array_unshift($parameters, $this);

return call_user_func_array($this->binds[$abstract], $parameters);
}
}
//在线执行laravel https://implode.io/4mT8O4
$container = new Container;

$container->bind('Board', function($container){
return new CommonBoard;
});

$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});

$computer = $container->make('Computer',['Board']);

魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//https://laravel-china.org/articles/20625
class Model
{
protected function increment($column, $amount = 1, array $extra = [])
{
return $this->incrementOrDecrement($column, $amount, $extra, 'increment');
}

public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}

return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}

public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
}
(new User)->increment('age');
User::increment('age');
第一种,因为我们User继承了Model类,但是increment方法前有个protected导致我们无法从外部访问这个方法。但是不用慌张,这个时候__call魔术方法就起到了效果,他会帮我们去访问increment方法。

第二种,我们用静态调用increment方法,运行的时候,程序就去找有没有定义的静态increment方法,找了一圈没有找到,怎么办?这个时候__callStatic开始发挥作用。我们用的是User类,因为延迟静态绑定的缘故,可以看成:

return (new User)->increment('age');

控制反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
class Boos{

//领导依赖员工
private $staff;

//现在老板只需要接受 hr 招聘就好,将控制权交给 hr
//以设置方法来实现依赖注入
public function setStaff(Standard $staff){
$this->staff = $staff;
}

public function task(){
$this->staff->work();
}
}

//招聘所设定的标准
interface Standard{
public function work();
}

//员工需要依赖的标准
class StaffA implements Standard{
public function work(){
echo '雇员A有能力能够完成老板指定的工作';
}
}

class StaffB implements Standard{
public function work(){
echo '雇员B有能力能够完成老板指定的工作';
}
}

//ioc容器
class Hr{
public function getStagff(){
return new StaffB();
}
}

//公司老板
$boos = new Boos();
//老板招的hr
$hr = new Hr();
$staff = $hr->getStagff();
//hr把招到的人给老板(控制反转和依赖注入)
$boos->setStaff($staff);
//老板让他工作了
$boos->task();

class Boos{

//领导依赖员工
private $staff;

//老板只需要告诉外部我需要什么样的人就好了,其它什么都不管,具体什么样的人交给外部处理。
//用构造方法方式实现依赖注入
public function __construct(Standard $staff){
$this->staff = $staff;
}

public function task(){
$this->staff->work();
}
}

//招聘所设定的标准
interface Standard{
public function work();
}

//员工需要依赖的标准
class StaffA implements Standard{
public function work(){
echo '雇员A有能力能够完成老板指定的工作';
}
}

class StaffB implements Standard{
public function work(){
echo '雇员B有能力能够完成老板指定的工作';
}
}

class Hr{

private $binds = [];

//接受不同员工的简历,并存起来
public function bind($contract,$concrete){
$this->binds[$contract] = $concrete;
}

//询问老板选人的标准由哪些,并且从满足的简历中筛选人
private function methodBindParams($className){
$reflect = new reflect($className,'__construct');
return $reflect->bindParamsToMethod();
}

//将选好的工作人员交给老板
public function make($className){
$methodBindParams = $this->methodBindParams($className);
$reflect = new reflect($className,'__construct');
return $reflect->make($this->binds,$methodBindParams);
}
}

class reflect{
private $className;

private $methodName;

public function __construct($className,$methodName){
$this->className = $className;
$this->methodName = $methodName;
}

//绑定参数到方法
public function bindParamsToMethod(){

$params = [];

$method = new ReflectionMethod($this->className,$this->methodName);

foreach ($method->getParameters() as $param) {
$params[] = [$param->name,$param->getClass()->name];
}

return [$this->className=> $params];
}

public function make($bind,$methodBindParams){
$args = [];
foreach ($methodBindParams as $className => $params) {
foreach ($params as $param) {
list($paramName,$paramType) = $param;

$paramName = new $bind[$paramType]();

array_push($args, $paramName);
}
}
$reflectionClass = new ReflectionClass($this->className);
return $reflectionClass->newInstanceArgs($args);
}

}
$hr = new Hr();

//老板如果需要换工作人员,只需要绑定其它的工作人员即可。
$staff = $hr->bind('Standard','StaffA');

$boos = $hr->make('Boos');

$boos->task();

依赖注入原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

namespace Database;
use ReflectionMethod;

class Database
{

protected $adapter;

public function __construct ()
{}

public function test (MysqlAdapter $adapter)
{
$adapter->test();
}
}

class MysqlAdapter
{

public function test ()
{
echo "i am MysqlAdapter test";
}
}

class app
{

public static function run ($instance, $method)
{
if (! method_exists($instance, $method))

return null;

$reflector = new ReflectionMethod($instance, $method);

$parameters = [
1
];
foreach ($reflector->getParameters() as $key => $parameter)
{

$class = $parameter->getClass();

if ($class)
{
array_splice($parameters, $key, 0, [
new $class->name()
]);
}
}
call_user_func_array([
$instance,
$method
], $parameters);
}
}

app::run(new Database(), 'test');

反射机制实现依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
namespace Models;

class Car
{
}

namespace Framework;

class App
{
public function getInstance($className)
{
//实例化 ReflectionClass 对象
$reflector = new \ReflectionClass($className);

if (!$reflector->isInstantiable()) {
//不能被实例化的逻辑
return false;
}

//获取构造器
$constructor = $reflector->getConstructor();

//如果没有构造器,直接实例化
if (!$constructor) {
//这里用了变量来动态的实例化类
return new $className;
}
}
}

$app = new App();
$car = $app->getInstance('Models\Car');
var_dump($car); //输出 object(Models\Car)#4 (0) { }
<?php
namespace Framework;

//定义一个类,用于实现依赖注入
class App
{
public function getInstance($className)
{
//实例化 ReflectionClass 对象
$reflector = new \ReflectionClass($className);

if (!$reflector->isInstantiable()) {
//不能被实例化的逻辑,抽象类和接口不能被实例化
return false;
}

//获取构造器
$constructor = $reflector->getConstructor();

//如果没有构造器,也就是没有依赖,直接实例化
if (!$constructor) {
return new $className;
}

//如果有构造器,先把构造器中的参数获取出来
$parameters = $constructor->getParameters();

//再遍历 parameters ,找出每一个类的依赖,存到 dependencies 数组中
$dependencies = array_map(function ($parameter) {
/**
* 这里是递归的去寻找每一个类的依赖,例如第一次执行的时候,程序发现汽车 Car 类依赖底盘 Chassis
* 类,此时 $parameter 是一个ReflectionParameter 的实例,接着调用 ReflectionParameter
* 的 getClass() 方法,获得一个 ReflectionClass 的实例,再接着调用 ReflectionClass
* 的 getName() 方法,取得类名,也就是 Models\Chassis ,但此时此刻还不能直接去 new
* Models\Chassis ,因为 Models\Chassis 也有依赖,故要递归的去调用 getInstance
* 进一步去寻找该类的依赖,周而复始,直到触发上面的 if(!$constructor) ,停止递归。
*/
return $this->getInstance($parameter->getClass()->getName());
}, $parameters);

//最后,使用 ReflectionClass 类提供的 newInstanceArgs ,方法去实例化类,参数将会传入构造器中
return $reflector->newInstanceArgs($dependencies);
}
}

namespace Models;

class Car
{
protected $chassis;

//汽车依赖底盘
public function __construct(Chassis $chassis)
{
$this->chassis = $chassis;
}
}

class Chassis
{
protected $tyre;
protected $axle;

//底盘依赖轮胎和轴承
public function __construct(Tyre $tyre, Axle $axle)
{
$this->tyre = $tyre;
$this->axle = $axle;
}
}

class Tyre
{
protected $axle;

//轮胎也依赖轴承
public function __construct(Axle $axle)
{
$this->axle = $axle;

}
}

class Axle
{
//轴承无依赖
}

$app = new \Framework\App();
$car = $app->getInstance('Models\Car');//不用$car = new Car(new Chassis(new Tyre(new Axle), new Axle()))
var_dump($car);

IOC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
/**
* 没有IoC/DI的时候,常规的A类使用C类的示例
*/

/**
* Class c
*/
class c
{
public function say()
{
echo 'hello';
}
}

/**
* Class a
*/
class a
{
private $c;
public function __construct()
{
$this->c = new C(); // 实例化创建C类
}

public function sayC()
{
echo $this->c->say(); // 调用C类中的方法
}
}

$a = new a();
$a->sayC();
<?php
/**
* 当有了IoC/DI的容器后,a类依赖c实例注入的示例https://segmentfault.com/a/1190000007536704
*/

/**
* Class c
*/
class c
{
public function say()
{
echo 'hello';
}
}

/**
* Class a
*/
class a
{
private $c;
public function setC(C $c)
{
$this->c = $c; // 实例化创建C类
}

public function sayC()
{
echo $this->c->say(); // 调用C类中的方法
}
}

$c = new C();
$a = new a();
$a->setC($c);
$a->sayC();

依赖注入 控制反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
interface DbConnect {

public function connect();
}

class DbA implements DbConnect {

public function connect()
{
// TODO: Implement test_echo() method.
echo 'A';
}
}
class DbB implements DbConnect {

public function connect()
{
// TODO: Implement test_echo() method.
echo "B";
}
}

class ConnectDb {

protected $connects;

public function __construct()
{
$this->connect = new DbA();
}

public function connect()
{
echo $this->connect->connect();
}
}
上面写法可以实现数据库连接功能,但是要切换到 B 数据库就要修改 ConnectDb 类,这样代码没法达到低耦合,也不符合编程开放封闭原则。我们可以把连接的类用函数传参的方式传进 ConnectDb 构造函数里.

class ConnectDb {

protected $connects;

public function __construct(DbConnect $dbConnect)
{
$this->connect = $dbConnect;
}

public function connect()
{
echo $this->connect->connect();
}
}

$ConnectDb = new ConnectDb(new DbA());
$ConnectDb->connect();
这样我们就可以不修改 ConnectDb 而是用构造函数参数传递的方式切换数据库,这就是控制反转(感觉好像工厂模式。。。),不需要自己内容修改,通过外部传递参数,这种由外部负责其依赖需求的行为,称控制反转(IoC)

不通过自己内部 new 对象或者实例,而是通过函数或者构造函数传递进来。称为依赖注入(DI)

反射

刚毕业那会面试一家公司面试就问我了不了解反射,当时给我问的一懵。工作这么长时间了其实还是不了解。。。。

反射其实就是根据类名返回这个类的任何信息,比如该类的方法,参数,属性等等

ReflectionClass 类报告了一个类的有关信息。
// — 初始化 ReflectionClass 类
$reflectionClass = new ReflectionClass('ConnectDb');

// — 获取类的构造函数 一个 ReflectionMethod 对象,反射了类的构造函数,或者当类不存在构造函数时返回 NULL。
$constructor = $reflectionClass->getConstructor();

// - 获取构造函数所有依赖参数
$dependencies = $constructor->getParameters();

// - 创建类的新的实例。给出的参数将会传递到类的构造函数。
$ConnectDb = $reflectionClass->newInstance();

// - 创建一个类的新实例,给出的参数将传递到类的构造函数。这个参数以 array 形式传递到类的构造函数 返回值 返回类的实例
$ConnectDb = $reflectionClass->newInstanceArgs($dependencies);
我们可以创建一个方法,利用反射的机制拿到 ConnectDb 类的构造函数,然后拿到构造函数的参数对象,用递归的方法创建参数依赖,最后调用 newInstanceArgs 方法生成 ConnectDb 实例

interface DbConnect {

public function connect();
}

class DbA implements DbConnect {

public function connect()
{
// TODO: Implement test_echo() method.
echo 'A';
}
}
class DbB implements DbConnect {

public function connect()
{
// TODO: Implement test_echo() method.
echo "B";
}
}

class ConnectDb {

protected $connects;
//反射是不能动态创建接口的
public function __construct(DbA $dbConnect)
{
$this->connect = $dbConnect;
}

public function connect()
{
echo $this->connect->connect();
}
}

function make ($concrete) {
// — 初始化 ReflectionClass 类
$reflectionClass = new ReflectionClass($concrete);
// — 获取类的构造函数 一个 ReflectionMethod 对象,反射了类的构造函数,或者当类不存在构造函数时返回 NULL。
$constructor = $reflectionClass->getConstructor();

//如果不需要传递参数直接返回实例
if (is_null($constructor)) {
return $reflectionClass->newInstance();
} else {
// - 获取构造函数所有依赖参数
$dependencies = $constructor->getParameters();
$instance = getdependencies($dependencies);
return $reflectionClass->newInstanceArgs($instance);
}

}

function getdependencies($parameter)
{
$dependencies = [];
foreach ($parameter as $paramete) {
//返回对象实例 object 所属类的名字。返回对象实例 object 所属类的名字。 如果 object 不是一个对象则返回 FALSE。
$dependencies[] = make($paramete->getClass()->name);
}
return $dependencies;
}

$ConnectDb = make('ConnectDb');
$ConnectDb->connect();
总结:

控制反转(IoC):不修改类内部自身,通过外部参数传递的方式,由外部负责其依赖需求的行为。
依赖注入(DI) :不通过自己 new 或实例实例化,通过函数或者构造函数传递进来。
反射:根据类名获取类的信息,方法、属性、参数等等https://learnku.com/articles/32310

IOC demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

interface Board {
public function type();
}

class CommonBoard implements Board {
public function type(){
echo '普通键盘';
}
}

class MechanicalKeyboard implements Board {
public function type(){
echo '机械键盘';
}
}

class Computer {
protected $keyboard;

public function __construct (Board $keyboard) {
$this->keyboard = $keyboard;
}
public function type(){
$this->keyboard->type();
}
}

$computer = new Computer(new MechanicalKeyboard());
class Container
{
protected $binds;

protected $instances;

public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}

public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}

array_unshift($parameters, $this);//闭包的时候第一个参数为$this

return call_user_func_array($this->binds[$abstract], $parameters);
}
}
$container = new Container;

$container->bind('Board', function($container){
return new CommonBoard;
});
//当我需要Computer类的时候你就给我实例化Computer类
$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});

$computer = $container->make('Computer',['Board']);//对Computer进行生产返回一个实例。
echo $computer->type();

//更换键盘怎么办呢?

$container->bind('Board', function($container){
return new MechanicalKeyboard;
});

$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});

$computer = $container->make('Computer',['Board']);
//https://3v4l.org/3krab

反射机制实现依赖注入

PHP中的依赖注入(DI)容器

Laravel 服务容器,IoC,DI

Laravel 依赖注入原理

浅析依赖倒转、控制反转、IoC 容器、依赖注入