ThinkPHP5
目录
这个之前忘发了,写得比较混乱
基础
命名规范
使用驼峰法,文件和类首字母大写,方法首字母小写
函数/配置/数据表和字段参数的命名使用小写字母和下划线
常量以大写字母和下划线命名
应用类库的根命名空间统一为app
目录结构
略
架构
整体架构
- 入口文件
- 应用
- 管理系统架构及生命周期的对象
- 模块
- 控制器
- 操作
- 模型
- 视图
- 行为
生命周期
- 入口文件:定义项目路径,加载框架引导文件,定义一些常量,一般是public/index.php
- 框架引导文件:start.php->include base.php,定义系统/环境常量,注册自动加载和错误处理机制,加载配置文件,App:run()->send()
- 应用初始化:加载各种各样的必需类/配置
- URL访问检测:
自动加载
splautoload->autoload()->findfile->inclue
在findfile中,一个类库的自动加载检测顺序为:
- 是否定义类库映射;
PSR-4
PSR-0

URL访问检测
pathinfo访问模式
http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值...]
兼容访问模式
http://serverName/index.php(或者其它应用入口文件)?s=/模块/控制器/操作/[参数名/参数值...]
请求过程
- App:run()先实例化一个Request类,并初始化配置(比如Request的filter),绑定模块/控制器,加载语言包,执行路由检查,debug记录(如果),最后调用self::exec,参数为dispatch(记录请求的模块,控制器,操作)
- App::exec检查了模块,控制器,操作以后调用self::invokeMethod($call, $vars);
- 在invokeMethod处,thinkphp设置反射类来执行

上面的是5.0的过程,5.1把exec放到别的类里面了


路由检查
检查配置参数是否为强路由,url路由的方式,解析模块/控制器/操作
tp路由定义
定义方式 | 定义格式 |
---|---|
方式1:路由到模块/控制器 | ‘[模块/控制器/操作]?额外参数1=值1&额外参数2=值2…’ |
方式2:路由到重定向地址 | ‘外部地址’(默认301重定向) 或者 [‘外部地址’,‘重定向代码’] |
方式3:路由到控制器的方法 | ‘@[模块/控制器/]操作’ |
方式4:路由到类的方法 | ‘\完整的命名空间类::静态方法’ 或者 ‘\完整的命名空间类@动态方法’ |
方式5:路由到闭包函数 | 闭包函数定义(支持参数传入) |
应用调度
先根据dispatch分类,然后除了redict和response之外,都会通过反射类来实现调用模块/控制器/操作

使用反射类来完成
Db:操作数据库
- 配置文件为database.php
连接
连接数据库的核心为配置连接信息
数据库的配置文件有多种定义方式。
- 配置文件
在database.php里面配置
有一些连接参数,比如ATTR_EMULATE_PREPARES,thinkphp将其设置为false
如果为true,PDO会模拟预编译并执行,可以执行多句
设置成false以后,会使用数据库附带的真正的预编译模式,就不会有堆叠注入的洞
- 方法配置
Db::connect([配置信息]); // 或者使用字符串的形式 数据库类型://用户名:密码@数据库地址:数据库端口/数据库名#字符集 Db::connect('mysql://root:[email protected]:3306/thinkphp#utf8');
- 模型类
//在模型里单独设置数据库连接信息 namespace app\index\model; use think\Model; class User extends Model { protected $connection = [配置参数]; //或者 protected $connection = 'mysql://root:[email protected]:3306/thinkphp#utf8'; }
基本使用
- Db::query 查询
- Db::execute 写入
- db()助手函数
源码分析
Db::query()触发Db::__callStatic(),实例化 Query 并传入通过工厂模式获取的Mysql实例,而Mysql连接器继承了Connection, 调用 query(),所以实际上是调用了Connection->query()
例如Db::table(‘users’)->insert([‘username’ => $username]);
先走到call_user_func_array

此处new $class实例化了Mysql连接器,并保存在self::$instance里,配置相同的Mysql连接器存在$instance中为同一个(单例模式)

接下来会调用call_user_func_array([\think\db\connector\Mysql,table],$params)
但是Mysql类并没有table方法,于是到父类Connection里面找,Connection为一个抽象类,没找到,于是调用__call()方法

此处getQuery将会实例化一个\think\db\Query

在Query的construct方法之后,就进入到了Query的table方法(我们最开始使用的就是table方法)

table方法最后return $this(实现链式操作),然后我们跳到之后调用的insert方法,此处会调用builder

此处的工厂模式
- 抽象基类:Connection类
- 继承抽象基类的子类:实现基类中的抽象方法,例如Mysql类等
- 工厂类:用以实例化所有对应的子类,Db类
总结
- Db::query()触发Db::__callStatic()
- 实例化一个Query,在Query的__construct函数中,解析config,并按照工厂模式获取一个单例数据库连接实例,比如Mysql
- Mysql连接器继承了Connection, 调用query(),所以实际上是调用了Connection->query()
- 在query的过程中,调用builder->query,生成真正的sql语句
- 返回connection->query,执行真正的sql语句
- db()助手函数实例化一个Db类,其他不变
(以上部分是自己看代码分析的,不一定准确)
Request:获取请求信息
View:视图渲染
<?php namespace app\index\controller; use think\Controller; class Index extends Controller { public function index($name) { $this->assign('name',$name); return $this->fetch(); } }
assign把参数存到view->data里
- View->fetch 刷新缓冲区,然后调用Think->faetch,最后进行过滤
- Think->fetch 找到模板文件,然后调用Template->fetch
- Template->fetch 先检查有无缓存,如果没有就新开一个缓存文件进行模板编译,然后进入$this->storage->read($cacheFile, $this->data);这个方法直接extract变量覆盖然后include缓存文件,即可生成返回内容
生成的缓存模板文件如下
<?php if (!defined('THINK_PATH')) exit(); /*a:1:{s:93:"C:\_code\thinkphp_vuln\thinkphp_5.0.10_full\public/../application/index\view\index\index.html";i:1605595395;}*/ ?> <h1>hello <?php echo $name; ?></h1>