基于thinkphp3.23源代码剖析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/d13579246810b/article/details/78139583
1、框架的学习思路:引入并配置框架,MVC三层的书写与命名,操作数据库,各种扩展包。


2、什么是框架?和类库的区别?类库:供我们调用,框架:组织controller。


3、tp是mvc模式框架及单入口文件。


4、分析tp3.23核心版:thinkphp/目录:
common:常用函数
conf:配置
Lang:语言包
Library:一些类库,包
(3.0是core,Behavior,Dirver)
Model:
Tpl:


5、核心功能:Action控制器类(3.2.没有) Model模型类,View类
Think核心类
App:应用管理
Dipspatcher.class.php,路由器


6、库供我们调用,框架调用我们的代码。


7、页面开始显示流程:
       单入口文件:index.php=>ThinkpHP.php
ThinkPhp.php定义了一些常用的系统常量,加载了核心类Think,并调用了里面的start()


在Think.class.php里注册autoload方法,设定错误和异常处理,通过数组加载Mode\common.php里的常用配置(大概实在113行创建应用目录,具体以后分析)。运行App:run()


在APP.class.php里,很重要的一个方法:Dispatcher::dispatch();实现对URL的调度。(以后分析)并定义了模块,控制器,方法的常量。


App::exec(),89行实例化,127行一个映射,182行调用controller方法,调用了reflectionclass,reflectionmethod方法(具体没有研究)。


空方法的执行流程:
按照上面的流程,到达了App.class.php执行了exec方法,里的约莫96行的controller()方法
这个方法在这个方法在common/functions下,用来实例化一个controller
然后执行invokeAction函数,约莫134行调用ReflectionMethod,invoke,此时,如果调用方法是不存在的,就进入controller.class.php里的__call方法里调用了_empty()方法。


前置和后置操作:
约莫137行开始,有类似于_before_.$action之类的,这段便是执行前置操作,同理后置操作在195行。


空控制器:tp3.23没有hack_moudel(我找不到),只有A(empty)


使用A()函数可以跨模块调用函数。具体可以分析A函数源码。
A()函数中使用了注册模式实现单例:利用一个数组存储对象,当需要实例化对象时,先实例化,并把该对象存储在数组中,下次再次实例化的时候,直接return数组中的对象。


看了一下A函数的源码,貌似不可以调用别的项目的内容。
此外,tp3.2没有分组。


8、model增加一则数据
有两种方式:
orm和activerecords
orm(数据关系映射):$data = array(键=>值,键=>值),数组的键与与表的列对应,一个数组与表的一行数据对应。


activerecords:对象属性与表的列对应起来,操作对象属性就是操作列的数值。




9:thinkphp是如何链接数据库的?粗略跟踪一下源代码:流程如下:当在controller里使用D()方法的时候,D()方法里面调用parse_res_name()加载model类,然后在D方法里实例化,实例化就调用Model类里的__controuct方法,然后再调用本类里的db方法,此db方法再次调用Db::getInstance()里的方法,在此实例化一个类:Think\Db\Driver\Mysql,去查看Driver.class.php,发现__contruct方法只是初始化一些值,那么问题来了,在哪里链接数据库?继续跟踪发现,在Model.class.php里_checkTableInfo方法后调用了flush方法,里面有getFields方法,继续跟踪到mysql.class.php里,里面有一个initConnect方法,在Driver.class.php里有initConnect方法初始化数据库的链接。


10、tp中model类是在flush里获取字段信息,148行左右$this->fields赋值。


11、model类里的add方法:如果使用crm方式传递data,则执行_facade方法,_facade方法作用是检测数据字段的合法性,首先获取他的字段信息,然后遍历,如果不存在则unset掉,然后调用_parseType方法强制转换字段保证安全。之后执行_parseOptions方法,(这个方法具体流程没有搞清楚),然后插入数据库,返回自增型id。


12、Activerecords其实和orm差不多,当我们设置一个属性的时候,就会调用__set魔术方法,设置了$data属性,之后再进行赋值给$data。剩下的操作就和11里的一样。


13、model类有一个非常重要的属性,$data,是一个与表映射后的数组,做增删改查时数组在里面运转。


14、model类里的save方法,可以传option=array(user_id=>1)也可以在data里直接写出data的主键值,分析代码如果option[where]为空的话,将使$where[pk] = $data[pk],然后option[where] = $where,不过也有一个细微的区别,就是这样做$where是数组,而另外一个是键值对的字符串,这点区别具体看一下db->update()方法。


