TP中的Behavior(行为)总结!

浏览:17070 发布日期:2016/01/14 分类:技术分享 关键字: 行为
首先解释一下什么是钩子 (hook)?什么是行为?
看TP的源码看得出,hook是一个抽象的概念,是一套触发机制。钩子应该具有的基本方法应该有:
-设置钩子
-触发钩子
-执行行为

行为就是继承了Behavior的行为类,类名以Behavior结尾,实现run方法。也可以称之为 钩子函数。

第一步设置钩子,也就是埋设陷阱。把你的钩子想象成一个陷阱,放到系统流程可能经过的路上,如果陷阱被系统踩到,就会执行你的行为,当执行完后,系统会根据你的程序的结果继续运行。

在Thinkphp Behavior中,我们把这些行为发生作用的位置(陷阱)称之为标签(位)(tag),当应用程序运行到这个标签的时候,就会被拦截下来,统一执行相关的行为。

在tp中,典型的例子就是:thinkphp/conf/mode/common.php'tags'  =>  array(
        'app_init'      =>  array(
        ),
        'app_begin'     =>  array(
            'Behavior\ReadHtmlCache', // 读取静态缓存
        ),
        'app_end'       =>  array(
            'Behavior\ShowPageTrace', // 页面Trace显示
        ),
        'path_info'     =>  array(),
        'action_begin'  =>  array(),
        'action_end'    =>  array(),
        'view_begin'    =>  array(),
        'view_parse'    =>  array(
            'Behavior\ParseTemplate', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
        ),
        'template_filter'=> array(
            'Behavior\ContentReplace', // 模板输出替换
        ),
        'view_filter'   =>  array(
            'Behavior\WriteHtmlCache', // 写入静态缓存
        ),
        'view_end'      =>  array(),
    ),
第二部,触发钩子。 在需要添加行为的函数里 ,直接Hook::Listen(tags,prarm),注意param一定要传变量,不需要传常量。触发行为的关键方法是Hook类中的listen方法,它通过遍历某个行为标签下的所有行为,依次实例化并调用run方法.
在tp中,典型的例子就是:App.class.phpstatic public function run() {
        // 应用初始化标签
        Hook::listen('app_init');
        App::init();
        // 应用开始标签
        Hook::listen('app_begin');
        // Session初始化
        if(!IS_CLI){
            session(C('SESSION_OPTIONS'));
        }
        // 记录应用初始化时间
        G('initTime');
        App::exec();
        // 应用结束标签
        Hook::listen('app_end');
        return ;
    }
第三部,执行行为。 找到行为类,类名以Behavior结尾,实现run方法。class ReadHtmlCacheBehavior {
    // 行为扩展的执行入口必须是run
    public function run(&$params){
        // 开启静态缓存
        if(IS_GET && C('HTML_CACHE_ON'))  {
            $cacheTime = $this->requireHtmlCache();
            if( false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME,$cacheTime)) { //静态页面有效
                // 读取静态页面输出
                echo Storage::read(HTML_FILE_NAME,'html');
                exit();
            }
        }
    }
钩子在MVC模式下十分重要,他实现了在不改变源代码的前提下提升系统的灵活性,如,在文章输出前打印版权信息,在文章输出后生成二维码信息,app运行前检查用户权限,还有更多产品经理提出的变态要求,都可以。
--------------------------------------------下面讲一下tp内部实现-------------------------------------------------------

源代码位于ThinkPHP/Library/Think/Hook.class.php,Hook类中全是静态方法,其中有唯一静态属性$tags,他是一个数组,键为钩子,值为行为。
其中有两个方法可以用于绑定,前者是单个,后者是是批量。static private  $tags       =   array();

    /**
     * 动态添加插件到某个标签
     * @param string $tag 标签名称
     * @param mixed $name 插件名称
     * @return void
     */
    static public function add($tag,$name) {
        if(!isset(self::$tags[$tag])){
            self::$tags[$tag]   =   array();
        }
        if(is_array($name)){
            self::$tags[$tag]   =   array_merge(self::$tags[$tag],$name);
        }else{
            self::$tags[$tag][] =   $name;
        }
    }

    /**
     * 批量导入插件
     * @param array $data 插件信息
     * @param boolean $recursive 是否递归合并
     * @return void
     */
    static public function import($data,$recursive=true) {
        if(!$recursive){ // 覆盖导入
            self::$tags   =   array_merge(self::$tags,$data);
        }else{ // 合并导入
            foreach ($data as $tag=>$val){
                if(!isset(self::$tags[$tag]))
                    self::$tags[$tag]   =   array();            
                if(!empty($val['_overlay'])){
                    // 可以针对某个标签指定覆盖模式
                    unset($val['_overlay']);
                    self::$tags[$tag]   =   $val;
                }else{
                    // 合并模式
                    self::$tags[$tag]   =   array_merge(self::$tags[$tag],$val);
                }
            }            
        }
    }
TP Hook::listen方法,该方法会查找$tags中有没有绑定app_start事件的方法,然后用foreach遍历$tags属性,并执行Hook:exec方法。
Hook:exec方法会检查行为名称,如果包含Behavior关键字,那么入口方法必须为run方法,而执行run方法的参数在调用Hook::listen时指定。/**
     * 监听标签的插件
     * @param string $tag 标签名称
     * @param mixed $params 传入参数
     * @return void
     */
    static public function listen($tag, &$params=NULL) {
          if(isset(self::$tags[$tag])) {
            if(APP_DEBUG) {
                G($tag.'Start');
                trace('[ '.$tag.' ] --START--','','INFO');
            }
            foreach (self::$tags[$tag] as $name) {
                APP_DEBUG && G($name.'_start');
                $result =   self::exec($name, $tag,$params);
                if(APP_DEBUG){
                    G($name.'_end');
                    trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
                }
                if(false === $result) {
                    // 如果返回false 则中断插件执行
                    return ;
                }
            }
            if(APP_DEBUG) { // 记录行为的执行日志
                trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
            }
        }
        return;
    }

    /**
     * 执行某个插件
     * @param string $name 插件名称
     * @param string $tag 方法名(标签名)     
     * @param Mixed $params 传入的参数
     * @return void
     */
    static public function exec($name, $tag,&$params=NULL) {
        if('Behavior' == substr($name,-8) ){
            // 行为扩展必须用run入口方法
            $tag    =   'run';
        }
        $addon   = new $name();
        return $addon->$tag($params);
    }
----------------------------------交流tp的可以加群:416664425---------------------
最佳答案
评论( 相关
后面还有条评论,点击查看>>