CI框架源码解析五之钩子类文件Hooks.php

        CI框架可以实现在不修改系统核心文件的基础上来改变或增加系统的核心运行功能(如重写缓存、输出等),那就是Hooks,主要作用是CI框架下扩展base_system,它的主要作用是在CI启动时运行一些开发者定义的一些方法,来实现一些特定的功能。钩子是什么呢?我们可以这样理解:

  1. 钩子是一种事件驱动模式,它的核心自然是事件(CI框架中pre_system,pre_controller等都是特定的事件)。
  2. 既然是事件驱动,那么必然要包含最重要的两个步骤: (1)、事件注册。对于Hook而言,就是指Hook钩子的挂载。(2).事件触发。在特定的时间点call特定的钩子,执行相应的钩子程序。
  3. 既然是事件驱动,那么也应该支持统一挂钩点的多个注册事件。
  4. 启动Hook钩子之后,程序的流程可能会发生变化,且钩子之间可能有相互调用的可能性,如果处理不当,会有死循环的可能性。同时,钩子的启用使得程序在一定程度上变得复杂,难以调试。

看看CI框架有哪些钩子:

  • pre_system:系统执行的早期调用.仅仅在benchmark 和 hooks 类 加载完毕的时候,没有执行路由或者其它的过程。
  • pre_controller:在调用你的任何控制器之前调用,此时所用的基础类,路由选择和安全性检查都已完成。
  • post_controller_constructor:在你的控制器实例化之后,任何方法调用之前调用。
  • post_controller:在你的控制器完全运行之后调用。
  • display_override:覆盖_display()函数,用来在系统执行末尾向web浏览器发送最终页面。这允许你用自己的方法来显示。注意,你需要通过 $this->CI =& get_instance() 引用 CI 超级对象,然后这样的最终数据可以通过调用 $this->CI->output->get_output() 来获得。
  • cache_override:可以让你调用自己的函数来取代output类中的_display_cache() 函数。这可以让你使用自己的缓存显示方法。
  • post_system:在最终着色页面发送到浏览器之后,浏览器接收完最终数据的系统执行末尾调用 。

        下面我们剖析CI框架中钩子的实现:

         CI中钩子的核心功能是由Hook组件完成的:
                 其中:
                    enabled:  钩子功能是否开启的标志。
                    hooks :保存系统中启用的钩子列表。
                    in_progress:之后我们会看到,这个标志位用于防止钩子之间的互相调用而导致的死循环。
                    _construct是Hook组件的构造函数,这其中调用了_initialize来完成初始化的工作。
                    _call_hook: 调用_run_hook来call指定的钩子程序。之前CodeIgniter.php中我们已经看到,_call_hook是实际提供给外部调用的接口。
                    _run_hook: 实际执行钩子程序的函数。

        在开始之前,我们先贴出预定义钩子的结构。这个结构可能会贯穿在源代码的始终,因而我们有必要知道该结构的参数含义。在application/config/hooks.php中定义的要在CI启动时启动的方法定义:

    $hook['pre_controller'][] = array(
        'class'    => 'MyClass',        //钩子调用的类名,可以为空
        'function' => 'Myfunction',    //钩子调用的函数名
        'filename' => 'Myclass.php',   //该钩子的文件名
        'filepath' => 'hooks',         //钩子的目录
        'params'   => array('beer', 'wine', 'snacks'),  //传递给钩子的参数
    );

1、钩子组件初始化

    public function __construct()
    {
        //初始化,获取hooks配合
        $CFG =& load_class('Config', 'core');
        log_message('info', 'Hooks Class Initialized');
        //检测配置是否开启钩子
        if ($CFG->item('enable_hooks') === FALSE) {
            return;
        }
        //检测是否配置钩子
        if (file_exists(APPPATH . 'config/hooks.php')) {
            include(APPPATH . 'config/hooks.php');
        }
        if (file_exists(APPPATH . 'config/' . ENVIRONMENT . '/hooks.php')) {
            include(APPPATH . 'config/' . ENVIRONMENT . '/hooks.php');
        }
        if (!isset($hook) OR !is_array($hook)) {
            return;
        }
        //把钩子信息都保存到Hook组件中。
        $this->hooks =& $hook;
        $this->enabled = TRUE;
    }

        首先,检查配置文件中hook功能是否被启用,这需要加载Config(配置管理组件);其次加载定义的hook列表,同样,你可以设定不同的ENVIRONMENT启用不同的hook,如果有的话,优先加载ENVRIONMENT下的hook;然后是对Hook的检查,如果未设置任何hook,或者设置的hook格式错误,则不作任何处理,直接退出;最后经过初始化之后,Hook::hooks中存储了已经定义的hook列表。

