数据过滤类

2021-09-27 11:01:49
admin
1975
最后编辑:admin 于 2022-10-14 08:11:06

数据过滤在 Web 应用中尤为重要,本节讲解 zentaoPHP 框架的数据过滤机制。 

一、在哪进行数据过滤? 

MVC 框架中,每一层都可以过滤数据。比如表单验证,会自动根据用户的输入进行验证,然后给予提示。那么数据过滤该放在哪一层呢?这个问题网络上大家有很多的争议,有的人主张放在 view 层,有的则主张放在 control 层。zentaoPHP 选择了 model 层。 

因为 model 层是底层,所有的数据操作都要经过 model 来进行处理。那么只要在这一关把数据过滤做好,就可以保证数据的准确和安全。当然,用户也可以同时在前端加上 js 的验证,和 model 层的验证不会冲突。 

二、zentaoPHP 数据过滤机制说明 

受 PHP 的 filter 机制启发,zentaoPHP 的数据过滤分为两个部分,一个是数据修正,一个是数据验证。首先是要对从客户端传递过来的数据进行修正,然后再对数据进行校验。 

数据修正类和验证类是在 lib/base/filter/filter.class.php 里面定义的。 

三、数据修正 

首先来看示例: 

$bug = fixer::input('post') 
->add('openedBy', $this->app->user->account)  
->add('openedDate', $now)  
->setDefault('project,story,task', 0)  
->setDefault('openedBuild', '')  
->setIF($this->post->assignedTo != '', 'assignedDate', $now)  
->setIF($this->post->story != false, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story)) 
->specialChars('title,steps,keyword')  
->cleanInt('product, module, severity')  
->join('openedBuild', ',')  
->remove('files, labels') 
->get();

首先,是调用 fixer 这个类的 input 方法,实例化 fixer 对象,它的参数 post 表示是从 $_POST 这个变量中获取数据。创建对象时,超级变量的数据被转化为对象的形式存储。 

紧接着的两行 add(),是向数据中增加两个变量; 

后面的两行 setDefault 则是表示,当这个变量没有传值的时候设置成默认值; 

接下来是两行setIF。setIF 共有三个参数,第一个是判断条件,后面两个分别是 key 和 value。也就是当条件为 true 的时候,设置 $key = $value; 

spechialchars 则表示对这三个字段进行 htmlspecialchars 处理; 

cleanInt 则将变量处理成 int 类型; 

join 则将 openedBuild 使用 , 连接起来; 

最后,使用 remove 把两个不需要的变量删掉。 

通过 get 方法就可以得到一个已经经过修正的完整的数据集合。这个集合已经可以准备入库了,接下来是进行数据验证操作。 

数据修正方法: 

方法 参数 说明 
setDefault$fields:待处理的字段,多个以逗号分隔; 
$value:这些字段的默认值 
设置指定字段的默认值。 
specialChars $fieldName:待处理的字段,多个以逗号分隔 对字段进行 htmlspecialchars 处理,转换为可以在浏览器查看的编码。 
cleanEmail$fieldName:待处理的字段,多个以逗号分隔 清理 Email 中的字符。删除除字母、数字和 !#$%&'*+-=?^_`{|}~@.[] 之外的所有字符。 
cleanFloat$fieldName:待处理的字段,多个以逗号分隔 清理 Float 类型,删除除数字、+- 和可选的 .,eE 之外的所有字符。 
cleanInt$fieldName:待处理的字段,多个以逗号分隔 清理 Int 类型,删除除数字、加号和减号之外的所有字符。 
cleanURL$fieldName:待处理的字段,多个以逗号分隔 清理 URL,删除除字母、数字和 $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&= 之外的所有字符。 
encodeurl$fieldName:待处理的字段,多个以逗号分隔 URL 编码字符串,可选择剥离或编码特殊字符。 
stripTags$fieldName:待处理的字段,多个以逗号分隔; 
$allowedTags :允许使用的标签 ,默认为空
去除目标字段中的标签。 
skipSpecial
 
