ThinkPHP6.0源码阅读笔记

1.系统服务

1.1系统服务主要是为被服务的类在使用之前初始化,做一些准备工作,例如,调用被服务的对象的静态方法,初始化被服务对象的静态属性等。

1.2系统服务分为自定义服务(app/service.php定义的服务)和内置系统服务,通过RegisterService类的init方法统一调用app的register方法,注册到app实例对象的services属性中,services属性中保存的是服务类对象的实例

1.3然后会使用BootService类的init方法统一调用app的bootService方法去调用之前注册好的服务类的boot方法(也就是说服务类注册完之后需要在类中添加boot方法),去为需要服务的类做一些初始化的准备工作。

1.4 所以在一个服务类中可以有register方法或者bind属性,其实就是为需要服务的对象注册别名,然后服务类中的boot方法,为被服务的类进行一些初始化工作,例如初始化静态属性等。

2.事件

2.1 通过app类的loadEvent方法加载事件,其实就是注册事件的别名到事件对象的bind属性中去,比如['AppInit'=> 'app/event/AppInit'],便于之后触发的时候调用,不用使用长长的类名,当然事件的触发也可以直接传入类的对象,tp会帮我们找到该对象所属的类,并把该事件对象实例当做参数,传递给监听者。

2.2 注册事件监听,例如['AppInit'=>['app\listener\ShowAppInit']],这里的AppInit就是被监听者,app\listener\ShowAppInit就是监听者,这里就会用到上面一步,注册的事件别名AppInit取出实际的被监听者,以被监听者作为键,监听者作为值,保存到事件对象的listener属性上

2.3 注册事件订阅者例如['app\subscribe\User'],首先会创建该类的对象,然后判断该类中是否实现了subscribe(手动订阅)方法,如果存在,则调用该方法订阅(例如 $event->listen('UserLogin', [$this,'onUserLogin']),这里就是手动订阅UserLogin用户登录事件),如果没有实现该方法,那么将会智能订阅,就是通过创建订阅者的实例,分析实例的对象的公有方法,规定,如果要订阅UserLogin事件,如果需要TP智能订阅,那么该订阅者的方法必须为onUserLogin,TP就是通过分析该实例的公有方法来完成智能订阅,同样,绑定到事件对象的listener属性上,和注册事件监听相同的是都是以被监听者为键,不过和注册事件监听不同的是,这是的值是一个数组['订阅者对象实例','订阅者的方法']

2.4这样当调用事件类的trigger方法触发事件时,如果是订阅模式,因为在listener属性上保存的是对象和方法,直接调用就可以了,如果是注册的事件监听,那么首先先创建监听者的对象实例,然后把它包装成数组[监听者对象.'handle'](这里采用事件监听必须是handle方法),到此,事件监听和订阅都被包装成[对象,方法]的数组,之后的调用也一样。

2.5 这里如果触发的时候传入的是事件的对象实例,那么该实例将会作为参数,传递到监听者或者订阅者的方法中,还有一点需要注意的是,如果在监听者和订阅者触发事件之后执行的的方法中返回了false,那么事件的触发就到此为止,不在往后执行监听者的方法了。

3.中间件

3.1中间件主要用于拦截或过滤应用的HTTP请求,并进行必要的业务处理。

3.2首先加载中间件,全局中间件定义的目录在app\middleware.php文件中,可以传入参数,例如[\app\http\middleware\Auth::class, 'admin'],中间件最后被包装成[[\app\http\middleware\Auth::class,'handle'],'admin']按照类型保存在Middleware类对象的queue属性上,该属性的保存的是个数组第一个键,就是类型,例如global表示全局中间件,这里可以看出中间件的处理方法必须是handle

3.3 然后把所有加载的中间件分装成闭包函数保存在Pipeline对象的pipes属性中,其保存的闭包函数类似于

//function ($request, $next) {
//    $response = handle($request, $next, $param);
//    return $response;
//}

其中$request是请求的对象,$next是下一个中间件的闭包函数,$param是配置闭包函数时传入的参数,例如上面的传入的admin参数,这里调用的handle,就是中间件类中的handle方法,源码中是call_user_func($call, $request, $next, $param),call是数组['根据配置的中间件类名实例化的对象','handle'].

