第二层内功 - 表单模型 ¶
作者:KK
发表日期:2017.2.18
已经有许多略有经验的程序员表示“我不愿意再在控制器里堆代码,我觉得应该增加一个逻辑层负责业务逻辑,控制器只管调用和传递中间信息,再由逻辑层调用模型,模型调DB……以便实现逻辑层被多个控制器重用和测试”
我也是支持逻辑层思想的程序员之一,而在Yii2框架中与逻辑层对应的则是由表单模型
来实现,也就可以这么通俗地说:Yii2里的逻辑层就是表单模型
基础示例 ¶
class LoginForm extends \yii\base\Model{
public $username = '';
public $password = '';
public function login(){
$user = \app\model\User::findOne(['username' => $this->username]);
if(!$user){
$this->addError('username', '无效的用户名或密码');
return false;
}
if($user->password != md5($password)){
$this->addError('username', '无效的用户名或密码');
return false;
}
return Yii::$app->user->login($user);
}
}
这是一个打算用在登录功能上的表单模型,其中login方法就是登录的逻辑处理,为了简单演示我省略了用户名和密码长度、类型的校验之类的详细处理
然后表单模型通常都让控制器来调用的,控制器的代码可以这样写:
public function actionLogin(){
$form = new LoginForm();
$form->username = Yii::$app->request->post('username');
$form->username = Yii::$app->request->post('password');
if($form->login()){
$errorMessage = current($form->firstErrors)[0];
//成功的结果响应
}else{
//失败的结果响应
}
}
4个基本要点 ¶
使用表单模型要注意下面4个要点:
-
一般都提供public方法作为业务逻辑处理的方法(像上面的login方法)
一个方法处理一个业务请求,可以有多个业务,也就是多个public方法
业务处理过程中,需要的客户端输入参数默认不直接从$_GET、$_POST数据获取,而是访问自身的属性(像上面的$username和$password两个属性)
而客户端输入的参数由控制器从GET、POST里获取,让控制器来传递给表单模型
这样就实现了:表单模型不依赖GET、POST数据,降低了依赖,只要能传来特定参数就能执行业务
也就实现了这个逻辑层可以用在更多场合(比如控制台、第三方接口、后台管理前台用户、模拟登录等)
业务处理过程中,如果对输入数据校验失败,报错的办法应该用
$this->addError('自定义错误标识', '错误消息');
并返回false给控制器当然也能返回0呀null呀什么的,只要跟控制器协商好什么表示失败就行了
重要的是,控制器如果要获取失败的消息提示,可以通过
$form->firstError[0]
这个代码获取,在入门级的文章里不解释这个东西,知道怎么用就是
自动校验数据 ¶
客户端用户要操作应用的业务,比如要登录,就要输入用户名和密码啦,那后端就要校验一下是不是string,长度又是否满足啦,其实这些校验级别的事,表单模型有一套方法可以快速自动完成,例子:
class LoginForm extends \yii\base\Model{
public $username = '';
public $password = '';
//这个方法是我要说的重点!!!
public function rules(){
return [
['username', 'required'], //第1条校验规则
['password', 'required'], //第2条校验规则
];
}
public function login(){
$user = \app\model\User::findOne(['username' => $this->username]);
if(!$user){
$this->addError('username', '无效的用户名或密码');
return false;
}
if($user->password != md5($password)){
$this->addError('username', '无效的用户名或密码');
return false;
}
return Yii::$app->user->login($user);
}
}
上面通过rules方法返回了一个数组,这个数组代表了一套校验规则
每个数组元素就是1条规则,其中这里有2条规则,第1条的意思是说“$this的username属性不能没有或为空”,第二条规则就是说password属性也不能为空
此时控制器试下跑这样的代码:
$form = new LoginForm();
var_dump($form->validate()); // false
echo current($form->firstErrors)[0]; // username 不能为空
其实表单模型还有一个validate方法
,这个方法会按照rules方法返回的规则进行数据校验(就是校验自己的属性)
上面返回false就是说校验不通过啦,为什么不通过呢?压根就是直接new了模型,可是用户名和密码默认就是空的,所以校验规则就通不过去了;而你试试赋一下非空值就能过了
校验规则的基本要素 ¶
其实就像上面的,第1个元素是要校验的属性名称,必须是public的属性
第2个元素就是要求特征,required这个特征表示“必须有的”
public function rules(){
return [
[属性名称//规则1
[属性名称//规则2
[属性名称//规则3……
];
}
校验规则并不是只声明required就行的 ¶
$form = new LoginForm();
$form->username = [json_encode('Jay')]; //这回赋值了
$form->username = '123456';
var_dump($form->validate()); // true
var_dump($form->login()); // false 重点
validate是通过了,因为数据都非空嘛,但login还是返回了false,因为username必须是个字符串,而上面却传了个数组进去!
所以说校验规则并不是只声明required就行的,正确姿势:
public function rules(){
return [
['username', 'required'],
['password', 'required'],
['username', 'string', 'length' => [2, 10], 'tooShort' => '用户名至少2个字符啊亲', 'tooLong' => '用户名最多10个字符呀亲!'],
['password', 'string', 'length' => [6, 16], 'tooShort' => '密码由6到16个字符组成', 'tooLong' => '密码由6到16个字符组成'],
];
}
以上rules方法就多了2个规则了,相信根据那些字面含义也猜得到用意是啥,我就不详解了
这样如果传入的username或password不是string类型就会validate失败
就算是string也要满足length的限制,否则就会根据情况返回tooShort或tooLong的错误消息
说白了就是把我们平时写的if判断换成了一定的规则来表达,免除写代码的麻烦,如果你愿意,自己在login方法里慢慢写校验代码都行,所以这个校验规则你可用可不用
然而Yii2表单模型的一大亮点恰恰就是它有校验规则,如果不用这些规则,你自己怎么另外实现逻辑层基本都没问题,个人觉得Yii2的还是挺好用的
关于rules方法的数组都能定义哪些校验规则,如何描述定义,下一篇验证器专门讲
快速填充数据 ¶
上面的例子中我new了一个Form后,进行了类似这样的代码赋值:
$form->username = 'abc';
$form->password = 'abc123';
这是为了将数据传给模型,然后模型才能把数据拿去校验,如果模型并没有得到数据,则根本没有可以校验的东西,所以有校验规则都是没用的
但你想哦,有些表单会有很多要填的东西,那后端不就要写很多赋值代码?其实有快速赋值的方法:
$_POST = [
'username' => 'abc',
'password' => 'abc123',
];
$form->load($_POST, '');
//输出
echo $form->username; // abc
echo $form->password; // abc123
很容易懂吧,用load方法就能把一个数组快速赋值到模型属性里了,前提是数组的key跟模型的属性命名是一模一样的才可以。
在这种用法下顺带一提,load方法的第2个参数必须是空字符串,如果按照官方权威指南所说的用ActiveForm来实现前端的话才不需要传第2个参数,这个你可以暂时不必关心
变通点,实际上咱们的代码应该这样写:
if(!$form->load(Yii::$app->request->post(), '')){
return '接收参数失败';
}
if(!$form->validate()){
return $form->firstError[0]; //校验失败的错误消息
}
if($form->login()){
return '登录成功';
}else{
return '登录失败';
}
可以多个属性共享一条规则 ¶
上面例子中,为了声明username和password是required的就定义了2条规则,其实可以这样挤在同一条规则里
本来规则的第一个值是字符串,描述一个自身的public属性嘛,多属性共享规则时,它就要变成数组了:
public function rules(){
return [
[['username', 'password'], 'required'], //第一个数组元素是重点,变成数组了
['username', 'string', 'length' => [2, 10], 'tooShort' => '用户名至少2个字符啊亲', 'tooLong' => '用户名最多10个字符呀亲!'],
['password', 'string', 'length' => [6, 16], 'tooShort' => '密码由6到16个字符组成', 'tooLong' => '密码由6到16个字符组成'],
];
}
这样第1条规则就是两个属性共享了,都说是required的
只是string特征的校验规则里由于大家的长度和报错消息不同,所以只能单独各自定义
完整的基础示例 ¶
class LoginForm extends \yii\base\Model{
public $username = '';
public $password = '';
public function rules(){
return [
[['username', 'password'], 'required'],
['username', 'string', 'length' => [2, 10], 'tooShort' => '用户名至少2个字符啊亲', 'tooLong' => '用户名最多10个字符呀亲!'],
['password', 'string', 'length' => [6, 16], 'tooShort' => '密码由6到16个字符组成', 'tooLong' => '密码由6到16个字符组成'],
];
}
public function login(){
if(!$this->validate()){
//不需要addError,因为validate里面会自动addError
return false;
}
$user = \app\model\User::findOne(['username' => $this->username]);
if(!$user){
$this->addError('username', '无效的用户名或密码');
return false;
}
if($user->password != md5($password)){
$this->addError('username', '无效的用户名或密码');
return false;
}
return Yii::$app->user->login($user);
}
}
控制器代码(和开始的一样,只是模型加了校验规则,login里执行了validate):
public function actionLogin(){
$form = new LoginForm();
$form->username = Yii::$app->request->post('username');
$form->username = Yii::$app->request->post('password');
if($form->login()){
$errorMessage = $form->firstError[0];
//成功的结果响应
}else{
//失败的结果响应
}
}
表单模型就是前端表单的后端版本体现 ¶
为什么叫表单模型呢?其实细心点想想,前端要做登录通常就会有一个表单(form)
表单里又有username和password两个输入项,就对应表单模型的那两个属性
用户输入后,提交时,前端又会用JS代码先做一些基本的输入格式校验,不通过就提示用户;这里后端表单模型对应的就是用校验规则进行格式校验,不通过也返回错误消息
如果通过了才可以执行login业务的实体逻辑
所以你只要学会“把表单模型想像成前端表单”就很容易掌握这个东西
精华:这是一种业务模型,模型可以跟数据库无关 ¶
- 提示:理解我下面说的意思,你就抛离大部分菜鸟程序员一段距离了
网上已经很多老程序员都发文表示模型与数据库无关,但很多新手还是停在“模型就是操作数据库的工具”这个理解阶段
前面我已经发表过Yii2的AR模型文章,那是一个便于操作数据库的模型,称为AR模型
这里是一个仅仅用于处理业务的模型,称为表单模型
其实模型是分成多个不同级别的
AR层的模型继承了yii\db\BaseActiveRecord
,而yii\db\BaseActiveRecord
其实又是继承了基础模型yii\base\Model
于是其实AR模型也能定义rules,也能validate,那主要是对数据库字段做校验的,可以翻一下官方文章和demo看看
模型模型,就是一个模型:比如表单模型,它是一个业务或表单的体现方式;再比如AR模型,它是数据库表里面某一条记录的体现方式
你想像一下,有个月饼模,它就是一个模型,把月饼材料放上去,一盖就出一个月饼,一个月饼就是一个模型实例,没有new的时候,它就只是一个模
可以有很多个不同的登录业务,所以能new很多个不同的LoginForm;可以有很多条不同的记录,于是也能findOne出很多个AR模型实例……
以我的能力也只能这么解释了,希望大家能明白