$fieldName:待处理的字段,多个以逗号分隔 忽略处理给定的字段。 
quote$fieldName:待处理的字段,多个以逗号分隔 给字段添加引用,防止字符与关键字冲突。(注:<php7.3 可用) 
setIF$condition:条件判断 
$fieldName:字段名 
$value:字段值 
如果条件为真,则为字段赋值。 
setForce$fieldName:字段名 
$value:字段值 
强制给字段赋值(覆盖)。 
remove$fieldName:待删除的字段名,多个以逗号分隔 移除字段。 
removeIF$condition:条件判断 
$fields:待删除的字段名,多个以逗号分隔 
如果条件为真,移除该字段。 
add$fieldName:新增数据字段名; 
$value:新增数据值 
向数据对象中新增一个数据字段。 
addIF$condition:条件判断 
$fieldName:字段名 
$value:字段值 
如果条件为真,则为数据添加新的项。 
join$fieldName:字段名 
$value:字段值 
为指定字段增加值,使用逗号连接起来。 
callFunc$fieldName:待处理的字段,多个以逗号分隔; 
$func:自定义方法名 
调用一个自定义方法来处理数据。 
get$fields:待返回的字段,多个以逗号分隔处理完成后返回数据。参数为空,则表示返回所有字段数据。 

注:quote 方法使用的 FILTER_SANITIZE_MAGIC_QUOTES 自 PHP 7.3.0 起已弃用,自 PHP 8.0.0 起已删除,可改用 FILTER_SANITIZE_ADD_SLASHES(>php7.3有效)。 

四、数据验证 

验证器 

框架的 validater 验证类中包含了一系列验证方法,这里所谓的验证器,是指向其对应验证方法的标识符,验证器和验证方法是一一对应的。 

我们通过在配置文件或某些方法中指定验证器,来告诉框架应该使用哪个验证方法进行验证操作。 

验证器的应用场景主要有两处: 

  1. 在参数过滤配置文件(config/filter.php)中设置验证器,用于验证页面传递的参数(传送门:参数过滤配置); 

  2. 在某些方法中作为参数传入,常用于数据入库前的验证。 

本节我们主要讲解验证器作为 dao 类方法的参数传入,用于数据入库前的验证操作。 

验证器列表: 

验证器 对应验证方法 方法说明 
bool checkBool 验证一个变量是否能过滤为 bool 值。 
过滤规则:当变量值为“1”、“true”、“on”和“yes”时返回 true,否则返回 false。 
int checkInt 验证一个变量是否能过滤为整数。 
可选择从指定的范围内,如果成功则返回该整数,否则返回 false。 
该方法会去掉参数值左侧的0。例如:'0000123'会返回123。 
notint checkNotInt 检查不是 int 类型,取的是上面方法的反值。 
float checkFloat 验证一个变量是否能过滤为浮点数。 
可选择指定的范围内,如果成功则返回过滤后的值,否则返回 false。 
email checkEmail 验证一个变量是否是有效的电子邮件地址。 
如果是则返回该邮箱地址,否则返回 false。 
tel checkTel 验证是否是有效电话号码,是则返回1,否则返回0。 
mobile checkMobile 验证是否是有效手机号码,是则返回1,否则返回0。 
phone checkPhone 基于上面两个方法,验证是否是有效电话号码或手机号码。 
url checkURL 验证是否是有效 URL,注意变量值须要带上协议。该方法只会查找有效的 ASCII URL,不支持中文。 如果成功则返回有效的 URL 地址,否则返回 false。 
domain checkDomain 验证是否有效域名,不支持中文。是则返回1,否则返回0。 
ip checkIP 验证是否是有效IP地址,支持IPv4、IPv6。是则返回 IP 地址,否则返回 false。 
idcard checkIdcard 验证是否是有效身份证号,返回值为 true 或false。 
date checkDate 验证是否是有效日期。返回值为 true 或 false。 
注意:2022-09-31是一个合法日期,系统会将它转换为2022-10-01。 
reg checkREG 根据 regexp(一种 Perl 兼容的正则表达式)验证字符串匹配正则表达式。如果匹配成功,返回该字符串,否则返回 false。 
length checkLength 验证字符串长度是否符合要求,成功则返回其长度值,否则返回 false。 
notempty checkNotEmpty 验证是否不为空。返回值为 true 或 false。 
empty checkEmpty 验证是否为空。返回值为 true 或 false。 
account checkAccount 验证用户名是否有效。有效则返回用户名,否则返回 false。 
用户名的组成规则可以在配置文件中设置 $config->accountRule。 
code checkCode 验证 code,匹配正则 |^[A-Za-z0-9]+$|,如果匹配成功,则返回该字符串,否则返回 false。 
captcha checkCaptcha 验证验证码是否正确。返回值为 true 或 false。 
equal checkEqual 验证是否等于给定的值,返回值为 true 或 false。 
notequal checkNotEqual 验证是否不等于给定的值,返回值为 true 或 false。 
gt checkGT 验证是否大于给定的值,返回值为 true 或 false。 
lt checkLT 验证是否小于给定的值,返回值为 true 或 false。 
ge checkGE 验证是否大于等于给定的值,返回值为 true 或 false。 
le checkLE 验证是否小于等于给定的值,返回值为 true 或 false。 
in checkIn 验证变量值是否在给定的列表里。返回值为 true 或 false。 
filename checkFileName 验证文件名是否有效。返回值为 true 或 false。 
sensitive checkSensitive 验证变量中是否包含敏感词。 
$vars 为待校验的词(对象),$dicts 为敏感词集合(数组)。 
包含则返回 false,否则返回 true。 