3.4之后再把request请求的实例对象保存在Pipeline对象的passable属性上

3.5 然后通过array_reduce方法,把保存在Pipeline对象的pipes属性中的中间件的闭包函数数组化为单一的值,其实就是一层层的嵌套,array_reduce返回的也是一个闭包函数如下

A:function ($passable) use ($stack, $pipe) {

B:return $pipe($passable,

//倒数第二层中间件 function ($passable) use ($stack, $pipe) {

C: return $pipe($passable,

//倒数第一层中间件 function ($passable) use ($destination) {

return $destination($passable); //包含控制器操作的闭包

}) ); }; ); };

其中A行是$stack,表示的上次迭代的值(是一个嵌套的中间件闭包函数的结果),$pipe表示的是本次中间件的闭包函数

其中B可以看成是当前一次中间件的闭包函数也就是前面的函数,如下,其中$passable对应之前分装好的闭包函数的$request参数,$passable后的第二个参数,一层层嵌套的闭包可以看成是下一个闭包函数,也就是下面的$next参数,只不过,下一层闭包函数也有下一层,所以就形成了这种层层嵌套的关系

//function ($request, $next) {
//    $response = handle($request, $next, $param);
//    return $response;
//}

3.6之后调用array_reduce返回的闭包函数,也就是上面的A行,并传入之前Pipeline到对象的属性passable(之前保存的request请求对象的实例),然后首先他会执行B层,也就是最外层的中间件闭包函数,实际也就是执行在中间件中的handle方法,这里也就明白中间件中handle方法的两个参数,一个是request请求对象实例,另一个的是$next闭包函数(即该中间件的下一层中间件的闭包),所谓的前置中间件,就是把代码放在执行下一层中间件之前,后置就是放在之后,也就是$next($request)方法的前后

4.多应用

4.1 TP6.0正式版的多应用已经作为扩展安装,下面来分析一下多应用的实现。

4.2 首先是通过app对象的initialize方法,注册系统服务,即通过RegisterService类的init方法,注册系统服务(在vendor目录下的services.php文件下),其文件的内容包括

return array (
  0 => 'think\\app\\Service',
  1 => 'think\\trace\\Service',
);

其中第一个数组中的元素就是think-multi-app目录下的Service类,注册的时候会调用该类的register,然后这个Service就在register方法中,通过$this->app->middleware->unshift(MultiApp::class)这一行代码,把MultiApp类注册到中间件的开始位置

4.3TP框架内部在调用中间件的时候,就会调用到上面注册好的多应用的中间件类中的handle方法,handle方法中所作的事情第一步就是通过设置的参数或者pathinfo来分析访问的是哪个应用名称,找到应用名称之后就会根据你访问的应用名称设置一些属性值,包括设置应用的目录,命名空间,加载应用的配置文件(会覆盖全局配置文件中的参数,而且会到两个目录下寻找首先在应用的目录下寻找config目录,找不到,其次在全局config目录下看看有没有以应用名称命名的文件夹)加载应用下的公共文件common.php,加载应用目录下的事件,中间件等,做好这些之后,就会把pathinfo下面关于应用名称的路由去掉,最后在逐个执行该应用下的中间件

5 数据库

5.1 tp6.0的数据库ORM从和核心代码中分离出来,放在think-orm文件夹下,首先以如下语句做分析

Db::name('user')->where('id', 1)->find()

5.2 首先是采用Db类的门面模式,此时调用不存在的name静态方法会触发门面类的__callStatic方法,返回真正需要代理的类think\DbManager类,然后通过think\DbManager类,通过Container容器类实例化DbManager类,实例化的过程中会取出think\DbManager类的别名类,也就是真正实例化的类think\Db类,该类继承自DbManager类,因为该类中存在__make静态公有方法,所以容器类会直接调用该方法,返回think\Db类实例对象

5.3 think\Db类的__make方法中,主要做的就是实例化自身并且返回,设置config配置参数,设置缓存,设置log,event对象等

5.4 然后门面类通过返回的实例,调用call_user_func_array方法调用think\Db类中的name方法,因为Db类没有name方法,并且Db类继承自DbManager类,所以会触发DbManager类中的__call魔术方法,如下

