1、基础知识预备
1.1 PHP 反射详解
什么是反射?直观理解就是根据到达地找到出发地和来源。比如,一个光秃秃的对象,我们可以仅仅通过这个对象就能知道它所属的类,拥有哪些方法。
PHP的反射是指在PHP运行状态过程中,扩展分析PHP程序,导出或提出关于类、方法、属性、参数等详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能成为反射API
下面是一个PHP反射的一个简单demo。
详细可参考PHP docuement http://php.net/manual/zh/book.reflection.php
<?php
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容器是自动注入的。
<?php
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();
<?php
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) 控制反转是一种面对对象编程的设计原则,用于降低代码之间的耦合度。其基本思想是借助第三方实现具有依赖关系的对象之间的解耦。
混乱的依赖关系 IOC容器
软件系统在没有IOC之前,对象之间相互依赖,那么对象A在初始化或者运行到某一个点的时候,自己必须主动去创建一个对象B,或者使用之间已经创建好的对象B。无论是创建还是使用对象B,控制权都在自己手上。
但是在有了IOC容器后,这种情况改变了,由于IOC的加入,对象之间的直接联系消失了。在对象A运行时需要对象B的时候,IOC会主动创建一个对象B注入到对象A需要的地方。这也是Laravel中实现的IOC容器的神奇之处,自动注入依赖。
2.2 一段比较经典的IOC容器的代码
下面这段代码源于《laravel框架关键技术解析》
<?php
// 容器类,将接口与实现绑定
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
<?php
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容器,里面已经进行了一系列的绑定。具体的绑定如下。
<?php
$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中实现的,具体的实现代码要比上面的那个简单版复杂很多,但是思想是一样的。
网友评论