model 层的验证操作 

在 model 层,使用 dao 对象操作数据,完成对数据入库前的最后验证。在 dao 类中定义了对数据字段的验证方法,主要有以下5个: 

方法 参数 说明 
autoCheck$skipFields:忽略验证的字段,默认为空 根据数据库结构(字段的类型、长度)验证字段。 
check$fieldName:要验证的字段; 
$funcName:验证器; 
$condition:验证唯一性时的附加查询条件,默认为空 
通过一个指定的验证器,验证字段是否满足条件。(可参见验证器列表) 
checkIF$condition:条件; 
$fieldName:要验证的字段; 
$funcName:验证器 
当指定条件成立时,才进行 check 操作。 
batchCheck$fields:要验证的字段集合,多个以逗号分隔; 
$funcName:验证器 
批量检查字段。即对集合里的每个字段进行 check 操作。 
batchCheckIF$condition:条件; 
$fields:要验证的字段集合,多个以逗号分隔; 
$funcName:验证器 
当指定条件成立时,才进行 batchCheck 操作。 
注:对于这几个方法的参数 $funcName,除了已给的验证器外,还可以使用 'unique' 来验证字段值在数据库中的唯一性。 

例如: 

接着前面的示例,完成数据修正后,对返回的数据集合 $bug 进行入库前的验证操作: 

$this->dao->insert(TABLE_BUG)->data($bug) 
->autoCheck() 
->batchCheck('id, name', 'notempty') 
->exec();

这句 sql 插入语句通过 data 方法,将修正过的数据传递给 dao 对象,然后通过 autoCheck() 对其进行自动检查。autoCheck() 会根据数据库里面字段的类型,长度进行判断。如果类型不对,或者长度不对,会自动记录错误。 

然后调用的 batchCheck() 方法,对一批字段进行非空的验证。 

当然也可以通过 check() 方法对单个字段进行验证。 

checkIF() 方法是当指定条件成立时,才进行 check 操作。batchCheckIF() 方法同理。 

例如: 

->checkIF($this->post->email != '', 'email', 'email')

其他地方的验证操作 

除了数据入库前的验证,我们平时在 control、modle 层时常也会遇到数据验证操作。此时我们可以直接通过静态调用的方式进行验证。 

例如: 

if(!empty($paramName) and !validater::checkREG($paramName, '/^[A-Za-z_0-9]+$/')) 
{ 
     dao::$errors[] = $this->lang->job->invalidName; 
     return false; 
}

call($val, $func)方法 

validater 验证类还提供了一个 call 方法,通过调用一个用户自定义函数来过滤数据。该方法为我们提供了对数据过滤的完全控制。 参数 $func 为自定义函数名,或是已有的 PHP 函数。 

示例: 

$str="Peter is a great guy!"; 
echo validater::call($str, "strtoupper");
// 输出结果:PETER IS A GREAT GUY!

五、获取验证错误 

如果数据验证过程中没有错误,则执行了exec()方法,将数据插入数据库。 

如果有错,exec()方法什么都不会执行,但会记录到错误日志中。可以在 control 层中判断是否有错误。 

if(dao::isError()) die(js::error(dao::getError()));

如果有错误,用 js 警告框的方式弹出,然后重置错误日志。 

框架在 module/common/lang 中定义了对应验证器的错误提示信息: 

/**
 * Error message. 
 * $lang->error->验证器 = "提示信息" 
 */ 
$lang->error = new stdclass();  
$lang->error->reg      = "『%s』不符合格式,应当为:『%s』。"; 
$lang->error->unique   = "『%s』已经有『%s』这条记录了。"; 
$lang->error->notempty = "『%s』不能为空。"; 
$lang->error->empty    = "『%s』必须为空。"; 
$lang->error->equal    = "『%s』必须为『%s』。"; 
$lang->error->int      = array("『%s』应当是数字。", "『%s』应当介于『%s-%s』之间。"); 
$lang->error->float    = "『%s』应当是数字,可以是小数。"; 
$lang->error->email    = "『%s』应当为合法的EMAIL。"; 
$lang->error->date     = "『%s』应当为合法的日期。"; 
$lang->error->account  = "『%s』应当为合法的用户名。";