这一段时间工作上着急的项目一个接一个,从自我的角度来说,幸好没有沉浸于重复的工作。杂乱看了一些知识点,然而实际中没有使用,也不知该如何总结。毕竟觉得,只有真的用到了,研究可能的坑和真的踩了坑,才敢勉勉强强说会用了。
另外在看Modern PHP和Java的Spring相关的视频。Spring学习到一点点觉得新奇且有意思的玩意儿,只是现在还达不到一个实现好项目的水平。而Modern PHP这本书,不厚大概在200页左右,对一些PHP的编写规则和平时开发中可能未被使用到的特性做了说明,比如迭代器、PSR规范、trait、闭包等,是近期以来看到最有意思的一本书,目前仍在感兴趣的看阶段。
剩下的就是TP5框架,其实想仔细研究好长一段时间了,拾起总又放下。使用一个框架不仅仅在于会用,更要纠结应该怎么用,为什么要这么用,怎么用又更加又效率。学习一点点源码,就觉得获益匪浅,那么在融会贯通后,自己想实现定制的小框架,也绝非难事。
说说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
// +----------------------------------------------------------------------
定义常量APP_PATH,指向到application目录,业务逻辑代码都放置在这里。要注意的是,如果index.php更换了位置,__DIR__表示当前目录路径,所以这两句的位置都要根据实际来调整。定义常量后,加载thinkphp(tp5框架的核心目录)内的start.php,开始进入到tp5的启动逻辑。
send();
start.php内容比较简单,不过可以以此文件作为核心出发点,来分析功能。
事务的生命周期往往经历开始->过程->结束的过程。
开始:base.php建设业务代码运行前的配置环境.
过程:App::run()方法包含了对应用的初始化、路由的解析等处理,查找到实际要执行的控制器和方法,执行真正意义上的业务逻辑,返回Response对象.
结束:通过“过程”执行返回的Response对象,执行send()方法,实现数据与客户端的输出.可以把start.php作为tp5运行的核心中转站,来理解tp5各组件部分运行的关系。
来看看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系统 |
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);
至此,框架运行业务逻辑前的热身工作已经完成! 剩下要专注就是业务的实现
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;
}
把这段代码按功能点划分,拆解内容如下:
Response::send()负责控制向客户端输出。
写一段简单的输出代码,运行输出Hello字符串。
这个实现和平时接触的也许并不一样,为什么不直接用echo或exit来输出内容呢?
从代码的结构来说,不同的类各自负责不同的功能,一切皆对象,功能即可按对象来划分。因此Request对应请求、Response便对应了响应。这个从刚开始学Java的servlet老早就是这么实现的。
细致点有什么不同呢。Response封装了设置响应状态码、设置头部、输出数据格式检测、trace调试注入、配置缓存等功能,功能更加完善,也更加统一。