因为要处理一个验证码插件。今天特地看了一下joomla的验证码插件的实现源码。有点感悟。感觉和昨天将的观察者模式非常的像。就贴出来总结一下。
Tips:在阅读本文前,建议先实现一下上一节的观察者模式,这样你会对下面的代码有一种似曾相识的感觉。
<?php defined('JPATH_PLATFORM') or die; jimport('joomla.filesystem.file'); /** * Joomla! Captcha base object * * @abstract * @package Joomla.Libraries * @subpackage Captcha * @since 2.5 */ class JCaptcha extends JObject { /** * An array of Observer objects to notify * * @var array * @since 2.5 */ //用来储存观察者状者的数组 protected $_observers = array(); /** * The state of the observable object * * @var mixed * @since 2.5 */ //主题的状态 protected $_state = null; /** * A multi dimensional array of [function][] = key for observers * * @var array * @since 2.5 */ //多维数组,key 是观察者 值是对应的功能 protected $_methods = array(); /** * Captcha Plugin object * * @var object * @since 2.5 */ //插件对象 private $_captcha; /** * Editor Plugin name * * @var string * @since 2.5 */ //插件的名称 private $_name; /** * Captcha Plugin object * * @var array */ //插件对象 private static $_instances = array(); /** * Class constructor. * * @param string $editor The editor to use. * @param array $options Associative array of options. * * @since 2.5 */ //构造器 构造的时候需要两个从参数 一个是插件的名称 一个是选项 public function __construct($captcha, $options) { $this->_name = $captcha; $this->_load($options); } /** * Returns the global Captcha object, only creating it * if it doesn't already exist. * * @param string $captcha The plugin to use. * @param array $options Associative array of options. * * @return object The JCaptcha object. * * @since 2.5 */ //得到全局的captcha对象 单件 public static function getInstance($captcha, array $options = array()) { //serialize :PHP函数 // 功能;产生一个可存储的值的表示。返回字符串 //想要将已经序列化的字符串变为PHP的值,可以用Unserialize() //md5 :php函数 //功能:计算一个字符串的md5 hash值 //得到一个唯一的字符串作为签名 $signature = md5(serialize(array($captcha, $options))); if (empty(self::$_instances[$signature]))//如果是空的 { try { self::$_instances[$signature] = new JCaptcha($captcha, $options); } catch (RuntimeException $e) { JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); return null; } } return self::$_instances[$signature]; } /** * @return boolean True on success * * @since 2.5 */ //成员函数 返回ture or false //大意是 初始化 传递了一个参数 $id public function initialise($id) { $args['id'] = $id ; //ID $args['event'] = 'onInit'; //onInit事件 try { $this->_captcha->update($args); //调用了主题的更新方法,每一个主题都会执行onInit函数 并且会获得一个id参数 } catch (Exception $e) { JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); return false; } return true; } /** * Get the HTML for the captcha. * * @return the return value of the function "onDisplay" of the selected Plugin. * * @since 2.5 */ //得到验证码的HTML //选择的验证码插件执行onDisplay后的返回值 public function display($name, $id, $class = '') { // Check if captcha is already loaded. if (is_null($this->_captcha)) { return; } // Initialise the Captcha. //在这里才开始调用验证码的initlaise方法 if (!$this->initialise($id)) { return; } $args['name'] = $name; $args['id'] = $id ? $id : $name; $args['class'] = $class ? 'class="'.$class.'"' : ''; $args['event'] = 'onDisplay'; return $this->_captcha->update($args); //这里又开始调用主题的 onDisplay事件 } /** * Checks if the answer is correct. * * @return the return value of the function "onCheckAnswer" of the selected Plugin. * * @since 2.5 */ //检查输入的验证码是否正确 //返回值为选择的验证码插件执行onCheckAnswer的返回值 public function checkAnswer($code) { //check if captcha is already loaded if (is_null(($this->_captcha))) { return; } $args['code'] = $code; $args['event'] = 'onCheckAnswer'; return $this->_captcha->update($args); } /** * Load the Captcha plug-in. * * @param array $options Associative array of options. * * @return void * * @since 2.5 * @throws RuntimeException */ //加载验证码插件 private function _load(array $options = array()) { // Build the path to the needed captcha plugin //获得正在执行的插件的路径 $name = JFilterInput::getInstance()->clean($this->_name, 'cmd'); //这里为什么是这样呢?为什么能够获得插件的名称 //需要看一下 JFilterInput的clear方法的作用。 //粗略估计应该是对名称的处理,确保为合法的名称 $path = JPATH_PLUGINS . '/captcha/' . $name . '/' . $name . '.php'; //插件的主文件 if (!JFile::exists($path)) //不存在抛出异常 { throw new RuntimeException(JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name)); } // Require plugin file require_once $path; // Get the plugin $plugin = JPluginHelper::getPlugin('captcha', $this->_name); //这个JPluginHelper类需要研究一下 // JPluginHelper类是一个虚类 ,里面提供了一个静态的方法 getPlugin //这个方法的作用是返回一个插件对象 if (!$plugin) { throw new RuntimeException(JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name)); } //获得参数 $params = new JRegistry($plugin->params); $plugin->params = $params; // Build captcha plugin classname //得到插件的类名 $name = 'plgCaptcha' . $this->_name; $this->_captcha = new $name($this, (array)$plugin, $options); } /** * Get the state of the JEditor object * * @return mixed The state of the object. * * @since 2.5 */ //得到对象的状态 public function getState() { return $this->_state; } /** * Attach an observer object * * @param object $observer An observer object to attach * * @return void * * @since 2.5 */ //加入一个观察者对象 public function attach($observer) { if (is_array($observer)) { if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler'])) { return; } // Make sure we haven't already attached this array as an observer foreach ($this->_observers as $check) { if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler']) { return; } } $this->_observers[] = $observer; end($this->_observers); $methods = array($observer['event']); } else { if (!($observer instanceof JEditor)) { return; } // Make sure we haven't already attached this object as an observer $class = get_class($observer); foreach ($this->_observers as $check) { if ($check instanceof $class) { return; } } $this->_observers[] = $observer; $methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin')); } $key = key($this->_observers); foreach ($methods as $method) { $method = strtolower($method); if (!isset($this->_methods[$method])) { $this->_methods[$method] = array(); } $this->_methods[$method][] = $key; } } /** * Detach an observer object * * @param object $observer An observer object to detach. * * @return boolean True if the observer object was detached. * * @since 2.5 */ //注销观察者 public function detach($observer) { // Initialise variables. $retval = false; $key = array_search($observer, $this->_observers); if ($key !== false) { unset($this->_observers[$key]); $retval = true; foreach ($this->_methods as &$method) { $k = array_search($key, $method); if ($k !== false) { unset($method[$k]); } } } return $retval; } }
如果你看了前面的章节,那么你至少会理解以下3个地方:
1,
/** * An array of Observer objects to notify * * @var array * @since 2.5 */ //用来储存观察者状者的数组 protected $_observers = array();
2,
/** * Attach an observer object * * @param object $observer An observer object to attach * * @return void * * @since 2.5 */ //加入一个观察者对象 public function attach($observer) //注销观察者 public function detach($observer)
3,
try { $this->_captcha->update($args); //调用了主题的更新方法,每一个主题都会执行onInit函数 并且会获得一个id参数 }
这个3个地方实现了观察者模式最基础的要求。
观察者模式要做的就是解偶,主题通过调用update方法来通知所有的观察者来更新自己的状态。
在上面的代码中我们看到系统一共触发了一下几个事件:
1,
$args['id'] = $id ; //ID
$args['event'] = 'onInit'; //onInit事件
2,
$args['name'] = $name;
$args['id'] = $id ? $id : $name;
$args['class'] = $class ? 'class="'.$class.'"' : '';
$args['event'] = 'onDisplay'; //onDisplay事件
3,
$args['code'] = $code;
$args['event'] = 'onCheckAnswer';//onCheckAnswer事件
每个事件都有对应的参数。知道了系统会触发这3件事,那么我们就该知道怎样写我们的观察者了(自己的验证码插件)。只要我们最这3个事件做出适当的反应就行了。说白了就是在自己的类中实现这3个函数。
但知道这3个函数还是不够的。因为这3个函数的执行顺序我们不是很清楚。
在上面的代码中, 我们知道在调用了,在display内部,系统先调用了$this->initialise() ,在Initialise方法中系统触发了一个OnInit事件。随后系统触发了onDisplay事件。但是第3个事件(onCheckAnswer)是什么时候触发的,就不清楚了。还需进一步的研究。