ThinkPHP5源码学习篇--业务执行前的过程

了解执行过程

我们经常会好奇配置好路由、写好业务代码后,为什么在URL上输出指定内容,就是执行到业务代码呢?


App::run()

// 模块/控制器绑定
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两种。


module模块运行

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
);

function闭包运行

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’

本质上差不多


App::exec()剩余代码

// 清空类的实例化
// 类每次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源码加以修改,不过这就是思想的传递和进步嘛,让自己的开发旅程更有成就感吧。

你可能感兴趣的:(PHP)