PHP具有完整的反射 API,提供了对类、接口、函数、方法和扩展进行逆向工程的能力。通过类的反射提供的能力我们能够知道类是如何被定义的,它有什么属性、什么方法、方法都有哪些参数,类文件的路径是什么等很重要的信息。也正式因为类的反射很多PHP框架才能实现依赖注入自动解决类与类之间的依赖关系,这给我们平时的开发带来了很大的方便。 本文主要是讲解如何利用类的反射来实现依赖注入(Dependency Injection),并不会去逐条讲述 PHP Reflection 里的每一个 API ,详细的 API 参考信息请查阅官方文档。
为了更好地理解,我们通过一个例子来看类的反射,以及如何实现依赖注入。
下面这个 Car
类代表汽车的信息。
/** | |
* Car 汽车信息 | |
*/ | |
class Car | |
{ | |
public $gearbox; | |
public $engine; | |
/** | |
* @param $gearbox // 变速箱 | |
* @param $engine // 汽车引擎 | |
*/ | |
public function __construct($gearbox = '4AT', $engine = 'V8') | |
{ | |
$this->gearbox = $gearbox; | |
$this->engine = $engine; | |
} | |
} |
接下里这个 Brand
类代表汽车品牌,它在构造函数中依赖了 Car
汽车信息类。
/** | |
* Brand 汽车品牌 | |
*/ | |
class Brand | |
{ | |
public $car; | |
public $name; | |
const WHEEL = 4; // 4个轮子 | |
/** | |
* @param Car $car // 依赖 Car 类 | |
* @param $name // 品牌名称 | |
*/ | |
public function __construct(Car $car, $name = '五菱') | |
{ | |
$this->car = $car; | |
$this->name = $name; | |
} | |
public function carInfo() | |
{ | |
printf($this->name . '汽车是一辆' . $this->car->gearbox . '变速箱' . $this->car->engine . '发动机的' . self::WHEEL . '轮车。'); | |
} | |
} |
ReflectionClass
下面我们通过反射来对 Brand
这个类进行反向工程。 把 Brand
类的名字传递给 ReflectionClass
来实例化一个 ReflectionClass
类的对象。
$reflectionClass = new ReflectionClass(Brand::class); | |
// 返回值 | |
object(ReflectionClass)#1 (1) { | |
["name"]=> | |
string(5) "Brand" | |
} |
反射出类的常量
$reflectionClass->getConstants(); | |
// 返回值 | |
array(1) { | |
["WHEEL"]=> | |
int(4) | |
} |
反射出类中定义的方法
$reflectionClass->getMethods(); | |
// 返回值:由 ReflectionMethod 对象构成的数组 | |
array(2) { | |
[ | ]=>|
object(ReflectionMethod) | |
["name"]=> | |
string(11) "__construct" | |
["class"]=> | |
string(5) "Brand" | |
} | |
[ | ]=>|
object(ReflectionMethod) | |
["name"]=> | |
string(7) "carInfo" | |
["class"]=> | |
string(5) "Brand" | |
} | |
} |
我们还可以通过 getConstructor()
来单独获取类的构造方法,其返回值为一个 ReflectionMethod
对象。
$constructor = $reflection->getConstructor();
反射出某方法的参数
$parameters = $constructor->getParameters(); | |
// 返回值:ReflectionParameter 对象构成的数组。 | |
array(2) { | |
[0]=> | |
object(ReflectionParameter)#3 (1) { | |
["name"]=> | |
string(3) "car" | |
} | |
[1]=> | |
object(ReflectionParameter)#4 (1) { | |
["name"]=> | |
string(4) "name" | |
} | |
} |
依赖注入
接下来我们编写一个名为 make
的方法,传递类名称给 make
以返回类的对象,在 make
里它会帮我们注入类的依赖,即在本例中帮我们注入 Car
对象给 Brand
类的构造方法。
/** | |
* 构建类与类的依赖 | |
* @param $className | |
* @return object|null | |
* @throws ReflectionException | |
*/function make($className) | |
{ | |
$reflectionClass = new ReflectionClass($className); | |
$constructor = $reflectionClass->getConstructor(); // 获取构造函数方法 | |
$parameters = $constructor->getParameters(); // 获取构造函数的参数 | |
$dependencies = getDependencies($parameters); // 开始解析依赖 | |
return $reflectionClass->newInstanceArgs($dependencies); // 返回实体类 | |
} | |
/** | |
* 依赖解析 递归调用 | |
* @param $parameters // 由 ReflectionParameter 对象组成的数组 | |
* @return array | |
* @throws ReflectionException | |
*/ | |
function getDependencies($parameters) | |
{ | |
$dependencies = []; | |
foreach($parameters as $parameter) { | |
/** | |
* 通过参数名称,使用 getClass() 方法, 获取 ReflectionClass 类的对象 | |
* 我们 Brand 的构造函数第一个参数是 Car 类,这里的代码流程就是: | |
* 第一次循环时: $parameter 的参数为 car, $dependency 此时是通过 getClass() 方法获取到了 Car 的反射类。 | |
* - 因为获取到了反射类,所以不会走 if(is_null()),而是走到下面的 else 的递归调用。 | |
* - 再次返回到上面的 make() 方法,此时获取的反射类是 Car Car Car! $paramters 有两个参数,这两个参数分别是 Car 的构造方法中的 $gearbox 和 $engine | |
* - 当再次进入这个 foreach 循环时, $parameter 无法再通过 getClass() 方法获取反射类,就会走到 if(is_null()) 方法,isDefaultValueAvailable() 方法是用于判断构造函数的参数是否有默认值,如果有将它加入到 $dependencies 数组中。如果没有,我们需要「补0」,用于构造函数必须含有参数的情况。 | |
* - 到此,Car 类就被依赖注入成功了。 | |
* 第二次循环时:因为 $dependency 无法通过 $name 的值获取到反射类,那和上面的 $gearbox 与 $engine 的流程相同。 | |
*/ | |
$dependency = $parameter->getClass(); | |
if (is_null($dependency)) { | |
if($parameter->isDefaultValueAvailable()) { | |
$dependencies[] = $parameter->getDefaultValue(); | |
} else { | |
// 不是可选参数的为了简单直接赋值为字符串0 | |
// 针对构造方法的必须参数这个情况 | |
// Laravel 是通过 service provider 注册 closure 到 IocContainer, | |
// 在 closure 里可以通过 return new Class($param1, $param2) 来返回类的实例 | |
// 然后在 make 时回调这个 closure 即可解析出对象 | |
// 具体细节我会在另一篇文章里面描述 | |
$dependencies[] = '0'; | |
} | |
} else { | |
//递归解析出依赖类的对象 | |
$dependencies[] = make($parameter->getClass()->name); | |
} | |
} | |
return $dependencies; | |
} | |
$brand = make(Brand::class); | |
$brand->carInfo(); // 执行结果:五菱汽车是一辆4AT变速箱V8发动机的4轮车。 |
到此,一个 Brand
已经被实例化成功了,你能通过上面的代码想象出 Brand
实例的数据结构吗?给你时间思考:
$brand
的最终数据结构是:
object(Brand) | |
["car"]=> | |
object(Car) | |
["gearbox"]=> | |
string(3) "4AT" | |
["engine"]=> | |
string(2) "V8" | |
} | |
["name"]=> | |
string(6) "五菱" | |
} |
是否和你的想法一致呢?如果一致说明你已经理解类的反射与依赖注入原理了,恭喜你
如果还是不理解,也不要灰心,从头再看一遍,依赖注入是 Laravel 框架中最核心的部分,理解它对于你后面的学习是非常有帮助的,加油。
Github:github.com/hiccup711/Learning_Lara...