public function __call($method, $args)
{
    //call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数
    return call_user_func_array([$this->connect(), $method], $args);
}

5.5 其中connect方法中所做的事,根据配置参数(这里是Mysql),实例化好连接器实例对象\think\db\connector\Mysql

并以mysql作为键名保存在该Db类的instances属性数组中,然后调用该连接器的getQueryClass方法,获取当前连接器类对应的Query类\think\db\Query并实例化该类,传入\think\db\connector\Mysql类上面实例化好的对象,保存在Query类的connection属性中后面会用到,最后connect方法返回实例化好的Query类

5.6之后通过call_user_func_array方法调用connect方法返回的Query类实例的name方法,因为该Query类又继承自BaseQuery抽象类,所以继承了父类的name方法,该name方法中做的事情也比较简单,直接把user变量保存在Query类实施对象的name属性上,并返回该实例对象(即可以链式调用)。

5.7之后分析一下where方法,因为BaseQuery类中引入了trait WhereQuery所以可以调用该特性下的where方法,这里的where方法分析比较复杂,总的来说,通过where方法分析之后得到['id','=',1]这样的一个数组,保存在Query对象的options属性数组中,代码:$this->options['where']['AND'][] = ['id','=',1];最后还是返回该Query对象

5.6 最后调用find方法,该方法存在于Query类的父类BaseQuery类中,该方法通过5.5分析的之前实例化该Query类时,通过构造函数保存在Query对象的connection属性上的\think\db\connector\Mysql连接器类的实例对象,通过调用该连接器对象的find的方法,又因该Mysql连接器类继承自PDOConnection类,所以调用的是继承自PDOConnection类中的find方法,代码如下:

 $this->connection->find($this)

5.7 调用find方法,传入了自身,即Query对象PDOConnection的find方法部分代码如下,调用自身的query方法,传入Query类的对象,还有一个闭包函数

// 执行查询
$resultSet = $this->query($query, function ($query) {
    return $this->builder->select($query, true);
});

5.8 这个query方法,首先就是调用传入的query对象的方法分析查询表达式,就是把之前保存的Query对象options属性中的参数分析出来,分析结果如下,包括表名之前保存在Query对象name属性中的user,和之前保存在Query对象options属性上的where条件,同时把分析出来的结果覆盖Query对象options属性上。

5.9 然后就是调用传入的闭包函数,该闭包函数又调用保存在Mysql对象上的builder属性上的think\db\builder\Mysql对象select方法如上5.7所示代码,还是要传入Query对象,该方法借由query对象,获取之前分析出来的options数组,如上5.8图所示的数组构造出sql语句出来,例如:SELECT * FROM `weibo_user` WHERE `id` = :ThinkBind_1_1138738238_ LIMIT 1,这里使用了PDO中的预处理,把id暂时以 :ThinkBind_1_1138738238_ 代替,同时把ThinkBind_1_1159212858_保存在Query对象的bind属性上,之后用于绑定值,如下图所示,以绑定的变量为键,保存了两个值,第一个1,是id=1的值1,第二个1,是id的类型是int类型,用于之后调用PDO的bindValue传入的第三个参数,值的类型:

5.10 之后就是创建PDO连接,保存PDO连接的资源,然后通过代码$this->PDOStatement = $this->linkID->prepare($sql);调用PDO的预处理,发送5.9分析出来的sql语句

5.11 之后调用PDO的bindValue,绑定参数,也就是用到之前保存在Query对象上的bind属性的值,即5.9所示的图

5.12 在通过$this->PDOStatement->execute();执行查询,返回保存在connector\Mysql上的$PDOStatement属性

5.13 最后通过调用PDOStatement的fetchAll方法,获取结果集,并返回,当然也包括缓存结果集等操作,代码: $result = $this->PDOStatement->fetchAll($this->fetchType);

5.14 这里对于ThinkPHP的数据库操作以一个例子作为说明,看起来步骤相当多,其实已经省略了好多分析的复杂过程,源码的实现比分析的的要复杂的多,但是也可以通过此分析对TP中的数据库操作有个大概的了解

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值