PHP 依赖注入(DI)和控制反转(IoC)
说明以及优点
- 用来减少程序间耦合的一种设计模式
- 依赖注入可以有效分离对象和所需资源,是整个体系变的灵活
概念
依赖注入
和控制反转
对同一件事情的不同描述(描述的角度不同)依赖注入:
应用程序依赖容器创建并注入它所需要的外部资源控制反转:
容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源
以代码为例子,来深刻理解一下,这个例子为正常User类调用FileLog类时的写法
<?php
//文件记录日志
class FileLog{
public function write(){
echo 'file log write...';
}
}
//登录成功,记录登录日志
class User
{
protected $log;
public function __construct()
{
$this->log = new FileLog();
}
public function login()
{
echo 'login success...';
$this->log->write();
}
}
$user = new User();
$user->login();
// 当User类需要FileLog类时,FileLog类就相当于User类的外部资源,主动实例化FileLog类
// 弊端:当我们不想用文件存储日志时,我们需要改动User类中构造方法
?>
这个例子为使用了IoC/DI容器后例子
<?php
//文件记录日志
class FileLog{
public function write(){
echo 'file log write...';
}
}
//登录成功,记录登录日志
class User
{
private $log;
public function setLog(FileLog $log){
$this->log = $log;
}
public function write(){
$this->log->write();
}
}
$fileLog = new FileLog();
$user = new User();
//当User类需要FileLog类的时候,采用注入的方式
$user->steLog($fileLog);
$user->write();
?>
在了解下面代码之前,我们需要简单的回顾一下匿名函数以及它使用外部参数和如何调用,同样,我们以一段代码为例子来简单说明一下
<?php
//定义一个简单的匿名函数
//匿名函数外部参数需要use($param)才能使用
$p1 = 'p1';
$test = function ($p0)use ($p1){
echo $p0,'------',$p1;
};
//调用匿名函数
$test('p0');
//输出:p0------p1
?>
上面简单的通过代码了解了上面代码,下面我们把上面代码完善优化一下
<?php
interface Log
{
public function write();
}
// 文件记录日志
class FileLog implements Log
{
public function write(){
echo 'file log write...';
}
}
// 数据库记录日志
class DatabaseLog implements Log
{
public function write(){
echo 'database log write...';
}
}
class User
{
protected $log;
public function __construct(Log $log)
{
$this->log = $log;
}
public function login()
{
// 登录成功,记录登录日志
echo 'login success...';
$this->log->write();
}
}
class Ioc
{
public $binding = [];
public function bind($abstract, $concrete)
{
//这里为什么要返回一个closure呢?因为bind的时候还不需要创建User对象,所以采用closure等make的时候再创建FileLog;
$this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
echo 3,"-----";
return $ioc->build($concrete);
};
}
public function make($abstract)
{
//根据key获取binding的值
echo 1,"-----";
$concrete = $this->binding[$abstract]['concrete'];
echo 2,"-----";
return $concrete($this);
}
// 创建对象
public function build($concrete) {
echo $concrete,"----";
$reflector = new ReflectionClass($concrete);
echo 4,"-----";
$constructor = $reflector->getConstructor(); //获取反射类的构造方法
var_dump(is_null($constructor));
if(is_null($constructor)) {
echo "--------",5,"-------";
return $reflector->newInstance(); // 从指定的参数创建一个新的类实例
}else {
echo "--------",6,"-------";
$dependencies = $constructor->getParameters(); //获取参数
$instances = $this->getDependencies($dependencies); // 获取依赖
return $reflector->newInstanceArgs($instances); //从给出的参数创建一个新的类实例
}
}
// 获取参数的依赖
protected function getDependencies($paramters) {
$dependencies = [];
echo 7,"-------";
foreach ($paramters as $paramter) {
$dependencies[] = $this->make($paramter->getClass()->name); //getClass 返回对象实例 obj 所属类的名字。如果 obj 不是一个对象则返回 FALSE。
}
return $dependencies;
}
}
//实例化IoC容器
$ioc = new Ioc();
$ioc->bind('Log','FileLog');
$ioc->bind('user','User');
$user = $ioc->make('user');
$user->login();
运行
$ php Test.php
1-----2-----3-----User----4-----bool(false)------6-------7-------
1-----2-----3-----FileLog----4-----bool(true)-------5-------
login success...file log write...
由上面可以看出来
//实例化Ioc容器
$ioc = new Ioc();
// 匿名函数外部参数需要use($param)才能使用
//把参数为外部参数为FileLog的匿名函数赋值给以$this->binding['Log']['concrete']
$ioc->bind('Log','FileLog');
//把参数为外部参数为User的匿名函数赋值给以$this->binding['user']['concrete']
$ioc->bind('user','User');
//获取 $this->binding['user']['concrete'] 的匿名函数,然后调用匿名函数 $concrete($this)
$user = $ioc->make('user');
//走3,调用build($concrete)方法,利用反射类获取到原始类的属性和方法。判断 is_null($constructor) 为false(因为User类有构造方法
//走6,获取构造方法需要传入的参数,走7,走1, 获取依赖类 $this->binding['Log']['concrete'] ,往次反复,加载到所需要的依赖类。最后执行
$user->login();
//login success...file log write...
Ioc 容器维护 binding 数组记录 bind 方法传入的键值对如:log=>FileLog, user=>User
拓展(ReflectionClass 类报告了一个类的有关信息。)
<?php
class X {
}
class_alias('X','Y');
class_alias('Y','Z');
$z = new ReflectionClass('Z');
echo $z->getName(); // X
?>
<?php
class a{}
$ref = new ReflectionClass('a');
$ref = unserialize(serialize($ref));
var_dump($ref);
var_dump($ref->getDocComment());
// object(ReflectionClass)#2 (1) {
// ["name"]=>
// string(1) "a"
// }
// PHP Fatal error: ReflectionClass::getDocComment(): Internal error: Failed to retrieve the reflection object
?>
了解更多ReflectionClass的更多用法请移步到PHP官网
2 comments
写的好呀,兄die
加油