因为要处理一个验证码插件。今天特地看了一下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)是什么时候触发的,就不清楚了。还需进一步的研究。


