前言

场景,顾名思义,就是一个情景,一种场面。在yii2中也有场景,这个场景跟你所理解的场景含义差不多。

和用户有交互的系统必不可少的功能包括收集用户数据、校验和处理。实际业务中,往往还需要将数据进行持久化存储。出于安全考虑,开发人员应当牢牢把握“客户端的输入都是不可信”的准则,客户端传过来的数据先进行过滤和清洗后再存储或传递到内部系统。

Yii2推荐使用Model类来收集和校验用户数据,持久化的ActiveRecord类是其子类。Model类的load和validate两个方法,分别用来收集和校验客户端数据。哪些数据应该被收集,哪些数据需要在什么场景下验证,便是本文的主题:场景(scenario)和验证规则(rule)。

下面话不多说了,来随着小编一起看看详细的介绍吧。

系统结构

先引入一个简单的业务系统:系统中存在学生和教师两种角色,数据库中使用了三张表保存角色信息:

user: [id, username, password, status, 其他通用属性]

student: [id, user_id, student_no, grade, class, 其他学生属性]

teacher: [id, user_id, work_no, title, telphone, 其他教师属性]

实际业务不限于对这三张表的增删查改操作。为了简化问题,后续仅讨论user和student两张表的数据变更(给出teacher表是为了让读者不认为设计数据库的人是脑残:明明可以放到一张表的,为什么要拆开!)。

学生报名

学生报名是典型的增删查改操作,送分题。学生报名的简要代码示例如下:

public function actionSignup()
{
 $data = Yii::$app->request->post();
 $user = new User();
 $user->load($data);
 if ($user->save()) {
  $student = new Student([
   "user_id" => $user->id,
  ]);
  $student->load($data);
  if ($student->save()) {
   // redirect to success page
  } else {
   $user->delete();
  }
 }
 // render error page
}

相信有Yii2使用经验的人都能根据数据库的字段约束快速的把User和Student类的rules方法写出来。例如User类文件内容可能如下:

namespace app\models;
class User extends \yii\db\ActiveRecord
{
 public function rules()
 {
  return [   [["username", "password", "status",], "required"],
   ["username", "unique"],
   // other rules
  ];
 }
 // other method
}

定义数据的验证规则,这是大多数人对rules的第一印象,并且是一个很好的印象:它打回非法的数据,让正常的数据进入系统中。安全的实践应该尽量定义完整的规则,充分验证数据。也建议每一个Yii2开发人员对内置的核心校验器熟悉。

修改信息

修改信息,也是典型的增删查改操作。实现代码和报名差别不大,这里仅讨论两点:

1、用户密码的验证

注册时会校验用户密码是否8-16位,密码的规则可能是: ["password", "string", "length" => [8, 16]] 。明文保存密码是不可取的,插入数据库时至少会做MD5加密,password变成32位。假设用户修改信息时未修改密码,再次保存时密码规则校验出错(长度不符合),无法保存!

怎么解决这个问题呢?翻阅Yii文档,发现了规则中的when属性可以救场。一种可能的验证规则是:

