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

 

 

 



收藏
0个人 收藏

关注Joomla中国微信公众号,随时获得最新的Joomla新闻资讯!