2、Call指定的钩子

    public function call_hook($which = '')
    {
        if (!$this->enabled OR !isset($this->hooks[$which])) {
            return FALSE;
        }
        //CI框架支持多次钩子,那么就是二维数组
        //同一个位置可以执行多个hook
        if (is_array($this->hooks[$which]) && !isset($this->hooks[$which]['function'])) {
            foreach ($this->hooks[$which] as $val) {
                $this->_run_hook($val);
            }
        } else {
            //一个钩子直接运行钩子
            $this->_run_hook($this->hooks[$which]);
        }
        return TRUE;
    }

        _call_hook是主程序中直接调用的接口。该接口主要的工作有:首先,检查钩子是否被启用,以及call的钩子是否被预定义(如果未启用或者call的钩子不存在,则直接返回);然后检查同一个挂钩点是否启用了多个钩子,如果有,则依次执行之,否则,只有一个钩子,执行它;最后执行。

3、run执行特定的钩子程序

    protected function _run_hook($data)
    {
        //一般来说,这个$data会有:类名,方法名,参数,类文件路径等参数。
        //也就是上述我们定义的调用方式
        if (is_callable($data)) {
            is_array($data) ? $data[0]->{$data[1]}() : $data();
            return TRUE;
        } elseif (!is_array($data)) {
            return FALSE;
        }
        if ($this->_in_progress === TRUE) {
            return;
        }
        if (!isset($data['filepath'], $data['filename'])) {
            return FALSE;
        }
        $filepath = APPPATH . $data['filepath'] . '/' . $data['filename'];
        if (!file_exists($filepath)) {
            return FALSE;
        }
        $class = empty($data['class']) ? FALSE : $data['class'];
        $function = empty($data['function']) ? FALSE : $data['function'];
        $params = isset($data['params']) ? $data['params'] : '';
        if (empty($function)) {
            return FALSE;
        }
        //在开始执行钩子相应的程序之前,先把当前hook的状态设为正在运行中。
        $this->_in_progress = TRUE;
        if ($class !== FALSE) {
            if (isset($this->_objects[$class])) {
                if (method_exists($this->_objects[$class], $function)) {
                    $this->_objects[$class]->$function($params);
                } else {
                    return $this->_in_progress = FALSE;
                }
            } else {
                class_exists($class, FALSE) OR require_once($filepath);
                if (!class_exists($class, FALSE) OR !method_exists($class, $function)) {
                    return $this->_in_progress = FALSE;
                }
                $this->_objects[$class] = new $class();
                $this->_objects[$class]->$function($params);
            }
        } else {
            function_exists($function) OR require_once($filepath);
            if (!function_exists($function)) {
                return $this->_in_progress = FALSE;
            }
            //$function($params);
        }
        //执行相应程序完毕后,重新把当前hook的状态改为非运行中,以让它可以再次被触发。
        $this->_in_progress = FALSE;
        return TRUE;
    }