public function rules()
{
 return [
   ["password", "string", "length" => [8, 16], 'when' => function ($model) {
    return $model->isNewRecord;
   }],
   // other rules
  ];

只有在注册(新增数据)时才校验密码字段。问题解决,完美!

2、防止用户私自改密码

假设有个小聪明的家伙(例如汤姆),发现系统是用Yii框架做的,想搞点小破坏炫耀一下水平。在发送修改信息的表单时,汤姆增加&password=12345678这一段数据。系统使用$user->load($data)收集用户输入,更新password字段,带来如下后果:rules设置更新时不校验密码字段,12345678直接作为password的值保存到数据库中。这个操作带来连锁反应:用户再次登录时,加密过后的密码与数据库中的明文密码不匹配,导致汤姆无法登录系统。烦人的是汤姆是个刺头,登录不上后整天骚扰客服,不省心!

怎么样防止这种情况出现呢?一种解决的方法是阻止修改密码:

unset($data["password"]); 
$user->load($data);
// 或者
$password = $user->password;
$user->load($data);
$user->password = $password;

把用户输入的密码过滤掉,私自修改密码的问题就解决了。

但是问题还没有结束:汤姆可以转向修改其他字段,比如说性别,身份证等。更严重情况是修改student中user_id,就可以更改任意学生的信息。事情十分严重,需要马上修复漏洞。

可以按照密码的方法,逐个屏蔽受保护属性,但显得啰嗦难看(虽然好使)。如果受保护属性多,可以仅允许白名单进入,具体操作为:新增一个UpdateInfoForm类继承Model,属性是白名单属性合计。用UpdateInfoForm类过滤用户数据,校验通过后再更新到user和student中:

$form = new UpdateInfoForm();
$form->load($data);
if ($form->validate()) {
 $user->load($form->attributes);
 $student->load($form->attributes);
 // next biz
}

这种方式更优雅,但仔细一想代价不小:属性和验证规则要重复写一遍;user和student保存时又重复校验属性。这种方式看起来优雅,实际上却冗余又低效。

scenario的登场,完美的解决解决上述问题。

场景(scenario)

分析上面问题,会发现关键点是批量赋值(massive assignment)和数据校验(validate)两个方法。如果对不同的场景指定赋值字段和检验规则,问题就迎刃而解。

Yii中的scenario有 安全属性 和 活跃属性 两个概念。安全属性用在批量赋值的load方法,只有安全属性才能被赋值;活跃属性用在规则校验的validate方法,在活跃属性集中并且定义了校验规则的属性才会被校验。活跃属性和安全属性的关系是,安全属性是活跃属性的子集。

\yii\base\Model类定义了默认场景:SCENARIO_DEFAULT(值为default)。默认场景下,出现在rules方法中的属性既是活跃属性,又是安全属性(这句话基本正确,看后续解释)。为不同场景指定活跃属性、安全属性以及校验器,可以通过覆盖senarios或rules两个方法实现(几乎每个Model类都会重写rules方法,senarios用得少)。

rules

先看rules方法。默认的属性加校验器定义方式,让每个属性既是安全属性,也是活跃属性。如果想让某个属性不是安全属性(不能通过load批量赋值),在属性名前加感叹号!即可。例如student中的user_id字段:

public function rules()
{
 return [
  ["!user_od", "required"],
  ["!user_id", "integer"],
  ["!user_od", "unique"],
  // other rules
 ];
}

user_id是活跃属性,在写入数据库时会被校验。但它不是安全属性,不能通过load方法进行赋值,解决了安全隐患。

再看rules方法按场景区分校验器规则的做法:定义校验器时on属性指定规则在哪些场景下生效,except属性则排除一些场景(如果不指定on和except,规则对所有场景生效)。例如:

public function rules()
{
 return [
  ["password", "string", "length" => [8, 16], "on" => ["signup"]], // 仅在signup场景时才被验证
  ["status", "integer", "except" => ["signup"], // 除了signup场景,其他情况都校验
  // other rules
 ];
}

在原来基础上新增感叹号和on/except属性,非常简便的就定义了非安全属性以及分场景指定校验规则。

scenarios

另外一种更清晰定义安全属性和活跃属性的做法是重写scenarios方法。scenarios方法返回一个数组,数组的键是场景名称,值是活跃属性集合(包饭安全属性)。例如student表的可能实现如下:

public function scenarios()
{
 return [
  self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx],
  "update" => ["grade", "class", xxxx],
 ];
}

默认情形下(学生报名),年级、班级这些信息是安全属性,但user_id不是,只能在程序内部赋值,并在插入数据时被校验;在修改信息时,user_id不是活跃属性,既不能被批量赋值,也不需要校验(事实上它不应该改变)。

scenarios方法只能定义活跃属性和安全属性,无法定义校验规则,需要和rules配合使用。

总结

金肯定义完善的数据校验规则

业务复杂时定义多个场景,仔细为每个场景定义安全属性和校验规则

优先使用rules;属性较多、rules复杂时,可以配合scenarios方法迅速理清安全属性和活跃属性

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对Devmax的支持。

参考

http://www.yiiframework.com/doc-2.0/guide-input-validation.html

Yii2中的场景(scenario)和验证规则(rule)详解的更多相关文章

  1. Yii2中组件的注册与创建方法

    这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

  2. 深入浅析yii2-gii自定义模板的方法

    Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。接下来通过本文给大家介绍yii2-gii自定义模板的方法,需要的朋友参考下吧

  3. Yii2语言国际化自动配置详解

    这篇文章主要介绍了Yii2语言国际化自动配置详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. php Yii2框架创建定时任务方法详解

    Yii2是一个基于组件、用于开发大型Web应用的高性能PHP框架,采用严格的OOP编写,并有着完善的库引用以及全面的教程,该框架提供了Web 2.0应用开发所需要的几乎一切功能,是最有效率的PHP框架之一

  5. YII2框架中ActiveDataProvider与GridView的配合使用操作示例

    这篇文章主要介绍了YII2框架中ActiveDataProvider与GridView的配合使用操作,结合实例形式分析了YII2框架中ActiveDataProvider与GridView的功能及配合使用相关操作实现技巧,需要的朋友可以参考下

  6. Yii2中添加全局函数的方法分析

    这篇文章主要介绍了Yii2中添加全局函数的方法,结合实例形式对比分析了2种添加全局函数的实现技巧,需要的朋友可以参考下

  7. yii2带搜索功能的下拉框实例详解

    带搜索功能下拉框在项目中经常会用到,下面小编把实现代码分享到脚本之家平台,供大家参考

  8. 使用YII2框架实现微信公众号中表单提交功能

    刚刚接触微信就要做一个表单提交功能,需求是这样的只能在数据库中存在的手机号看到表单。下面通过本文给大家分享使用YII2框架实现微信公众号中表单提交功能,感兴趣的朋友一起看看吧

  9. Yii2单元测试用法示例

    这篇文章主要介绍了Yii2单元测试用法,结合实例形式分析了Yii2单元测试的具体实现与使用技巧,需要的朋友可以参考下

  10. yii2 上传图片的示例代码

    这篇文章主要介绍了yii2 上传图片的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

随机推荐

  1. PHP个人网站架设连环讲(一)

    先下一个OmnihttpdProffesinalV2.06,装上就有PHP4beta3可以用了。PHP4给我们带来一个简单的方法,就是使用SESSION(会话)级变量。但是如果不是PHP4又该怎么办?我们可以假设某人在15分钟以内对你的网页的请求都不属于一个新的人次,这样你可以做个计数的过程存在INC里,在每一个页面引用,访客第一次进入时将访问时间送到cookie里。以后每个页面被访问时都检查cookie上次访问时间值。

  2. PHP函数学习之PHP函数点评

    PHP函数使用说明,应用举例,精简点评,希望对您学习php有所帮助

  3. ecshop2.7.3 在php5.4下的各种错误问题处理

    将方法内的函数,分拆为2个部分。这个和gd库没有一点关系,是ecshop程序的问题。会出现这种问题,不外乎就是当前会员的session或者程序对cookie的处理存在漏洞。进过本地测试,includes\modules\integrates\ecshop.php这个整合自身会员的类中没有重写integrate.php中的check_cookie()方法导致,验证cookie时返回的username为空,丢失了登录状态,在ecshop.php中重写了此方法就可以了。把他加到ecshop.php的最后面去就可

  4. NT IIS下用ODBC连接数据库

    $connection=intodbc_connect建立数据库连接,$query_string="查询记录的条件"如:$query_string="select*fromtable"用$cur=intodbc_exec检索数据库,将记录集放入$cur变量中。再用while{$var1=odbc_result;$var2=odbc_result;...}读取odbc_exec()返回的数据集$cur。最后是odbc_close关闭数据库的连接。odbc_result()函数是取当前记录的指定字段值。

  5. PHP使用JpGraph绘制折线图操作示例【附源码下载】

    这篇文章主要介绍了PHP使用JpGraph绘制折线图操作,结合实例形式分析了php使用JpGraph的相关操作技巧与注意事项,并附带源码供读者下载参考,需要的朋友可以参考下

  6. zen_cart实现支付前生成订单的方法

    这篇文章主要介绍了zen_cart实现支付前生成订单的方法,结合实例形式详细分析了zen_cart支付前生成订单的具体步骤与相关实现技巧,需要的朋友可以参考下

  7. Thinkphp5框架实现获取数据库数据到视图的方法

    这篇文章主要介绍了Thinkphp5框架实现获取数据库数据到视图的方法,涉及thinkPHP5数据库配置、读取、模型操作及视图调用相关操作技巧,需要的朋友可以参考下

  8. PHP+jquery+CSS制作头像登录窗(仿QQ登陆)

    本篇文章介绍了PHP结合jQ和CSS制作头像登录窗(仿QQ登陆),实现了类似QQ的登陆界面,很有参考价值,有需要的朋友可以了解一下。

  9. 基于win2003虚拟机中apache服务器的访问

    下面小编就为大家带来一篇基于win2003虚拟机中apache服务器的访问。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. Yii2中组件的注册与创建方法

    这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

返回
顶部