ThinkPHP5源码学习篇--从执行到输出流程简介

        这一段时间工作上着急的项目一个接一个,从自我的角度来说,幸好没有沉浸于重复的工作。杂乱看了一些知识点,然而实际中没有使用,也不知该如何总结。毕竟觉得,只有真的用到了,研究可能的坑和真的踩了坑,才敢勉勉强强说会用了。
        另外在看Modern PHP和Java的Spring相关的视频。Spring学习到一点点觉得新奇且有意思的玩意儿,只是现在还达不到一个实现好项目的水平。而Modern PHP这本书,不厚大概在200页左右,对一些PHP的编写规则和平时开发中可能未被使用到的特性做了说明,比如迭代器、PSR规范、trait、闭包等,是近期以来看到最有意思的一本书,目前仍在感兴趣的看阶段。
        剩下的就是TP5框架,其实想仔细研究好长一段时间了,拾起总又放下。使用一个框架不仅仅在于会用,更要纠结应该怎么用,为什么要这么用,怎么用又更加又效率。学习一点点源码,就觉得获益匪浅,那么在融会贯通后,自己想实现定制的小框架,也绝非难事。


ThinkPHP5框架如何输出一个字符串

说说ThinkPHP5框架,如何从开始执行index.php到输出Hello World。
框架默认入口文件是public/index.php(前端控制器),当然index.php的位置可以自定义更改。
引入代码时,为了更加简洁的显示代码信息,将作者等信息省略,如下:

// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st 
// +----------------------------------------------------------------------

从执行文件的顺序来理解

  • public/index.php

定义常量APP_PATH,指向到application目录,业务逻辑代码都放置在这里。要注意的是,如果index.php更换了位置,__DIR__表示当前目录路径,所以这两句的位置都要根据实际来调整。定义常量后,加载thinkphp(tp5框架的核心目录)内的start.php,开始进入到tp5的启动逻辑。

thinkphp/start.php

send();

start.php内容比较简单,不过可以以此文件作为核心出发点,来分析功能。

  1. 要关注下这个namespace是think,后续了解到自定义think命名空间映射实际路径即为当前thinkphp目录
  2. 引入当前目录的base.php文件
  3. 执行App::run()后再执行send()方法

事务的生命周期往往经历开始->过程->结束的过程。

开始:base.php建设业务代码运行前的配置环境.
过程:App::run()方法包含了对应用的初始化、路由的解析等处理,查找到实际要执行的控制器和方法,执行真正意义上的业务逻辑,返回Response对象.
结束:通过“过程”执行返回的Response对象,执行send()方法,实现数据与客户端的输出.

可以把start.php作为tp5运行的核心中转站,来理解tp5各组件部分运行的关系。


来看看base.php文件

thinkphp/base.php

  • 默认定义了一批系统运行的常量参数
常量名称 常量值 真实含义
THINK_VERSION 5.0.19 TP版本信息
THINK_START_TIME 如:1536800036.3592 开始执行时间戳,毫秒级。如果要统一每个请求的耗时,利用该字段再好不过,比在业务代码内定义更精确
THINK_START_MEM 如:350968(bytes) 返回当前分配给PHP脚本的内存量。一般较多作用是两次访问计算差值来评估业务代码内存占用的合理性
EXT .php 设置PHP文件后缀名
DS 如:/ 不同操作系统文件分隔符有所不同,已存在变量DIRECTORY_SEPARATOR,新定义DS让操作更方便
THINK_PATH /thinkphp 框架路径
LIB_PATH /thinkphp/library 框架库路径
CORE_PATH /thinkphp/library/think 框架核心库路径
TRAIT_PATH /thinkphp/library/traits 框架traits库路径
ROOT_PATH / 项目根路径(绝对路径)
EXTEND_PATH /extend 拓宽库路径
VENDOR_PATH /vendor 第三方库路径,如composer
RUNTIME_PATH /runtime 系统运行缓存路径
LOG_PATH /runtime/log 系统运行日志路径
CACHE_PATH /runtime/cache 系统运行缓存文件路径
TEMP_PATH /runtime/temp 系统运行临时文件路径
CONF_PATH /application 配置文件路径
ENV_PREFIX PHP_ 设置ENV文件配置前缀
IS_CLI true | false 是否CLI请求
IS_WIN true| false 环境是否为win系统
  • 载入/thinkphp/library/think/Loader.php文件(加载文件相关)
