php ioc容器特点,Laravel IOC容器深度剖析

1、基础知识预备

1.1 PHP 反射详解

什么是反射?直观理解就是根据到达地找到出发地和来源。比如,一个光秃秃的对象,我们可以仅仅通过这个对象就能知道它所属的类,拥有哪些方法。

PHP的反射是指在PHP运行状态过程中,扩展分析PHP程序,导出或提出关于类、方法、属性、参数等详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能成为反射API

class Person{

public $name;

public $gender;

private $age;

public function __construct($name,$gender){

$this->name = $name;

$this->gender = $gender;

}

public function __set($key, $value){

$this->$key = $value;

}

public function __get($key){

if(!isset($this->$key)){

return "$key not exists";

}

return $this->$key;

}

}

//用ReflectionClass得到A的反射类对象,通过反射类对象可以得到类的各种属性,

//包括类名空间,父类,类名等,使用newInstanceArgs可以传入构造函数的参数创建一个新的类的实例

$reflect = new ReflectionClass("Person");

//注入参数

$student = $reflect->newInstanceArgs(["xiaoming", "male"]);

echo $student->name;

echo "\n";

echo $student->gender;

echo "\n";

echo $student->age;

echo "\n";

$student->age = 20;

echo $student->age;

echo "\n";

output:

xiaoming

male

age not exists

20

1.2 依赖

程序的执行过程就是方法的调用过程,有方法调用,就必然会促使对象和对象之间产生依赖,除非一个对象不参与程序的运行,这样的对象就像一座孤岛,与其他对象没有任何交互,但是这样的对象也就没有任何存在的价值了。因此,在我们的程序代码中,任何一个对象必然会与其他一个甚至更多个对象产生依赖关系。

依赖产生的原因主要有:

方法调用

继承(子类依赖于父类)

传递参数(一个类作为参数传递给另外一个类的成员方法时)

临时变量引入

下面有两段代码。

第一段是直接在代码中引入了Train类,这时候就形成了依赖。但是假如旅客不想坐火车,想坐飞机,只能修改StupidTraveller中的代码。

第二段代码使用依赖注入的方式解除了上面StupidTraveller对Train的依赖。代码的可扩展性变强了很多,如果还有其他的交通工具比如Car,只需要将Car注入Traveller类中即可。此处的依赖注入是手动的,Laravel的IOC容器是自动注入的。

class Train{

public function go(){

echo "move by train \n";

}

}

class Airplane{

public function go(){

echo "move by airplane \n";

}

}

class StupidTraveller{

private $trafficTool;

public function __construct(){

$this->trafficTool = new Train();

}

public function travel(){

$this->trafficTool->go();

}

}

$mike = new StupidTraveller();

$mike->travel();

interface TrafficTool{

public function go();

}

class Train implements TrafficTool{

public function go(){

echo "move by train \n";

}

}

class Airplane implements TrafficTool{

public function go(){

echo "move by airplane \n";

}

}

class Traveller{

private $trafficTool;

public function __construct(TrafficTool $tool){

$this->trafficTool = $tool;

}

public function travel(){

$this->trafficTool->go();

}

}

//在这里进行了依赖注入

$mike = new Traveller(new Train());

$mike->travel();

2、IOC容器

2.1 IOC容器是什么?

在理解IOC之前,首先从1.2的例子中理解下什么是DI(Dependency Injection)。Traveller类在初始化的时候注入了TrafficTool类,这就是一个典型的依赖注入的例子。依赖注入有两个好处,一个是解耦,将依赖之间解耦,显然Traveller类要比StupidTraveller要优雅很多;第二个是由于已经解耦了,方便做单元测试,尤其是Mock测试。如果TrafficTool是一个数据库的操作类(打个比方啊),这个时候你做单元测试的时候可以直接Mock一个虚拟的数据库类注入到Traveller中去来做单元测试。

IOC(Inversion of Control) 控制反转是一种面对对象编程的设计原则,用于降低代码之间的耦合度。其基本思想是借助第三方实现具有依赖关系的对象之间的解耦。

101821a3c34f

混乱的依赖关系

101821a3c34f

IOC容器

软件系统在没有IOC之前,对象之间相互依赖,那么对象A在初始化或者运行到某一个点的时候,自己必须主动去创建一个对象B,或者使用之间已经创建好的对象B。无论是创建还是使用对象B,控制权都在自己手上。

但是在有了IOC容器后,这种情况改变了,由于IOC的加入,对象之间的直接联系消失了。在对象A运行时需要对象B的时候,IOC会主动创建一个对象B注入到对象A需要的地方。这也是Laravel中实现的IOC容器的神奇之处,自动注入依赖。

2.2 一段比较经典的IOC容器的代码

下面这段代码源于《laravel框架关键技术解析》

// 容器类,将接口与实现绑定

class Container

