运用连贯接口实现简化方法调用
上一篇:如何运用连贯接口
下面的代码是从一个实际应用程序代码而来,但简化了错误处理部分。该段代码验证 $data 数组中包含的信息是否符合要求。如果验证失败,则输出错误信息。
$validate = new QValidate();
$validate->check('username', $data)
->notEmpty('用户名不能为空')
->isAlphaNumber('用户名只能是字母和数字,不区分大小写')
->minLength(4, '用户名必须至少4个字符')
->maxLength(16, '用户名不能超过16个字符');
$validate->check('password', $data)
->notEmpty('登录密码不能为空')
->minLength(6, '登录密码必须至少6个字符')
->maxLength(16, '登录密码不能超过16个字符')
->equal($data['password2'], '两次输入的密码必须一致');
$validate->check('email', $data)
->notEmpty('电子邮件地址不能为空')
->isEmail('必须输入有效的电子邮件地址')
->maxLength(80, '邮件地址不能超过80个字符');
$validate->check('secure_question', $data)
->notEmpty('安全问题不能为空')
->maxLength(30, '安全问题不能超过30个字符');
$validate->add('secure_answer', $data)
->notEmpty('安全问题答案不能为空')
->maxLength(30, '安全问题答案不能超过30个字符');
if (!$validate->isPassed()) {
// 验证没有通过,输出错误信息
print_r($validate->isFailed());
}
与以前的非连贯接口写法相比,这个 QValidate 类提供的连贯接口方法让验证变得更容易阅读和理解。
现在,我们就来分析一下这个 QValidate 类的具体实现。
class QValidate
{
/**
* 所有的验证器对象(QValidate_Validator)
*
* @var array
*/
protected $_validators = array();
/**
* 创建一组检查
*
* @param string $id
* @param mixed $data
*
* @return QValidate_Validator
*/
function check($id, $data)
{
if (is_array($data)) {
if (array_key_exists($id, $data)) {
$obj = new QValidate_Validator($id, $data[$id]);
} else {
$obj = new QValidate_Validator($id, null);
}
} else {
$obj = new QValidate_Validator($id, $data);
}
$this->_validators[$id] = $obj;
return $obj;
}
/**
* 返回所有检查总的验证结果
*
* @return boolean
*/
function isPassed()
{
$result = true;
foreach ($this->validators as $validator) {
/* @var $validator QValidate_Validator */
$result = $result && $validator->isPassed();
}
return $result;
}
/**
* 返回所有失败的验证
*
* @param boolean $onlyFirstError 指示对于每一组检查都只返回第一个错误信息
*
* @return array
*/
function isFailed($onlyFirstError = false)
{
$failed = array();
foreach ($this->validators as $validator) {
/* @var $validator QValidate_Validator */
$failed = $validator->isFailed($onlyFirstError);
if (empty($failed)) { continue; }
$failed[$validator->id] = $failed;
}
return $failed;
}
}
QValidate 类的代码是很简单的,只有三个方法。其中最重要的就是 check() 方法。该方法将对数据的具体验证工作委托给了 QValidate_Validator 对象进行。
QValidate_Validator 类的代码:
class QValidate_Validator
{
/**
* 验证器的id
*
* @var string
*/
public $id;
/**
* 要验证的数据
*
* @var mixed
*/
protected $_value;
/**
* 验证结果
*
* @var boolean
*/
protected $_result;
/**
* 所有没有验证通过的检查
*
* @var array
*/
protected $_failed;
/**
* 构造函数
*
* @param string $id
* @param mixed $value
*/
function __construct($id, $value)
{
$this->id = $id;
$this->_value = $value;
$this->_result = null;
$this->_failed = array();
}
/**
* 返回验证结果
*
* @return boolean
*/
function isPassed()
{
return (bool)$this->_result;
}
/**
* 返回所有失败验证的错误信息
*
* @param boolean $onlyFirstError 指示是否只返回第一个错误信息
*
* @return array
*/
function isFailed($onlyFirstError = false)
{
return $onlyFirstError ? reset($this->_failed) : $this->_failed;
}
/**
* 返回要验证的数据
*
* @return mixed
*/
function getData()
{
return $this->_value;
}
/**
* 设置检查结果
*
* @param boolean $result
* @param string $check
* @param string $msg
*/
protected function _setResult($result, $check, $msg)
{
if (is_null($this->_result)) {
$this->_result = true;
}
$this->_result = $this->_result && (boolean)$result;
if (!$result) {
$this->_failed[$check] = $msg;
}
}
/**
* 是否等于指定值
*
* @param mixed $test
* @param string $msg
*
* @return QValidate_Validator
*/
function equal($test, $msg)
{
$this->_setResult($this->_value == $test, __FUNCTION__, $msg);
return $this;
}
/**
* 最小长度
*
* @param int $len
* @param string $msg
*
* @return QValidate_Validator
*/
function minLength($len, $msg)
{
$this->_setResult(strlen($this->_value) >= $len, __FUNCTION__, $msg);
return $this;
}
/**
* 最大长度
*
* @param int $len
* @param string $msg
*
* @return QValidate_Validator
*/
function maxLength($len, $msg)
{
$this->_setResult(strlen($this->_value) <= $len, __FUNCTION__, $msg);
return $this;
}
// 此处省略更多验证方法
}
QValidate_Validator 的具体实现就不再赘述了。实现要点就是每一个验证方法都返回 $this,以便开发者可以连续书写验证规则。
之所以使用了两个类,最重要的原因就是保持接口的最小化。所谓接口最小化原则就是指不应该在类中包含不属于自己职责的方法。对于 QValidate 来说,这个类只是提供对一组数据进行综合验证的服务,而具体的验证操作并不是它的职责。所以具体的验证操作由另一个 QValidate_Validator 类来实现。而且采用两个分离的类来实现,可以让开发者自由选择是使用 QValidate 对一组数据进行验证,还是直接使用 QValidate_Validator 对单个数据进行验证。
下面是另一个实际例子:
class Member extends QActiveRecord_Abstract
{
.....
}
$allVIPMembers = Member::find('is_vip = 1')->orderBy('username ASC')->query();这里,Member 类的静态方法 find() 会返回一个查询对象。这个查询对象则提供了 orderBy()、query() 等方法。 通过这种委托的模式,就把 orderBy() 这样与 Member 类无关的方法迁移到了查询对象类中,保持了 Member 类接口的最小化。当然,不是说要运用连贯接口,就一定要使用类似的模式。但是避免接口膨胀是需要特别注意的问题,可以采用各种设计模式来解决。