if (is_file(ROOT_PATH . '.env')) {
    /*
    [databases]
    dbuser=root
    dbpasswd=db123

    [redis]
    redispwd=redis
    redisport=6379

    若.env内为如上格式数据,设置true返回
    array (
      'databases' =>
      array (
        'dbuser' => 'root',
        'dbpasswd' => 'db123',
      ),
      'redis' =>
      array (
        'redispwd' => 'redis',
        'redisport' => '6379',
      ),
    )
    不设置返回
    array (
      'dbuser' => 'root',
      'dbpasswd' => 'db123',
      'redispwd' => 'redis',
      'redisport' => '6379',
    )
    一维数组和二维数组的区别
     * */
    $env = parse_ini_file(ROOT_PATH . '.env', true);

    foreach ($env as $key => $val) {
        $name = ENV_PREFIX . strtoupper($key);

        //设置env变量
        if (is_array($val)) {
            foreach ($val as $k => $v) {
                $item = $name . '_' . strtoupper($k);
                putenv("$item=$v");
            }
        } else {
            putenv("$name=$val");
        }
    }
}
  • 配置文件的自动加载机制、注册错误和异常捕获方法、加载系统惯例配置
// 注册自动加载
\think\Loader::register();

//后续所有的类都通过autoload自动加载引入

// 注册错误和异常处理机制
\think\Error::register();

// 加载惯例配置文件
\think\Config::set(include THINK_PATH . 'convention' . EXT);

以上为base.php所要实现的功能,
  1. 设置必要的系统常量
  2. 加载系统默认配置
  3. 设置自动加载
  4. 注册错误和异常捕获方法

至此,框架运行业务逻辑前的热身工作已经完成! 剩下要专注就是业务的实现


thinkphp/library/think/App.php

App::run()对路由进行解析,执行业务逻辑,最后对输出对象进行实例化。

/**
     * 执行应用程序
     * @access public
     * @param  Request $request 请求对象
     * @return Response
     * @throws Exception
     */
    public static function run(Request $request = null)
    {
        //初始化request对象
        $request = is_null($request) ? Request::instance() : $request;

        try {
            //初始化应用,并返回配置信息
            $config = self::initCommon();

            // 模块/控制器绑定
            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);
                }
            }
            //设置filter的过滤执行方法
            $request->filter($config['default_filter']);

            // 默认语言
            Lang::range($config['default_lang']);
            // 开启多语言机制 检测当前语言
            $config['lang_switch_on'] && Lang::detect();
            $request->langset(Lang::range());

            // 加载系统语言包
            Lang::load([    //主要是将错误英文信息 对应到 中文
                THINK_PATH . 'lang' . DS . $request->langset() . EXT,
                APP_PATH . 'lang' . DS . $request->langset() . EXT,
            ]);

            // 监听 app_dispatch
            Hook::listen('app_dispatch', self::$dispatch);
            // 获取应用调度信息
            $dispatch = self::$dispatch;

            // 未设置调度信息则进行 URL 路由检测
            if (empty($dispatch)) {
                $dispatch = self::routeCheck($request, $config);
            }

            // 记录当前调度信息
            $request->dispatch($dispatch);

            // 记录路由和请求信息
            if (self::$debug) {
                Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
                Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
                Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
            }

            // 监听 app_begin
            Hook::listen('app_begin', $dispatch);

            // 请求缓存检查
            $request->cache(
                $config['request_cache'],
                $config['request_cache_expire'],
                $config['request_cache_except']
            );
            //执行逻辑程序,获取返回值
            $data = self::exec($dispatch, $config);
        } catch (HttpResponseException $exception) {
            $data = $exception->getResponse();
        }

        // 清空类的实例化
        Loader::clearInstance();

        // 输出数据到客户端
        if ($data instanceof Response) {
            $response = $data;
        } elseif (!is_null($data)) {
            // 默认自动识别响应输出类型
            $type = $request->isAjax() ?
            Config::get('default_ajax_return') :
            Config::get('default_return_type');

            $response = Response::create($data, $type);
        } else {
            $response = Response::create();
        }

        // 监听 app_end
        Hook::listen('app_end', $response);

        return $response;
    }

把这段代码按功能点划分,拆解内容如下:

  1. 实例化Request对象,封装HTTP请求相关的内容
  2. 初始化应用,进一步导入更深层次的配置文件
  3. 模块、控制器的初始绑定判断
  4. 设置请求参数的过滤方法,默认为空
  5. 加载系统语言提示信息
  6. 分析路由,定位将被执行的控制器和方法
  7. 执行业务代码,获取返回数据
  8. 根据返回数据的封装,实例化Response对象

thinkphp/Response

Response::send()负责控制向客户端输出。


写一段简单的输出代码,运行输出Hello字符串。

这个实现和平时接触的也许并不一样,为什么不直接用echo或exit来输出内容呢?

从代码的结构来说,不同的类各自负责不同的功能,一切皆对象,功能即可按对象来划分。因此Request对应请求、Response便对应了响应。这个从刚开始学Java的servlet老早就是这么实现的。

细致点有什么不同呢。Response封装了设置响应状态码、设置头部、输出数据格式检测、trace调试注入、配置缓存等功能,功能更加完善,也更加统一。


ThinkPHP5源码学习篇--从执行到输出流程简介_第1张图片

你可能感兴趣的:(PHP)