我们经常会好奇配置好路由、写好业务代码后,为什么在URL上输出指定内容,就是执行到业务代码呢?
// 模块/控制器绑定
if (defined('BIND_MODULE')) {
BIND_MODULE && Route::bind(BIND_MODULE);
} elseif ($config['auto_bind_module']) { //入口自动绑定模块 默认为false
// 入口自动绑定
$name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
Route::bind($name);
}
}
如果先定义一个常量BIND_MODULE或在config.php内配置了auto_bind_module项,则调用Route::bind()对模块进行绑定
//执行逻辑程序,获取返回值
$data = self::exec($dispatch, $config);
------------------------------------------------------------
protected static function exec($dispatch, $config)
{
switch ($dispatch['type']) {
case 'redirect': // 重定向跳转
//重定向,url地址将更换
//$dispatch可以传入http://完整地址跳转到其他网站
//也可以传入控制器+方法跳转到内部其他逻辑
$data = Response::create($dispatch['url'], 'redirect')
->code($dispatch['status']);
break;
case 'module': // 模块/控制器/操作
//多模块配置,执行业务逻辑
$data = self::module(
$dispatch['module'], //模块信息
$config, //配置项
isset($dispatch['convert']) ? $dispatch['convert'] : null
);
//$data 为控制器方法的返回值
break;
case 'controller': // 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
//Loader::action() 执行某个控制器或当前控制器的 指定方法,此处单纯传入控制器运行有误
//$dispatch['controller'] 1、携带模块+控制器+方法名 2、携带方法名 (前提是Request的controller被赋值)
$data = Loader::action(
$dispatch['controller'],
$vars,
$config['url_controller_layer'],
$config['controller_suffix']
);
break;
case 'method': // 回调方法
//直接执行某个类的方法
//$dispatch['method'] 可以是一个数组,包含类实例对象,和方法名
//也可以是传入静态方法定义规则的字符串
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;
case 'function': // 闭包
//比如在route.php 定义一个路由指向到闭包function,会执行这句
$data = self::invokeFunction($dispatch['function']);
break;
case 'response': // Response 实例
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}
return $data;
}
根据$dispatch数组内的type字段,来区分业务的实际执行类型
redirect:重定向跳转
module:模块控制器方法执行
controller:执行控制器操作
method:回调方法
function:闭包
response:Response实例
从个人的应用角度来说,目前用到的是module和function两种。
case 'module': // 模块/控制器/操作
//多模块配置,执行业务逻辑
$data = self::module(
$dispatch['module'], //模块信息
$config, //配置项
isset($dispatch['convert']) ? $dispatch['convert'] : null
);
//$data 为控制器方法的返回值
break;
这种是使用场景最多的,我们一般通过URL访问都是定向访问指定模块、指定控制器、指定方法运行。
$dispatch[‘module’] 包含"模块信息",这个"模块"并不单单表示框架的模块,而是路由相关的整个信息
$config是配置项
$dispatch[‘convert’]来设置传入的pathinfo信息是否自动转换
if ($config['app_multi_module']) {
//TODO ...........
} else {
// 单一模块部署
$module = '';
$request->module($module);
}
根据app_multi_module配置项来区分是否是多模块部署模式,一般来说访问地址格式是module/controller/method,在application下可以构造多个模块目录,可以在各个模块内继续生成controller等等彼此不相关的控制器。如果我们想在一个项目文件下实现多套业务逻辑,比如一个模块是api接口,一个模块是web站点,那么这将是很好的分割方式。
而如果使用单模块的模式,就没有这么复杂,application下有且有一个可被直接访问的controller结构。
那么多模块部署做了什么特殊的工作呢
/*
* 若模块名不存在,则获取配置文件中的默认模块名
* 如访问:http://localhost/thinkphp5/public
* array(
* 0 => '',
* 1 => null,
* 2 => null
* */
//$result[0] 不存在则取出 配置中的default_module作为默认模块
$module = strip_tags(strtolower($result[0] ?: $config['default_module']));
//如果请求未传入pathinfo相关信息,框架会使用default_module配置项作为默认模块
//获取绑定的模块名
$bind = Route::getBind('module');
$available = false;
if ($bind) {
// 绑定模块
list($bindModule) = explode('/', $bind);
//如果传入$result[0]为空 则$module 被赋值为 预绑定的模块名
if (empty($result[0])) {
$module = $bindModule;
$available = true;
} elseif ($module == $bindModule) {
$available = true;
}
} elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
//如果 模块名 在白名单内 或 模块目录 不存在,则$available不会设置为true
$available = true;
}
这部分代码要放在一起考虑,
Route::getBind(‘module’)来获取前置操作中绑定的模块,若请求模块与绑定模块不同,则请求无效,我们可以创建不同的入口文件来绑定指定模块达到业务定制的目的。
如果未手动绑定模块,需要判断当前模块是否在不可访问名单内,且模块目录需检测存在。deny_module_list配置项可以设置不想被访问的模块,默认common模块不可访问,可以放入一些较为机密的文件。
// 模块初始化
// $module有值 且 $available为true,表示模块已检测成功
if ($module && $available) {
// 初始化模块
//模块赋值
$request->module($module);
//加载模块内的配置文件
$config = self::init($module);
// 模块请求缓存检查
//正常情况下不会再运行逻辑了,
$request->cache(
$config['request_cache'],
$config['request_cache_expire'],
$config['request_cache_except']
);
} else {
throw new HttpException(404, 'module not exists:' . $module);
}
以上模块准备工作做了那么多,如果发现模块检测失败则抛出异常,反馈错误信息。
检测成功后,执行App::init()将导入模块目录内的配置文件,init()方法在框架初始化时已初步调用,此处继续载入新的配置,新的覆盖旧的,这也是为什么模块配置优先级要大于惯例配置
// 设置默认过滤机制
$request->filter($config['default_filter']);
// 当前模块路径
App::$modulePath = APP_PATH . ($module ? $module . DS : '');
// 是否自动转换控制器和操作名 url_convert设置默认为true
$convert = is_bool($convert) ? $convert : $config['url_convert'];
// 获取控制器名
// 如果$result[1]传入为空,则取出default_controller作为 控制器名称
// 如果$result[1]传入非空,则可作为控制器名称
$controller = strip_tags($result[1] ?: $config['default_controller']);
// 根据$convert
$controller = $convert ? strtolower($controller) : $controller;
// 获取操作名
// 如果$result[2]传入为空,则取出default_action作为 方法名称
// 如果$result[2]传入非空,则可作为方法名称
$actionName = strip_tags($result[2] ?: $config['default_action']);
if (!empty($config['action_convert'])) {
//action_convert 可以理解为 是否将方法名转换格式
//但是实际上初始化未设置该配置项,需要的话得开发者自行设置
//首字母转大写 _字符 转大写
$actionName = Loader::parseName($actionName, 1);
} else {
//若$convert为true,则$actionName转换为全小写格式
$actionName = $convert ? strtolower($actionName) : $actionName;
}
// 设置当前请求的控制器、操作
// 控制器和方法 赋值 存入到Request内
$request->controller(Loader::parseName($controller, 1))->action($actionName);
此处除了一些参数的赋值外关键是
如果控制器名不存在,使用default_controller配置项
如果方法名不存在,使用default_action配置项
而$convert决定了控制器名和方法名是否全部转换成小写格式
再将控制器名和方法名存入到Request对象内,方便后续使用
try {
// 通过前面的处理,模块名、控制器名、方法名都已经赋值到Request类内部
//加载控制器 并实例化
//$controller:控制器名称
//url_controller_layer:默认的访问控制器层 ==> controller
//controller_suffix:是否设置控制器后缀 ==> false
//若设置为true,如访问Index,会查找IndexController控制器
//empty_controller:默认的空控制器名,如果无法访问指定控制器,若空控制器存在,会默认调用
$instance = Loader::controller(
$controller,
$config['url_controller_layer'],
$config['controller_suffix'],
$config['empty_controller']
);
//$instance ==> object类型 通过反射实现实例化的控制器类
} catch (ClassNotFoundException $e) {
throw new HttpException(404, 'controller not exists:' . $e->getClass());
}
实现通过反射机制返回指定控制器的实例化对象。PHP反射可以动态的获取类的构成信息,比如存在什么属性,存在什么方法,方法是否static、public,是否存在构造函数,方法有几个参数,参数类型是什么,实现依赖注册等等。基于此,框架的核心便实现了。被多人使用的代码,大至框架,小至于公司项目,我们要尽量的掌控可能出现的问题,将对应错误信息展示给开发者或接入商,以实现更好、更快、更方便的接入。类是否存在、方法是否存在可访问,单靠class_exists()和method_exists()两个方法可不够。
另外这里有个较好的编程习惯,Loader::controller()正常情况下返回类实例赋值到$instance变量,当类不存在情况发生时,若通过return操作,则需要对$instance变量作if判断。这里以一个try-catch封装,当类不存在手动抛出一个ClassNotFound异常并捕获生成指定Response类,代码实现上更为简洁。
附上Loader::getModuleAndClass()方法,用于获取模块名和控制器名
/**
* 解析模块和类名
* @access protected
* @param string $name 资源地址
* @param string $layer 验证层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return array
*/
protected static function getModuleAndClass($name, $layer, $appendSuffix)
{
//总结来说,这个方法是想要获取到模块名+携带命名空间信息的类名
//1、$name:命名空间+类名 根据第一个if判断 暂未发现实际情况
//2、$name:控制器名称 $layer:controller $appendSuffix:false
//3、$name:模块名/控制器名称(/...) $layer:controller $appendSuffix:false
/*
* 例:
* $name ==> index
* $layer ==> controller
* $appendSuffix ==> false
* */
if (false !== strpos($name, '\\')) {
//$name 存在\\,说明已为 命令空间+类名 则作为$class变量处理
//$module通过Request类来获取
$module = Request::instance()->module();
$class = $name;
} else {
//如果$name 存在/,说明模块/控制器名称
if (strpos($name, '/')) {
//若$name字段中间存在/ 切割获取模块名和控制器名
list($module, $name) = explode('/', $name, 2);
} else {
//如果 $name 不存在/,则通过Request类获取模块名
$module = Request::instance()->module();
}
//解析 控制器类的详细信息 命名空间+类名称
$class = self::parseClass($module, $layer, $name, $appendSuffix);
}
//如:$class ==> app\index\controller\Index $module ==> index
return [$module, $class];
}
附上Loader::parseClass()方法,获取控制器信息
/**
* 解析应用类的类名
* @access public
* @param string $module 模块名
* @param string $layer 层名 controller model ...
* @param string $name 类名
* @param bool $appendSuffix 是否添加类名后缀
* @return string
*/
public static function parseClass($module, $layer, $name, $appendSuffix = false)
{
//controller目录下 可以存在多级目录,通过explode的切割 和 implode的组合,来获取path及class
//$name为控制器类名,若存在/或.字符,说明存在多级目录,则替换为\\ 并以\\为标志拆分成数组
$array = explode('\\', str_replace(['/', '.'], '\\', $name));
//若存在多级,array_pop弹出数组尾部元素为控制器名称
//若不存在多级,数组数量仅为1,array_pop弹出即为控制器名称
$class = self::parseName(array_pop($array), 1);
//以上,获取到具体控制器名称,避免多级目录的影响,并将控制器名称 大小写按要求转换
//逻辑判断 决定控制器名称是否存在后缀
//App::$suffix默认为false $appendSuffix由config.php配置
//所以控制器是否携带后缀,通过配置项来决定
//$layer 由config.php配置,且与实际路径有关,且作为后缀追加,首字母需大写转换
//如$layer 为abc 则控制器全名称为 控制器+Abc,实际路径是 modulePath/abc/控制器
$class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
//实现控制器多层目录嵌套
// 若$array经过前面的array_pop处理后仍存在数据,则说明当前控制器存在嵌套目录
// 通过implode处理将数据整合,作为下面的类路径拼接
$path = $array ? implode('\\', $array) . '\\' : '';
//拼接命令空间+类库地址
return App::$namespace . '\\' .
($module ? $module . '\\' : '') .
$layer . '\\' . $path . $class;
// 返回 应用类库命名空间(app)+ 模块名(可选)+ 控制器目录 + 嵌套目录(可选)+ 控制器名称
}
Loader::controller()方法实现细节
/**
* 实例化(分层)控制器 格式:[模块名/]控制器名
* @access public
* @param string $name 资源地址
* @param string $layer 控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $empty 空控制器名称
* @return object
* @throws ClassNotFoundException
*/
public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
//获取控制器解析数据 命名空间+控制器类名
/*
* 例:
* $name ==> index
* $layer ==> controller
* $appendSuffix ==> false
* */
list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
//判断指定类是否存在
if (class_exists($class)) {
//若类实际存在,调用反射执行类的实例化
return App::invokeClass($class);
}
//若控制器未找到,则判断是否可执行空执行器逻辑
//默认为Error
if ($empty) { //设置了空操作控制器
//分析空操作器的命令空间信息
$emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
//判断空控制器类是否存在
if (class_exists($emptyClass)) {
//实例化空控制器,默认在构造函数内传入$request
return new $emptyClass(Request::instance());
}
}
//1、控制器分析未找到
//2、空操作控制器不存在
//抛出ClassNotFount异常
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
首先获取模块名和控制器类名,如果类存在则调用App::invokeClass()生成类对象;如果类不存在,则判断"空"控制器是否存在,存在继续实例化。以上两个控制器都未找到时,将抛出ClassNotFoundException异常由上层代码捕获处理
TP5实现反射实例化控制器对象
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 变量
* @return mixed
*/
public static function invokeClass($class, $vars = [])
{
//$reflect 报告了一个类的有关信息
$reflect = new \ReflectionClass($class);
//获取类的构造函数
//$constructor:ReflectionMethod
//报告了一个方法的有关信息
$constructor = $reflect->getConstructor();
//若不存在构造函数,则也不会存在注入参数
//若存在构造函数,则调用bindParams方法查找可能需要依赖注入的参数
$args = $constructor ? self::bindParams($constructor, $vars) : [];
//$args 为索引下标数组,下标从小到大,对应方法从左往右到参数列表
//创建类的实例 携带给定参数。给出的参数将传递到类的构造函数
//参数以 array 形式传递到类的构造函数。
//返回值:返回类的新实例
return $reflect->newInstanceArgs($args);
}
额外说说此处实现依赖注入的方式,反射类获取指定方法的参数数量、类型,经过判断处理后,调用方法以传入参数的方式实现参数的注入。
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 变量
* @return mixed
*/
public static function invokeClass($class, $vars = [])
{
//$reflect 报告了一个类的有关信息
$reflect = new \ReflectionClass($class);
//获取类的构造函数
//$constructor:ReflectionMethod
//报告了一个方法的有关信息
$constructor = $reflect->getConstructor();
//若不存在构造函数,则也不会存在注入参数
//若存在构造函数,则调用bindParams方法查找可能需要依赖注入的参数
$args = $constructor ? self::bindParams($constructor, $vars) : [];
//$args 为索引下标数组,下标从小到大,对应方法从左往右到参数列表
//创建类的实例 携带给定参数。给出的参数将传递到类的构造函数
//参数以 array 形式传递到类的构造函数。
//返回值:返回类的新实例
return $reflect->newInstanceArgs($args);
}
/**
* 绑定参数
* @access private
* @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
* @param array $vars 变量
* @return array
*/
private static function bindParams($reflect, $vars = [])
{
// 自动获取请求变量
if (empty($vars)) {
//URL参数方式 0 按名称成对解析 1 按顺序解析
//其实不管按这种方式解析,route()和param()方法返回的内容都一致的
$vars = Config::get('url_param_type') ?
Request::instance()->route() :
Request::instance()->param();
}
$args = [];
//判断参数数目
if ($reflect->getNumberOfParameters() > 0) {
//可以说是为了下一步做铺垫,避免不可控情况
reset($vars);
//判断$vars的下标是否为数字来获取配置的 url_param_type类型
$type = key($vars) === 0 ? 1 : 0;
//通过getParameters()逐个获取参数,再调用getParamValue实现参数需要格式 存入到$args数组内
foreach ($reflect->getParameters() as $param) {
$args[] = self::getParamValue($param, $vars, $type);
}
}
return $args;
}
/**
* 获取参数值
* @access private
* @param \ReflectionParameter $param 参数
* @param array $vars 变量
* @param string $type 类别
* @return array
*/
private static function getParamValue($param, &$vars, $type)
{
//获取参数的名称
$name = $param->getName();
//获取参数的类反射对象
//若非类对象,则$class为null
//$class变量类型:ReflectionClass
$class = $param->getClass();
//传入类对象处理
if ($class) {
//获取类的名称
$className = $class->getName();
//查看Request内是否已绑定存在
$bind = Request::instance()->$name;
//若存在则直接赋值
if ($bind instanceof $className) {
$result = $bind;
} else {
//若类内部存在invoke方法,public且static
//则调用$className内部的静态invoke方法,默认注入Request类,将执行结果值返回作为变量
//其实和instance方法没什么差别,只是做了更为严格的访问权限控制,和强制静态访问
//invoke方法也必须返回当前类的实例化对象才行
if (method_exists($className, 'invoke')) {
$method = new \ReflectionMethod($className, 'invoke');
if ($method->isPublic() && $method->isStatic()) {
return $className::invoke(Request::instance());
}
}
//若类内部存在instance()方法,则默认作为静态方法,public,且将返回实例化对象。可视为框架内部的功能
//若类内部不存在,则new新对象
$result = method_exists($className, 'instance') ?
$className::instance() :
new $className;
}
} elseif (1 == $type && !empty($vars)) {
// type == 1,解析规则为:按顺序解析
// 弹出数组头部的值,即从左往右的顺序逐个赋值
$result = array_shift($vars);
} elseif (0 == $type && isset($vars[$name])) {
// type == 0,解析规则为:按键值对解析
// 获取对应键名的值
$result = $vars[$name];
} elseif ($param->isDefaultValueAvailable()) {
//以上检测 参数皆无有效值,那么就判断是否有默认值,若有则设置
//这步是蛮有必要的,比如共需两个变量,$id = 5,$name 仅传入name:LLL,则在此处将5赋给$id后能继续处理$name
$result = $param->getDefaultValue();
} else {
//综上检测无条件符合,则当前变量在method内无法定位到
throw new \InvalidArgumentException('method param miss:' . $name);
}
return $result;
}
正常情况下,以上处理后,$instance变量已经是某个控制器的类对象了,也以依赖注入的方式执行了构造函数。
$vars = [];
//检测函数或方法在当前环境中是否可调用
//$action为方法名,忽略大小写
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
// 严格获取当前操作方法名
$reflect = new \ReflectionMethod($instance, $action);
// 重复:严格获取当前操作方法名,区分实际的大小写
$methodName = $reflect->getName();
// 若配置类方法名通过后缀,则去除后缀,获取真实方法名
$suffix = $config['action_suffix'];
$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
//真实方法名赋值,方便后续取出
$request->action($actionName);
} elseif (is_callable([$instance, '_empty'])) {
//若当前$action方法不可用,判断是否存在_empty方法,作为空方法调用
// 空操作
$call = [$instance, '_empty'];
// 将实际调用方法名作为参数
$vars = [$actionName];
} else {
// 以上判断,$action方法不可调,且不存在空方法,则抛出异常
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}
Hook::listen('action_begin', $call);
return self::invokeMethod($call, $vars);
顺便一说,有同事说$request->action(true)获取的方法名称被转小写了,仔细一看,原来他是在控制器的构造函数里获取,在url_convert配置开启的时候,调用构造函数,方法名是经过strtolower()转小写处理。而在上面这段代码内,会重新严格获取方法的名称,因此在指定方法内获取又是正常。毕竟在构造函数调用和方法调用的中间过程里,还执行了好大一段逻辑。
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param string|array $method 方法
* @param array $vars 变量
* @return mixed
*/
public static function invokeMethod($method, $vars = [])
{
/*
* $method
* 1、
* array (
* object 控制器类实例化对象
* action 控制器内被调用方法名
* )
* 2、
* string
* 调用静态方法
*
* $vars
* 依赖注入参数
* */
if (is_array($method)) {
//$method[0] 非 object,调用invokeClass实例化
$class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
$reflect = new \ReflectionMethod($class, $method[1]);
} else {
// 静态方法
$reflect = new \ReflectionMethod($method);
}
//获取方法的参数列表,为下一步的依赖注入封装参数
$args = self::bindParams($reflect, $vars);
self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
//使用数组给方法传送参数,并执行他
//ReflectionMethod 执行invokeArgs方法,带入实例化的$class object对象,和参数数组
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
}
再看这儿,$data即为$reflect->invokeArgs()调用返回结果。
$data = self::module(
$dispatch['module'], //模块信息
$config, //配置项
isset($dispatch['convert']) ? $dispatch['convert'] : null
);
case 'function': // 闭包
//比如在route.php 定义一个路由指向到闭包function,会执行这句
$data = self::invokeFunction($dispatch['function']);
break;
再来看闭包的实现
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param string|array|\Closure $function 函数或者闭包
* @param array $vars 变量
* @return mixed
*/
public static function invokeFunction($function, $vars = [])
{
$reflect = new \ReflectionFunction($function);
$args = self::bindParams($reflect, $vars);
// 记录执行信息
self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
return $reflect->invokeArgs($args);
}
redirect重定向跳转
$data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
这是Response的内容了,此处不展开说明,功能很直白。实现进行站内跳转或第三方地址跳转。
case ‘controller’
case ‘method’
case ‘response’
本质上差不多
// 清空类的实例化
// 类每次new后,都会将实例存储在数组内,此时将数组清空
// 则所有已实例化都对象,没有变量指向,会加速清理
// 更好的方式 可以循环数组 对每个指向对象进行unset处理
Loader::clearInstance();
//检查$data变量对类型,针对不同类型做各自处理
//作用是为了能够return Response类型的变量
//以下三个判断分别为
//1、已是Response的变量,仅做简单赋值
//2、data数据非null,根据请求类型和返回类型配置,生成Response
//3、data数据为null,则生成空Response
// 输出数据到客户端
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
//若是ajax请求,则通过预设置的ajax return类型作为type输出
//若非ajax请求,则通过预设置type return类型作为输出格式
//default_ajax_return ==> json
//default_return_type ==> html
$type = $request->isAjax() ?
Config::get('default_ajax_return') :
Config::get('default_return_type');
$response = Response::create($data, $type);
} else {
//$data数据类型为null,创建一个空的Response对象
$response = Response::create();
}
// 监听 app_end
Hook::listen('app_end', $response);
//后续就是执行 Response的send()方法
return $response;
至此,业务逻辑执行完毕,剩下的就是通过Response对象来实现向客户端输出了,TP5将请求、执行、输出进行了功能分层,让各个类各司其职。
阅读源码过程并不顺利,总是磨一点磨一点的啃着,特别路由、模版渲染、数据库操作三大块个人觉得是最复杂的地方还没开始看。
在慢慢享受这个过程的同时,对自己日常对开发工作确实也提高了点效率和增加了乐趣。比如更好的利用Response,利用Debug类实现接口内存、运行时间消耗的监控等等。在近日重新搭建api项目时,按照往常统一入口封装,但是这次在这个基础上,使用工厂类来实例化具体的api-service对象,同时基于反射实现了其它服务类和工具类的依赖注入,使用monolog库记录输入输出报文。虽然只是copy源码加以修改,不过这就是思想的传递和进步嘛,让自己的开发旅程更有成就感吧。