_run_hook函数是hook的实际执行者,该函数接收一个预定义的hook数组作为参数,实现如下:

  1. 如果传递的参数压根就不是数组(自然也就不是有效的hook),那么直接返回;
  2. 检查hook执行状态:in_progress用于标志当前hook的执行状态。这个参数的主要作用,是防止hook之间的相互调用而导致的死循环;
  3. Hook的合法性检查:为了方便讲述,我们再次提出一个预定义的hook需要的参数,其中class和params是可选参数,其他3个参数为必选参数,如果不提供,则由于无法准确定位到hook程序,只能直接返回;
  4. 到这里,已经基本确认钩子程序的位置了,这里有两种情况:a. 预定义的hook中class参数为空,表明使用的是过程式的调用方式,则直接执行hook文件中的function xxx ;b. class参数不为空,提供的是面向对象的方式,则实际的钩子程序是$class->$function .同样,如果既没有设置class,也没有设置function参数,则无法执行hook,直接返回;
  5. 设置执行标志in_progress,并执行上述两种情况下的hook;
  6. 最后,别忘了在hook执行完之后,设置标识位in_progress为false,并返回执行成功的标志。

        最后,按照惯例,贴一下整个钩子类Hooks.php文件的源码(注释版):

    <?php
    
    /**
     * =======================================
     * Created by Pocket Knife Technology.
     * User: ZhiHua_W
     * Date: 2016/10/18 0018
     * Time: 下午 5:21
     * Project: CodeIgniter框架—源码分析
     * Power: Analysis for Hooks.php
     * =======================================
     */
    
    //不再赘述
    defined('BASEPATH') OR exit('No direct script access allowed');
    
    /**
     * Class CI_Hooks
     * 钩子类
     * hooks即钩子,主要作用是CI框架下扩展base_system,它的主要作用是在CI启动时,
     * 运行一些开发者定义的一些方法,来实现一些特定的功能。
     * 也就是在不修改系统核心文件的基础上来改变或增加系统的核心运行功能。
     */
    class CI_Hooks
    {
        //检测hook是否开启
        public $enabled = FALSE;
        //config/hooks.php中的hooks配置信息
        public $hooks = array();
        //数组与类对象使用钩子方法
        protected $_objects = array();
        //防止死循环,因为钩子程序里面可能还还有钩子
        protected $_in_progress = FALSE;
    
        /**
         * 构造函数
         */
        public function __construct()
        {
            //初始化,获取hooks配合
            //获取配置文件内容
            $CFG =& load_class('Config', 'core');
            log_message('info', 'Hooks Class Initialized');
    
            //检测配置是否开启钩子
            //如果配置文件中设置了是不允许hooks,则直接返回退出本函数
            if ($CFG->item('enable_hooks') === FALSE) {
                return;
            }
            //检测是否配置钩子
            //要使用到的钩子,必须在配置目录下的hooks.php里面定义好。否则无法使用。
            if (file_exists(APPPATH . 'config/hooks.php')) {
                include(APPPATH . 'config/hooks.php');
            }
            if (file_exists(APPPATH . 'config/' . ENVIRONMENT . '/hooks.php')) {
                include(APPPATH . 'config/' . ENVIRONMENT . '/hooks.php');
            }
            if (!isset($hook) OR !is_array($hook)) {
                return;
            }
            //把钩子信息都保存到Hook组件中。
            $this->hooks =& $hook;
            $this->enabled = TRUE;
        }
    
        /**
         * 外部其实就是调用这个_call_hook函数进行调用钩子程序。
         * 而此方法中再调用_run_hook去执行相应的钩子。
         * 运行钩子程序,外部就是这样调用:
         * $EXT =& load_class('Hooks', 'core');
         * $EXT->_call_hook('pre_system');
         * 之前CodeIgniter.php中我们已经看到,_call_hook是实际提供给外部调用的接口。
         */
        public function call_hook($which = '')
        {
            if (!$this->enabled OR !isset($this->hooks[$which])) {
                return FALSE;
            }
            //CI框架支持多次钩子,那么就是二维数组
            //同一个位置可以执行多个hook
            if (is_array($this->hooks[$which]) && !isset($this->hooks[$which]['function'])) {
                foreach ($this->hooks[$which] as $val) {
                    $this->_run_hook($val);
                }
            } else {
                //一个钩子直接运行钩子
                $this->_run_hook($this->hooks[$which]);
            }
            return TRUE;
        }
    
        /**
         * 运行一个特定的钩子
         * _run_hook函数是hook的实际执行者,该函数接收一个预定义的hook数组作为参数
         */
        protected function _run_hook($data)
        {
            //一般来说,这个$data会有:类名,方法名,参数,类文件路径等参数。
            if (is_callable($data)) {
                is_array($data) ? $data[0]->{$data[1]}() : $data();
                return TRUE;
            } elseif (!is_array($data)) {
                return FALSE;
            }
            //防止死循环,因为钩子程序里面可能还还有钩子
            //如果调用某一个hook,执行某些脚本,而有可能这些脚本里面再会触发其它hook,如果这个其它hook里面又包含了当前
            //的hook,那么就会进入死循环,这个in_progress的存在就是阻止这种情况。
            if ($this->_in_progress === TRUE) {
                return;
            }
            //下面都是一些执行钩子的预处理,包括判断类文件是否存在,类和方法是否正确等等。
            //设置路径
            if (!isset($data['filepath'], $data['filename'])) {
                return FALSE;
            }
            //filepath就以应用文件夹(application)为基准,application/hooks下, 你可以把hooks 作为你的filepath
            $filepath = APPPATH . $data['filepath'] . '/' . $data['filename'];
            if (!file_exists($filepath)) {
                return FALSE;
            }
    
            //确定和类和/或函数名称
            $class = empty($data['class']) ? FALSE : $data['class'];
            $function = empty($data['function']) ? FALSE : $data['function'];
            $params = isset($data['params']) ? $data['params'] : '';
            if (empty($function)) {
                return FALSE;
            }
            //在开始执行钩子相应的程序之前,先把当前hook的状态设为正在运行中。
            $this->_in_progress = TRUE;
            //获取钩子配置信息成功后,运行钩子程序
            if ($class !== FALSE) {
                if (isset($this->_objects[$class])) {
                    if (method_exists($this->_objects[$class], $function)) {
                        $this->_objects[$class]->$function($params);
                    } else {
                        return $this->_in_progress = FALSE;
                    }
                } else {
                    class_exists($class, FALSE) OR require_once($filepath);
                    if (!class_exists($class, FALSE) OR !method_exists($class, $function)) {
                        return $this->_in_progress = FALSE;
                    }
                    $this->_objects[$class] = new $class();
                    $this->_objects[$class]->$function($params);
                }
            } else {
                function_exists($function) OR require_once($filepath);
                if (!function_exists($function)) {
                    return $this->_in_progress = FALSE;
                }
                //$function($params);
            }
            //执行相应程序完毕后,重新把当前hook的状态改为非运行中,以让它可以再次被触发。
            $this->_in_progress = FALSE;
            return TRUE;
        }
    
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值