15、options是存放sql查询条件的。


16、select可以传什么参数?查看源代码可以得知,如果是数字或者是字符串,就可以塞到$where[pk]里面,如果是字符串并且有逗号间隔的,就加个‘IN’,


17、连贯操作:涉及到__call()魔术方法,分别把order,group存放到model属性$this->options属性里面,当调用__parseOptions方法时,有一句$options =  array_merge($this->options,$options);根据这句代码可以得知,如果$options与$this->options重合,就会覆盖掉前面的。不过发现where.limit方法都另外自己写了。


18、_parseOptions源代码详解:合并$options和option属性,获取表名,模型名,字段检测:如果传入数组如where('email'=>'[email protected]'),则调用_parseType()方法设置字段,如果非法字段,就unset掉,最后返回$options


19、仔细分析model类始末:从D函数说起,D函数内调用parse_res_name函数,parse_res_name函数解析model,形成例如Home\Model\UserModel的值返回,然后实例化new,这就调用了Model函数的__construct方法,赋值给$this->name后,调用$this->db初始化数据库,然后调用Db::getInstance()方法,实例化,注意,此时还没有connet数据库,需要调用 _checkTableInfo(方法,如果配置文件里的DB_FIELDS_CACHE设置为true,并且是上线模式,就不会每次都调用flush函数,也可以在model里设置fields属性,这样也同样可以不调用flush函数,如果调用flush函数,就会通过db->getFields函数取得内容,在整合到$this->fields里,之后在_facade的方法里可以使用。


20、php中:回调函数不止可以是简单函数,还可以是对象的方法,包括静态类方法。如where函数里面有一句:array_map(array($this->db,'escapeString'),$parse)就是对$parse使用Db目录下的escapeString方法。


21、可以用M()实例化一个空模型,加上连贯操作table()指定表名。


22、遇到一个问题;搞不懂preg_replace_callback("/__([A-Z0-9_-]+)__/sU"里面的正则后面的sU是什么意思。


23、复杂的sql查询可以使用自己手动写的sql语句。查询可以用query,增删改可以用execute。


24、自动验证的源码运行流程:先从I函数说起,如果传入I(post.)则返回所有数据,由于array_map函数没有递归调用的,所以tp框架自己写了一个array_map_recursive函数递归检验数据。默认htmlspecialchars 。create方法先获得数据,自动验证调用autoValidation()方法,先获取验证属性,再调用_validationField()方法,里面再调用_validationFieldItem()方法,然后在各种调用。自己分析。最后调用自己的function时的文件是在哪里加载的,这个到时候在找一找。这个在配置文件common/function.php里,因为会默认加载,当然,你写在其它文件也是一样的,前提是它也加载出来。我试验了一下,在命名空间里是可以加载全局函数的。


25、自动完成的源码运行流程:实例化一个对象(具体查看19),autoOperation()方法前,将接收到的post数据通过if($this->autoCheckFields) 下的一个foreach完成,完成数据非法字段的过滤,其中$fields来源有两种:第一种看19点,第二种自己在model方法里写$fields属性。也就是说此时自动填充的数据库字段尚未进入data,直到调用autoOperation后,才进入data并返回。


26、字段映射的源代码运行流程:调用parseFieldsMap()进行一个foreach循环。如果在使用find(1)进行查询的时候,将在_read_data方法里进行数据处理,设置'READ_DATA_MAP'=>true才能使读出来的数据是表单的键值而不是数据库的。当然,直接调用parseFieldsMap方法也是没有问题的。


27、查看controller.class.php时,想追踪源代码是如何写入Log文件的,发现一些很奇怪的问题得不到解决。首先调用Hook::listen()方法,了解了有log记录这一回事,然后查看log记录,从源代码运行开始查看,首先在App.class.php里的App::init()方法里面下设置exit,果然发现log记录只有app_init,其他没有了,我继续追踪,不断设置断点,Dispatcher.class.php的157行这句C('LOG_PATH',  realpath(LOG_PATH).'/'.MODULE_NAME.'/');前后设置exit,发现前无记录,后有记录,可是这句话就只是一个设置值而已,至于为什么可以执行??搞不懂。它是如何,什么时候调用LOG::save()???




28、模版输出$this->display()发生了什么??查看Controller的_construct()方法,实例话视图类,调用了think.class下的instance方法,new了一个view类,display方法即是调用view类里的display方法,此方法里调用了fetch()方法,然后调用parseTemplate方法分析出你想调用的模版文件(具体分析一下parseTemplate方法,这里调用了getThemePath方法,这个方法返回主题模版路径,一般来说是当前模块下的view文档,当然可以设置如调用theme方法,可以在view里再分具体查看getThemePath方法的内容,然后将模块名/view/controller名/方法名合并返回),然后进入视图解析标签,调用Hook::listen方法,在此方法里,关键看foreach这个循环,调用了exec()这个方法,查看self::$tags,由于传参是view_parse,所以exec调用了Behavior\ParseTemplateBehavior这个类的run方法,由于是debug模式,所以进入到31行的else语句,实例化一个Template类,并调用了fetch方法,然后再调用loadTemplate这个方法,在约莫98行根据模版名定位文件,此时才发现之所以缓存模版名字唯一且很乱是因为他将你自己的视图名使用了md5加密,然后编译模版内容compiler方法(具体到时候再分析),然后回到loadTemplate方法里,Storage::put调用了这个函数。发现里面很简单,只有两个方法,其中有一个魔术方法__callstatic,具体测试发现connect方法已经被运行过,于是返回之前的代码,在Think.class下约莫38行,Storage::connect(STORAGE_TYPE);有一个文件初始化方式,$handler其实是一个Think\\Storage\\Driver\file.class.php下的一个类,查看put方法,发现在此处创建缓存文件,然后loadTemplate方法运行完毕,返回到fetch方法,loadTemplate方法返回缓存文件。然后再调用Storage::load,即file类里的load方法,将此文件包含进来,然后在调用templateContentReplace包含特殊变量,fetch函数结束,返回content,回到display()方法里,调用render方法,输出模版内容。




29、具体查看<if condition=""></else></if>和{$title}的具体过程。即首先调用$this->assign()方法,赋值给$this->tVal[],然后调用$this->display(),参考28,即调用了view类的display方法,经过Hook,然后调用Template.class.php里的fetch方法,然后是loadTemplate方法,然后调用compiler方法,具体分析这个compiler方法,调用了$this->parse方法,然后来到约莫192行foreach ($tagLibs as $tag){
            $this->parseTagLib($tag,$content,true);
        }执行parseTagLib方法,然后就实例化Think\\Template\TagLib\\CX.class.php,就进行一个大的foreach循环,涵盖掉$this->tags里面的标签,然后具体分析<if>:使用正则分析出来的是/<if\s([^>]*)>(.*?)<\/if(\s*?)>/is这样一个东西,然后调用Taglib.class.php里的parseXmlAttr方法,这个方法具体作用是通过生产xml对象安全解析,取出数组,返回,然后再调用CX.class.php下的_if()方法,然后在调用parseCondition方法,与$this->comparison里的内容转换,如:gt:>,然后再识别数组如a.leg等,然后在返回(同理else也是在大的foreach里进行,这里就不多说),然后回到parse方法里/(\{)([^\d\w\s\{\}].+?)(\})/is通过此正则识别{$title}、U函数的过程,然后变量调用parseVar方法,分析出$name,再约莫573行返回php的输出,至此,parse函数运行完毕,返回compiler方法里,然后返回loadTemplate方法里,然后调用file.class.php方法里的put函数,创建模版文件,然后返回fetch函数里,然后调用file.class.php类里的load方法,通过extract方法引入$this->tVal[]里的变量,并include进来,所以才会出现模版文件里的变量变成变量值,然后view类的fetch函数回到view类的display函数里,然后调用render函数输出模版。


30、详细分析<for></for>和<foreach></foreach>,其中,<for>可以参考29,通过parseXmlAttr方法,解析出数组,然后调用_for()方法,其中值得注意的是源代码中使用了rand防止函数嵌套引起的变量冲突。<foreach></foreach>的源代码中,content变量又重新调用了Template.class.php里的parse方法,解析模版和数组变量,值得注意的是在最后语句调用parseTag方法,里再调用parseVar将g.goods_id解析成g[goods_id],然后返回_foreach函数里拼接返回。


31、{name|strtoupper}{time|date="y-m-s",###}变量调节器,即手册上的使用函数,具体与普通变量类似,可以参考29点的{$title},唯一不同的地方是parse方法结束前调用的parseTag方法,然后进入到parsevar方法,然后将变量名与方法分开,方法放到$varArray,变量名放到$var里,然后调用parseVarFunction方法,将函数与变量结合再返回。




32、分析<include file="public/head"/>的源代码:其他的和28,29类似,具体的到Template.class.php下的parse方法,调用了$this->parseInclude方法,正则分析出include 里的内容,并调用$this->parseIncludeItem方法,在里面调用parseTemplateName方法,如果不是.html的绝对路径,那就并调用T函数,在T函数里,分析出具体的路径返回,parseTemplateName里获取内容并返回,然后再次对包含文件进行分析(包含文件可能也有<include />),然后返回>parseInclude代替<include>标签。


33、模版布局:如果在配置项设置全局模版布局,则结合28,29,在loadTemplate方法里就进行了判断,大致是获取view下的路径,用内容取代模版布局里的{__CONTENT__},然后再解析include标签,如果是使用手册里的第二种方法,则是在parseInclude里的parseLayout方法就用include方法取代,具体是用正则,第三种则是调用function.php里的loadout方法,原理一样。


34、缓存:查看S函数,如果设置缓存的话,调用Cache类的getInstance函数,然后调用connect方法,然后实例化File方法,调用init方法创建缓存目录,然后回到S函数,调用File.class.php的set方法,然后调用filename函数,可以发现只是根据名字直接使用md5加密,这就导致一个问题,可能会被覆盖,然后返回路径名,然后将数据变成一个可存储的字节流,写进filename函数返回的路径名,如果设置了‘length’属性,则调用了父类Cache.class.php的方法,如果是file存储形式,则调用F函数,所以_handle中的key值为file的值为F的数组,如果是第一次调用F函数,什么都没有,就返回false,然后再次调用F函数,进行存储,在runtime/data目录下可以看到think_queue.php。


35、分析一下直接输出<literal></literal>,系统变量{$think.get.id}以及模版常量替换,比如__PUBLIC__的源代码:首先说一下<literal></literal>在Template.class.php里的compiler方法调用了$this->parse()方法,然后约莫164行调用了parseLiteral方法,将内容存储在$this->literal数组里面,并将原文替换成<!--###literal{$i}###-->$i代表$this->literal数组的索引,然后parse方法结束后,回到compiler方法,利用正则分析出$i,调用restoreLiteral方法,将$this->literal[$i]里的替换即可。接下来是系统变量,在parse方法的最后调用了parseTag方法,调用了parseVar方法,分析出以Think变量开头的,调用parseThinkVar方法,然后拆分,替换。而常量替换是在compiler方法之后,调用hook::listen(),里面调用ContentReplaceBehavior.class.php里的方法。从里面可以看到,你可以自定义模版常量只需在配置文件设置C('TMPL_PARSE_STRING')即可。


36、由分析C函数可以得知在Think.class.php里foreach ($mode['config'] as $key=>$file){
              is_numeric($key)?C(load_config($file)):C($key,load_config($file));
          }加载配置项。


37、<taglib name="" />的源代码:在Template.class.php里,的parse函数里,约莫172行获取需要引入的标签库列表,调用了getIncludeTagLib方法,然后正则识别/<taglib\s(.+?)(\s*?)\/>\W/is ,然后调用parseXmlAttrs这个方法返回解析的数组,并存储在$this->tagLib属性里,然后调用parseTagLib这个方法,然后在这个方法里,实例化了Think\\Template\TagLib\\刚刚存储在tagLib属性里的类,这个类必须继承自Taglib类,因为要调用Taglib里的getTags方法,然后正则解析/<Html:radio\s([^>]*)\/(\s*?)>/is ,然后进入parseXmlTag方法,再调用_radio()方法,由此可以得知如何自己写一个标签扩展库,根据源码一目了然。另外,由源代码可以知道可以在配置文件设置TAGLIB_PRE_LOAD,这样就不用写Taglib标签,当然,还可以在内置标签TAGLIB_BUILD_IN配置文件里设置。


38、有空再整理一下model类的调用,与前面的具体分析。比如table(),与M方法的调用。


39、ajaxReturn源代码:其实就是包装了一层ajax返回前端的方法,其中留意一下JSONP,默认是callback的回调方法,并且是以json格式返回。




40、多语言支持:先谈如何实现,在应用的配置目录下定义文件tags.php,return array(
'app_begin' => array('Behavior\CheckLangBehavior'),
);,然后,配置项里设置return array(
//'配置项'=>'配置值'
    'LANG_SWITCH_ON' => true,
    'LANG_AUTO_DETECT' => true,
    'LANG_LIST' => 'zh-cn,en-us',
    'VAR_LANGUAGE' => 'lang',
);最后,浏览器输入lang/zh-cn即可。


再来说说源代码:在Think.class.php下,加载模式行为定义和加载应用行为定义,使得Hook下的self::$tags有内容,其中app_begin=>CheckLangBehavior,每次调用Hook:Listen('app_begin')的时候(在App.class.php下的run里),就会进入Hook类调用CheckLangBehavior,当设置了参数后就会从浏览器里读取(当然也有其他方式)语言,然后再使用L函数加载语言包,这里谈一下L函数,如果你是字符串且第二个参数为空,则获取语言内容,也可以设置,和支持传递变量形式,如果传进去的是第一个参数是数组,则批量导入,就是CheckLangBehavior函数里L(include)的作用,即包含的语言包文件是数组。




41、app_begin里的ReadHtmlCacheBehavior看一下。


42、面向切面编程:就是给某些参数,经历过一系列过程,得到结果,在这个过程里,划分每个细小的步骤,每当要运行到一定地方的时候,就调用Hook查看是否需要某个函数,就像一些关卡一样。如Hook::listen(view_parse).


43、使用use引入命名空间是完全限定名称的。


44、命名空间:Tp采用命名空间定义和加载类库文件,有效的解决了各个模块之间的冲突问题,并且实现了更高效的类库自动加载机制,并且根命名空间,模块中的命名空间都是可以自动加载。例如:在ThinkPHP/Libray/下的目录,或者自己在其下创建,都可以视为根目录,比如我创建了ThinkPhp/Library/My/test.class.php,就会自动加载。查看源代码:在Think.class.php下,注册了AUTOLOAD方法:spl_autoload_register('Think\Think::autoload');追踪去到autoload方法里,可以看到第一个elseif下的判断,如果是library下的就可以自动定位,还可以在配置项里设置AUTOLOAD_NAMESPACE注册新的命名空间,如果没有注册,则一APP_PATH命名,然后在include进来,当然,也可以使用类库映射Think\Think::addMap('Think\Log',THINK_PATH.'Think\Log.php');查看addMap方法,其实就是将内容塞进一个$_map的数组里,再回头看看autoload方法,可以发现类库的加载是在最前面的。这也印证了手册上的自动加载优先级。接下来再来看看手动加载第三方类库,即方法import,由源代码str_replace可以知道,如果想替换比如实现加载stu.info.php,写的时候必须import("My.stu#info");否则会失败。如果是@就会加载当前模块类库,也可以是第三方类库包,当然,你也可以手动传值,要加载的起始目录(第二个参数),和第三个后缀参数,如果类不存在,则导入类库文件,调用require_cache,在里面调用file_exists_case判断文件是否存在,存在则返回,并且下次可以先判断而不用再次引入。当然,手册上介绍的vendor方法,其实也是调用了import方法,简单的包装一下而已。


45、几个错误注册函数需深入了解一下。


46、配置文件的引入:
首先$mode 引进了E:\wnmp\nginx\html\tptest2\ThinkPHP/Mode/common.php这个文件的内容,打印$mode,可以发现存在的键名分别是:config,alias,core,tags。加载一些核心文件以及ThinkPHP/Common/functions.php,./Common/Common/function.php,然后通过$mode[config]加载惯例配置以及应用配置,由于使用普通模式,模式配置就没有进行,通过add_Map方法加载$mode[alias],然后加载应用别名./Common/Conf/alias.php,通过Hook::import加载模式行为定义,然后加载应用行为定义(即自己可以在./Common/Conf/tags.php下定义自己的行为定义),加载框架底层语言包,默认是zh-ch,然后是加载状态配置ThinkPHP/Conf/debug.php,然后就是可以根据APP_STATUS切换状态配置文件,然后进入App::init()里load_ext_file方法进行扩展配置,然后在 URL调度Dispatcher::dispatch()里分析出模块名,加载MODULE/Conf/config.php,以及应用模式和当前应用状态的配置文件,然后加载别名,tags,模块函数,扩展配置文件,




加载的配置项:
惯例配置:ThinkPHP/Conf/convention.php
应用配置:Application/Common/Conf/config.php
模式配置:Application/Common/Conf/config_应用模式名称.php
调试配置:ThinkPHP/Conf/debug.php   Application/Common/Conf/debug.php
状态配置:APP_STATUS
模块配置:1
读取配置:1
动态配置:1
扩展配置:1




47、简单看一下URL调度的代码:Dispatcher::dispatch(),我的路径是:http://localhost/tptest2/index.php/home/user/text/id/6判断是否具有兼容模式参数,与普通模式同一到$_SERVER['PATH_INFO']=/home/user/text/id/6里面,由于没有开启子域名部署,所以先跳过这段代码,将其变成数组,并以'/'划分成两个,$_SERVER['PATH_INFO']就变成了除模块名外的剩余部分:user/text/id/6,并且$_GET[m] = home,然后通过getModule获得常量MODULE_NAME,即模块名,并删除$_GET[m]的内容,然后定义一些关于模块名的目录,加载关于模块名的配置文件,然后,获得当前应用的URL地址,__APP__:/tptest2/index.php,定义模块URL常量__MODULE__:/tptest2/index.php/Home,然后判断如果是非法后缀,则显示404,然后将$_SERVER['PATH_INFO']变成数组,存储在$paths里:Array ( [0] => user [1] => text [2] => id [3] => 6 ),然后$GET[C],$GET[A]取得内容,将剩余的解析为参数,合并到$_GET里,然后,获取控制器,方法名,并奖其$_GET里的内容删除,再定义其相关的url路径,所以$_GET的内容只有参数。


48、查看一些匿名函数。。




49、系统流程:遇到一个问题:就是查看trace方法的时候,$recode是false,但是查看log文件的时候却记录下来了。
问题2:app_init的绑定函数是什么作用??lite.php文件??
问题3:有空看看session,cookie函数。


按照手册上的架构模块下的系统流程单元:以下是其中一些补充与细化。


终于搞清楚了log函数如何运转:遇到一点小问题:在log.class.php里,在record方法里,将$message打印出来,发现有改变,追踪了一下,发现如果是debug模式下,就会读取调试配置文件,就在CONF_PATH.'debug'.CONF_EXT这个文件里,发现LOG_LEVEL比convention里面的更多。


运行到app.class.php下,调用了Hook::listen()函数,追踪过去,发现调用了G函数,和trace 函数,查看函数里面,可以发现G函数作用是记录时间和内存,而trace函数则是调用了think:/think/trace方法,里面调用了Log::record()方法,再查看此方法,因为为调试模式,所以像INFO::这些都记录下来,放到一个数组里,self::log[],在此,遇到一个问题,哪里会记录这个数组log呢?追踪了好久都不行,直到后来,研究tp的流程的时候,才发现了register_shutdown_function这个函数,在页面结束的时候会调用里面的方法,发现里面真的有个Log::save(),于是所有的问题都豁然开朗。再看log.class.php里的save方法,发现它实例化一个类:Think\\Log\\Driver\\file,并调用了里面的方法write(),在Think\\Log\\Driver\\file里的write方法发现一个很有趣的函数:error_log:如果你的文件超出大小,就会更改原文件的名字并调用此函数继续发送。


再来看看app::exec()方法,如果设置类绑定到类,则按照平时的访问路径是http://localhost/tptest2/index.php/home/index/index,访问的是home/controller/index/index.class.php里的run(默认的)方法,否则,就调用controller方法实例化,然后调用invokeAction方法,发现了很多很有趣的新函数,首先实例化ReflectionMethod获得这个方法的具体信息,然后实例化ReflectionClass获得这个类的具体信息,其中的一些前置操作,后置操作就不多说,调用getNumberOfParameters判断参数个数并将其归到$vars,然后调用getParameters获取参数名字,取得参数,执行invokeArgs,如果没有参数,就执行invoke。




再来看一下静态缓存,即app_begin切面的ReadHtmlCacheBehavior,首先查看手册,按照规则设置好,然后就回到 Behavior\ReadHtmlCacheBehavior里查看源代码,首先检查是否开启静态缓存,如果开启了,就跳转到requireHtmlCache方法里,然后读取静态规则:C('HTML_CACHE_RULES'),然后正则分析静态规则,定义常量HTML_FILE_NAME,然后回到run函数里,调用checkHTMLCache检查静态页面是否有效,通过Think\View')->parseTemplate()分析出模版文件,再调用file.class.php里的get函数返回时间比较,以及与现在比较,如果静态文件有效,直接返回true,通过Storage::read调用file.class.php里的read方法获取内容然后直接退出。查看切面view_filter,可以发现有一个类WriteHtmlCacheBehavior,原来才发现,如果是有模版的才会自动生产静态缓存的目录,即调用的方法下有display方法,view目录下有文件。


再来看一下app_end的绑定行为,Behavior\ShowPageTraceBehavior,可以结合手册调试章节下的页面trace和trace方法,要设置SHOW_PAGE_TRACE为true,回到源代码,无非就是将信息汇总,查看thiink.class.php:trace方法,可以设置$_trace[$level]所要记录的信息,然后通过trace方法再次取回放到$debug里面,然后赋值,如果开启了PAGE_TRACE_SAVE,就会保存到log里,使用到的函数是error_log,上文有所提及。然后调用trace页面模版赋值。


50、有空可以看一下手册下的调试板块。


51、cookie、session函数的源码分析:如果传入的$name是数组,则进行初始化,注意,要开启SESSION_AUTO_START才能使用。其它的参考手册。cookie函数发现源代码里两个问题:1、null好像不会删除所有的cookie。2、存储数组是通过转换成think:json进行存储的。即本质上还是字符串。


52、D方法:


53、M方法:


54、发现一个问题:__autoload函数是要在全局空间里才有效。即不能在一个有命名空间的文件里声明__autoload,否则这个函数不调用。


55、详解第19点调用D方法或者M方法,实例化自定义的model,由于继承关系,所以基类model也会被调用,__construct里调用db方法,然后调用Db::getInstance获得实例,里面实例化Think\\Db\\Driver\\mysql,然后在进行字段检查,如果没有定义或者不设置缓存,则调用flush方法,里面调用$this->db->getFields方法,getFields方法里,使用了pdo对show columns from db.table进行查询(调用了query方法,)query方法里调用了initConnect,在基类Driver调用了initConnect后再调用了connect方法进行数据库链接(也就是说,取得mysql的实例并没有链接数据库,用手册上的话来说就是惰性连接)。然后将查询结果返回。


56、add数据源码详解:获取数据,调用_facade方法过滤字段并在其中调用_parseType进行数据类型检测,调用_parseOptions分析表达式,然后调用$this->db->insert,进入Db/Driver.class.php,通过parseBind将bind绑定到$this->bind,过滤数据标量,统一都将数据放入$this->bind里面(以:count($this->bind)为键),形成sql语句,并调用$this->execute方法,然后调用strtr方法替换绑定中的替换符(用以输出语句,查询的时候仍然是使用有替换符的语句进行预处理),然后调用$this->PDOStatement->bindValue绑定,然后执行语句,返回上一个sql语句影响的行数到add,然后add返回插入id。


57、addAll无非就是循环再拼接而已,只需从第一行分析出键就可以了。


58、查找数据:可以使用select和find,调用_parseOptions,分析表名,将数据表别名加入到名字后面,(我才发现select a.* from user a和select a.* from user as a),然后调用$this->db->select,然后调用buildSelectSql,然后调用parseSql,然后调用str_replace替换已经写好的sql语句。然后调用pdo预处理和查询。注意,find的_read_data传的是$resultSet[0],所以返回的结果是一维数组。别名的用法可以使用alias,或者table('user a')即可。


59、使用getField方法,具体对照手册与源代码一一对应即可。


60、按照手册上的setInc方法,查看了一下源代码,只是缓存到文件里而已,只有再规定时间后再次调用这个方法,才可以写入数据库。


61、删除是一样的。


62、看一下token令牌如何使用。查看手册可知,首先在home/conf/config.php下引入配置,其实关键的是TOKEN_ON,其他的都可以默认的,然后在Common/cnof/tags.php下引入'view_filter' => array('Behavior\TokenBuildBehavior'),具体有没有引入成功可以在Hook里的listen下打印self::$tags。然后在解析完模版之后,调用了TokenBuildBehavior方法,然后调用getToken方法生产$_SESSION,然后引入隐藏域,然后调用autoCheckToken验证后再销毁,否则,就是重复提交了。

猜你喜欢

转载自blog.csdn.net/d13579246810b/article/details/78139583
今日推荐