{

// 保存与接口绑定的闭包,

// 闭包必须能够返回接口的实例。

protected $bindings = [];

// 为某个接口绑定一个实现,有两种情况:

//

// 第一种是绑定接口的实现的类名;

// 第二种是绑定一个闭包,这个闭包应该返回接口的实例。

// 不管哪种情况,实例化操作都是调用 build() 方法完成。

// 第二种情况,主要是为了能够在实例化前后进行额外的操作,

// 实例化的逻辑就书写在闭包中。

public function bind($abstract, $concrete = null)

{

// 如果提供的参数不是闭包,而是一个类,

// 则构建一个闭包,直接调用 build() 方法进行实例化

if (! $concrete instanceof Closure) {

// 调用闭包时,传入的参数是容器本身,即 $this

$concrete = function ($c) use ($concrete) {

return $c->build($concrete);

};

}

$this->bindings[$abstract] = $concrete;

}

// 生成指定接口的实例

public function make($abstract)

{

// 取出闭包

$concrete = $this->bindings[$abstract];

// 运行闭包,即可取得一个实例

return $concrete($this);

}

public function build($class)

{

// 取得类的反射

$reflector = new ReflectionClass($class);

// 检查类是否可实例化

if (! $reflector->isInstantiable()) {

// 如果不能,意味着接口不能正常工作,报错

echo $message = "Target [$class] is not instantiable";

}

// 取得构造函数的反射

$constructor = $reflector->getConstructor();

// 检查是否有构造函数

if (is_null($constructor)) {

// 如果没有,就说明没有依赖,直接实例化

return new $class;

}

// 取得包含每个参数的反射的数组

$parameters = $constructor->getParameters();

// 返回一个真正的参数列表,那些被类型提示的参数已经被注入相应的实例

$realParameters = $this->resolveDependencies($parameters);

return $reflector->newInstanceArgs($realParameters);

}

protected function resolveDependencies($parameters)

{

$realParameters = [];

foreach($parameters as $parameter) {

// 如果一个参数被类型提示为类 foo,

// 则这个方法将返回类 foo 的反射

$dependency = $parameter->getClass();

if(is_null($dependency)) {

$realParameters[] = NULL;

} else {

$realParameters[] = $this->make($dependency->name);

}

}

return (array)$realParameters;

}

}

下面在用上面的IOC容器来实现先前的Traveller

require __DIR__ . '/Container.php';

interface TrafficTool

{

public function go();

}

class Train implements TrafficTool

{

public function go()

{

// TODO: Implement go() method.

echo "move by train";

}

}

class Airplane implements TrafficTool

{

public function go()

{

// TODO: Implement go() method.

echo "move by airplane\n";

}

}

class Destination

{

public function mark()

{

echo "I am here";

}

}

class Traveller

{

protected $trafficTool;

protected $destination;

public function __construct(TrafficTool $tool, Destination $destination)

{

$this->trafficTool = $tool;

$this->destination = $destination;

}

public function travel()

{

$this->trafficTool->go();

$this->destination->mark();

}

}

// 实例化容器

$app = new Container();

//绑定某一功能到ioc

//将Train绑定到TrafficTool,abstract 是TrafficTool, concrete是Train

$app->bind('TrafficTool', 'AirPlane');

$app->bind("Destination", "Destination");

$app->bind('travellerA', 'Traveller');

//实例化对象

$tra = $app->make('travellerA');

$tra->travel();

注意$tra = $app->make('travellerA');这里并没有手动的注入依赖TrafficTool。这个依赖在container的resolveDependencies方法中获取,最后在$reflector->newInstanceArgs($realParameters)这行代码中间进行了注入。并且这一系列的注入都不是手动的,注入都是自动完成的。

2.3、laravel中的IOC容器

在laravel初始化的地方index.php中有这样一行代码

/*

|--------------------------------------------------------------------------

| Turn On The Lights

|--------------------------------------------------------------------------

|

| We need to illuminate PHP development, so let us turn on the lights.

| This bootstraps the framework and gets it ready for use, then it

| will load up this application so that we can run it and send

| the responses back to the browser and delight our users.

|

*/

$app = require_once __DIR__.'/../bootstrap/app.php';

开灯了!!!这里的app就是IOC容器,里面已经进行了一系列的绑定。具体的绑定如下。

$app = new Illuminate\Foundation\Application(

realpath(__DIR__.'/../')

);

$app->singleton(

Illuminate\Contracts\Http\Kernel::class,

App\Http\Kernel::class

);

$app->singleton(

Illuminate\Contracts\Console\Kernel::class,

App\Console\Kernel::class

);

$app->singleton(

Illuminate\Contracts\Debug\ExceptionHandler::class,

App\Exceptions\Handler::class

);

return $app;

容器就是在vendor/laravel/framework/src/illuminate/Container/Container.php中实现的,具体的实现代码要比上面的那个简单版复杂很多,但是思想是一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值