背景描述
一个成熟的框架对与每个请求的处理其实大致都是相同的,大致流程(主要突出解析路由到控制器的步骤):
- 疑问一:我理解路由只能解析出对应的控制器和方法,如果实例化控制器需要参数改怎么处理呢?
- 疑问二:如果控制器实例化需要的参数是个对象或者是类怎么办?(确实存在这种情况)
问题解析:
带着疑问我们取啃源码,找到根源所在。当你看到这断代码ReflectionClass,就是找到问题的根源了。它叫反射
new ReflectionClass($className);
- 何为反射?有什么作用?
官方文档没有写太多,我就说说我自己的理解:反射包含很多方法,在框架中使用广泛的是 ReflectionClass,它可以帮我们取实例化一个类,类似new 但是 new一个对象,我们需要自己传参数,用 ReflectionClass 它会告诉我们模板对象需要哪些参数,再循环去处理需要的参数
反射的理解和使用:
创建一个测试项目,跟我一起来探秘一下ReflectionClass的使用
| Test
| - Controller
| - - TestController.php
| - Model
| - - TestModel.php
| - Service
| - - TestService.php
| - inde,php
- TestController.php
<?php
namespace Test\Controller;
use Test\Service\TestService;
class TestController {
private $model;
public function __construct(TestService $model)
{
$this->model = $model;
}
public function test(){
echo $this->model->test();
}
}
- TestService.php
<?php
namespace Test\Service;
use Test\Model\TestModel;
class TestService {
private $test;
public function __construct(TestModel $test)
{
$this->test = $test;
}
public function getTest(){
return $this->test->test();
}
}
- TestModel.php
<?php
namespace Test\Model;
class TestModel {
public function test(){
return 'implements TestInterface';
}
}
- 正常调用TestController (index.php) 自动加载相关请看
function test_autoload($class){
$classPath = dirname(__DIR__).'\\'.$class.'.php';
if(file_exists($classPath)){
require_once ($classPath);
}else{
echo $classPath."不存在";
}
}
spl_autoload_register('test_autoload');
$model = new \AotoLoad1\Model\TestModel();
$server = new \AotoLoad1\Service\TestService($model);
$c = new \AotoLoad1\Controller\TestController($server);
$c->test();//输出:implements TestInterface
- 利用反射实例化TestController(index.php) 自动加载相关请看
//自动加载(简化代码,代替incloud_once)
function test_autoload($class){
$classPath = dirname(__DIR__).'\\'.$class.'.php';
if(file_exists($classPath)){
require_once ($classPath);
}else{
echo $classPath."不存在";
}
}
spl_autoload_register('test_autoload');
/**
* 反射加载
* @param $className
*
* @return object
* @throws ReflectionException
*/
function classReload($className){
try{
//实例化需要的类
$class = new ReflectionClass($className);
//是否有__contruct()
$constructor = $class->getConstructor();
$p = [];//定义需要实例化的类的构造函数都有哪些参数
if($constructor!==null){//有_contruct()构造函数
foreach ($constructor->getParameters() as $k=>$parameter){//遍历构造函数的参数
$parameterClass = $parameter->getClass();//构造函数的参数是不是类
if ($parameterClass === null) {//不是类
$resolveName = $parameter->getName();//获取参数的名字
} else {
$resolveName = $parameterClass->name;//或者参数类的className
}
try {
$p[] = classReload($resolveName);//递归处理构造函数的参数
} catch (ReflectionException $e) {//如果不能加载
if ($parameter->isDefaultValueAvailable()) {//验证是否有可用默认值 有
$p[] = $parameter->getDefaultValue(); //直接获取默认值
} else if ($parameterClass !== null) {//如果参数是类 且不能加载
$resolveName = $parameter->getName();//获取类名
$p[] = classReload($resolveName);//递归处理类名
} else {//如果依然不能加载 抛错
throw $e;
}
}
}
}
return $class->newInstanceArgs($p);//传参执行实例化
}catch (ReflectionException $exception){
throw $exception;
}
}
$c = classReload('AotoLoad1\Controller\TestController');//去实例化加载TestController
$c->test();//执行test方法
- 输出结果:
implements TestInterface
- 代码执行流程
总结
通过例子可以看出 其实框架就是通过反射ReflectionClass方法去实例化控制器,如果有在构造函数内的对象,就通过递归再次用ReflectionClass去实例化,知道把所有都全部实例化了,再去调用控制器内的方法,使代码更加简洁