CI框架源码学习笔记5——Hooks.php
接着Benchmark.php往下看,下一个引入的文件是Hooks.php,我们称之为钩子。它的目的是在不改变核心文件的基础上,来修改框架的内部运作流程。具体使用方法参见手册http://codeigniter.org.cn/user_guide/general/hooks.html。
首先看类里面的几个属性,
public $enabled = FALSE; 用来表示钩子是否可用
public $hooks = array(); 配置文件中的信息
protected $_objects = array(); 缓存用变量,用来储存挂钩点方法对应的对象
protected $_in_progress = FALSE; 表示当前钩子是否正在进程中
public function __construct() { $CFG =& load_class('Config', 'core'); log_message('info', 'Hooks Class Initialized'); // If hooks are not enabled in the config file // there is nothing else to do if ($CFG->item('enable_hooks') === FALSE) { return; } // Grab the "hooks" definition file. 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 there are no hooks, we're done. if ( ! isset($hook) OR ! is_array($hook)) { return; } $this->hooks =& $hook; $this->enabled = TRUE; }
再来看看构造函数,$CFG =& load_class('Config', 'core');是加载config组件,用来获取hook的配置(具体的配置获取流程,我们后面分析Config.php时再详细讨论),log_message('info', 'Hooks Class Initialized');用来在日志中记录调用钩子的信息,注意需要在config.php配置中修改log_threshold的对应等级才能记录,具体原理参见前面的Log.php章节。
再往下判断配置中的enable_hooks属性是否设置为了true,如果没有设置那么不继续往下,所以$this->enabled属性不会置为true,实际上意味着此时钩子功能不生效。
接着引入hooks.php文件,并且获取其中配置,若配置信息格式正确,设置$this->enabled = TRUE;
此时构造函数结束,我们接着看call_hook方法。
public function call_hook($which = '') { if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; } 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; }
钩子功能的之所以生效是因为我们在Codeigniter.php文件中,多次使用了call_hook方法,该方法的参数我们称之为挂钩点,ci中的挂钩点有7个,具体参见文档,不细述。
先判断enabled属性是否为true,并且相应挂钩点的配置内容是否存在,只有上面两个条件都符合才继续往下。
if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function']))这行代码的目的是判断是否多次调用该挂钩点,ci框架支持挂钩点多次调用,只需要在配置中写成数组形式即可。若是,循环对配置执行_run_hook,如不是就不需要循环。
protected function _run_hook($data) { //判断$data是否时合法的可调用结构,如果是的话直接调用 //主要是为了处理ambda 表达式/匿名函数(或闭包)作为钩子的这种情况 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; } //即将调用挂钩点方法,设置_in_progress为true,阻止再次进入脚本执行 $this->_in_progress = TRUE; //判读$class是否为false,目的是判断配置中是否写了class,若没有写,那么可能 //挂钩点的文件不是class的形式,只有一个函数,直接调用即可 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); //判断类和类中的方法是否存在,若为false,是否钩子的进程变量_in_progress,推出_run_hook方法 if ( ! class_exists($class, FALSE) OR ! method_exists($class, $function)) { return $this->_in_progress = FALSE; } //类实例化,并且储存到_objects变量中 $this->_objects[$class] = new $class(); //调用类中的方法 $this->_objects[$class]->$function($params); } } //直接调用文件中的函数 else { //检查类是否已经定义,若没有定义那么引入类文件 function_exists($function) OR require_once($filepath); //判断即将调用的方法是否存在,若不存在释放hook进程变量,退出_run_hook方法 if ( ! function_exists($function)) { return $this->_in_progress = FALSE; } //传入参数,调用方法 $function($params); } //释放进程变量,返回true,_run_hook方法执行成功 $this->_in_progress = FALSE; return TRUE; }
(代码分析写在注释中了)。
Hooks.php文件就是上面的这些内容了,分析完毕,下面贴出全部代码。
class CI_Hooks { /** * Determines whether hooks are enabled * * @var bool */ public $enabled = FALSE; /** * List of all hooks set in config/hooks.php * * @var array */ public $hooks = array(); /** * Array with class objects to use hooks methods * * @var array */ protected $_objects = array(); /** * In progress flag * * Determines whether hook is in progress, used to prevent infinte loops * * @var bool */ protected $_in_progress = FALSE; /** * Class constructor * * @return void */ public function __construct() { $CFG =& load_class('Config', 'core'); log_message('info', 'Hooks Class Initialized'); // If hooks are not enabled in the config file // there is nothing else to do if ($CFG->item('enable_hooks') === FALSE) { return; } // Grab the "hooks" definition file. 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 there are no hooks, we're done. if ( ! isset($hook) OR ! is_array($hook)) { return; } $this->hooks =& $hook; $this->enabled = TRUE; } // -------------------------------------------------------------------- /** * Call Hook * * Calls a particular hook. Called by CodeIgniter.php. * * @uses CI_Hooks::_run_hook() * * @param string $which Hook name * @return bool TRUE on success or FALSE on failure */ public function call_hook($which = '') { if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; } 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 * * Runs a particular hook * * @param array $data Hook details * @return bool TRUE on success or FALSE on failure */ protected function _run_hook($data) { // Closures/lambda functions and array($object, 'method') callables if (is_callable($data)) { is_array($data) ? $data[0]->{$data[1]}() : $data(); return TRUE; } elseif ( ! is_array($data)) { return FALSE; } // ----------------------------------- // Safety - Prevents run-away loops // ----------------------------------- // If the script being called happens to have the same // hook call within it a loop can happen if ($this->_in_progress === TRUE) { return; } // ----------------------------------- // Set file path // ----------------------------------- if ( ! isset($data['filepath'], $data['filename'])) { return FALSE; } $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath)) { return FALSE; } // Determine and class and/or function names $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; } // Set the _in_progress flag $this->_in_progress = TRUE; // Call the requested class and/or function if ($class !== FALSE) { // The object is stored? 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; } // Store the object and execute the method $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); } $this->_in_progress = FALSE; return TRUE; } }