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) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(11) "__construct"
["class"]=>
string(5) "Brand"
}
[1]=>
object(ReflectionMethod)#3 (2) {
["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)#5 (2) {
["car"]=>
object(Car)#10 (2) {
["gearbox"]=>
string(3) "4AT"
["engine"]=>
string(2) "V8"
}
["name"]=>
string(6) "五菱"
}
是否和你的想法一致呢?如果一致说明你已经理解类的反射与依赖注入原理了,恭喜你
如果还是不理解,也不要灰心,从头再看一遍,依赖注入是 Laravel 框架中最核心的部分,理解它对于你后面的学习是非常有帮助的,加油。
Github:github.com/hiccup711/Learning_Lara...