ReflectionClass实现了 Reflector 接口,使得我们可以使用该类查看另一个类的相关信息。所谓的反射,大概的意思就是将一个类的相关信息给反射 (映射、反映) 出来,转载。
无依赖的情况
要实例化一个类,获得其类名即可,实际项目中还需要结合自动加载,这里为了方便说明情况,就将所有类写在同一个文件中。这个操作很简单。
namespace Models;
class Car
{
}
namespace Framework;
class App
{
public function getInstance($className)
{
//实例化 ReflectionClass 对象
$reflector = new \ReflectionClass($className);
if (!$reflector->isInstantiable()) {
//不能被实例化的逻辑
return false;
}
//获取构造器
$constructor = $reflector->getConstructor();
//如果没有构造器,直接实例化
if (!$constructor) {
//这里用了变量来动态的实例化类
return new $className;
}
}
}
$app = new App();
$car = $app->getInstance('Models\Car');
var_dump($car); //输出 object(Models\Car)#4 (0) { }
带有多层依赖的情况
假设有一个汽车依赖底盘,底盘依赖轮胎和轴承,轮胎也依赖轴承,轴承无依赖。那么当需要实例化一个汽车类时,不友好的方式是这样的,$car = new Car(new Chassis(new Tyre(new Axle), new Axle())) ,打脑阔。
利用依赖注入是这样的。
namespace Framework;
//定义一个类,用于实现依赖注入
class App
{
public function getInstance($className)
{
//实例化 ReflectionClass 对象
$reflector = new \ReflectionClass($className);
if (!$reflector->isInstantiable()) {
//不能被实例化的逻辑,抽象类和接口不能被实例化
return false;
}
//获取构造器
$constructor = $reflector->getConstructor();
//如果没有构造器,也就是没有依赖,直接实例化
if (!$constructor) {
return new $className;
}
//如果有构造器,先把构造器中的参数获取出来
$parameters = $constructor->getParameters();
//再遍历 parameters ,找出每一个类的依赖,存到 dependencies 数组中
$dependencies = array_map(function ($parameter) {
/**
* 这里是递归的去寻找每一个类的依赖,例如第一次执行的时候,程序发现汽车 Car 类依赖底盘 Chassis
* 类,此时 $parameter 是一个ReflectionParameter 的实例,接着调用 ReflectionParameter
* 的 getClass() 方法,获得一个 ReflectionClass 的实例,再接着调用 ReflectionClass
* 的 getName() 方法,取得类名,也就是 Models\Chassis ,但此时此刻还不能直接去 new
* Models\Chassis ,因为 Models\Chassis 也有依赖,故要递归的去调用 getInstance
* 进一步去寻找该类的依赖,周而复始,直到触发上面的 if(!$constructor) ,停止递归。
*/
return $this->getInstance($parameter->getClass()->getName());
}, $parameters);
//最后,使用 ReflectionClass 类提供的 newInstanceArgs ,方法去实例化类,参数将会传入构造器中
return $reflector->newInstanceArgs($dependencies);
}
}
namespace Models;
class Car
{
protected $chassis;
//汽车依赖底盘
public function __construct(Chassis $chassis)
{
$this->chassis = $chassis;
}
}
class Chassis
{
protected $tyre;
protected $axle;
//底盘依赖轮胎和轴承
public function __construct(Tyre $tyre, Axle $axle)
{
$this->tyre = $tyre;
$this->axle = $axle;
}
}
class Tyre
{
protected $axle;
//轮胎也依赖轴承
public function __construct(Axle $axle)
{
$this->axle = $axle;
}
}
class Axle
{
//轴承无依赖
}
$app = new \Framework\App();
$car = $app->getInstance('Models\Car');
var_dump($car);
处理构造方法中的 普通参数class Car
{
protected $chassis;
protected $width;
//汽车依赖底盘
public function __construct(Chassis $chassis, $width) //
{
$this->chassis = $chassis;
$this->width = $width;
}
}
运行代码,报错 call to function getName() on null ,问题出在了 return $this->getInstance($parameter->getClass()->getName()) 这一行,原因是 $parameter->getClass() 的结果是 null,这也是必然的。查看手册发现这样的一段描述,ReflectionParameter::getClass — Get the type hinted class (获取所提示的类),上面加入的 $width ,没有做类型提示,$parameter->getClass() 得到的结果必然是 null 。
故,将有类型提示的和没有类型提示的分开处理。需要这样处理
namespace Framework;
class App
{
public function getInstance($className)
{
$reflector = new \ReflectionClass($className);
if (!$reflector->isInstantiable()) {
return false;
}
$constructor = $reflector->getConstructor();
if (!$constructor) {
return new $className;
}
$parameters = $constructor->getParameters();
$dependencies = array_map(function ($parameter) {
if (null == $parameter->getClass()) {
//处理没有类型提示的参数
return $this->processNoHinted($parameter);
} else {
//处理有类型提示的参数
return $this->processHinted($parameter);
}
}, $parameters);
return $reflector->newInstanceArgs($dependencies);
}
protected function processNoHinted(\ReflectionParameter $parameter)
{
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getName();
} else {
//参数为空则抛出异常
throw new \Exception($parameter->getName() . "不能为空", 1);
}
}
protected function processHinted(\ReflectionParameter $parameter)
{
return $this->getInstance($parameter->getClass()->getName());
}
}
namespace Models;
class Car
{
protected $chassis;
protected $width;
public function __construct(Chassis $chassis, $width = 2)
{
$this->chassis = $chassis;
$this->width = $width;
}
}
class Chassis
{
protected $tyre;
protected $axle;
public function __construct(Tyre $tyre, Axle $axle)
{
$this->tyre = $tyre;
$this->axle = $axle;
}
}
class Tyre
{
protected $axle;
public function __construct(Axle $axle)
{
$this->axle = $axle;
}
}
class Axle
{
}
$app = new \Framework\App();
$car = $app->getInstance('Models\Car');
var_dump($car);