ThinkPHP5

这个之前忘发了,写得比较混乱

基础

命名规范

使用驼峰法,文件和类首字母大写,方法首字母小写

函数/配置/数据表和字段参数的命名使用小写字母和下划线

常量以大写字母和下划线命名

应用类库的根命名空间统一为app

目录结构

架构

整体架构

  • 入口文件
  • 应用
    • 管理系统架构及生命周期的对象
  • 模块
  • 控制器
  • 操作
  • 模型
  • 视图
  • 行为

生命周期

  • 入口文件:定义项目路径,加载框架引导文件,定义一些常量,一般是public/index.php
  • 框架引导文件:start.php->include base.php,定义系统/环境常量,注册自动加载和错误处理机制,加载配置文件,App:run()->send()
  • 应用初始化:加载各种各样的必需类/配置
  • URL访问检测:

自动加载

php的自动加载机制

splautoload->autoload()->findfile->inclue

在findfile中,一个类库的自动加载检测顺序为:

  1. 是否定义类库映射;
  2. PSR-4
  3. 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>

发表评论

邮箱地址不会被公开。 必填项已用*标注