YII2 模型概述
模型是MVC模式中的一部分,代表业务数据、规则和逻辑的对象。
可继承yii\base\Model或它的子类定义模型类。
基类包括的特性有:
1.属性:代表可像普通类属性或数组一样被访问的业务数据
2.属性标签: 指定属性显示出来的标签
3.块赋值: 支持一步给许多属性赋值
4.验证规则:确保输入数据符合所申明的验证规则
5.数据导出:允许模型数据导出为自定义格式的数组
Model类也是更多高级模型如Active Record活动记录的基类。
属性
模型通过属性来代表业务逻辑,每个属性像是模型的公有可访问属性。
attributes() 指定模型所拥有的属性。
像访问一个对象属性一样访问模型的属性:
$model = new \app\models\ContactForm; // "name" 是 ContactForm 模型的属性 $model->name = 'example'; echo $model->name;
像访问数组单元项一样访问属性,因为yii\base\Model支持 ArrayAccess 数组访问和 ArrayIterator 数组迭代器:
$model = new \app\models\ContactForm; // 像访问数组单元项一样访问属性 $model['name'] = 'example'; echo $model['name'];
// 迭代器遍历模型
foreach ($model as $name => $value) {
echo "$name: $value\n";
定义属性
所有的非静态公有成员变量都是属性。
ContactForm 模型类有四个属性
name, email, subject and body, ContactForm 模型用来代表从 HTML 表单获取的输入数据。
namespace app\models; use yii\base\Model; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }
另一种方式是覆盖yii\base\Model::attributes()来定义属性,该方法返回模型的属性名,ActiveRecord返回对应数据表列名作为属性名。
属性标签
当属性显示或获取输入时,经常要显示属性相关标签,属性名为firstName,在表单输入或者错误提示处,可改成更友好的First Name标签。
可以调用 [[yii\base\Model::getAttributeLabel()]] 获取属性的标签,例如:
$model = new \app\models\ContactForm; // 显示为 "Name" echo $model->getAttributeLabel('name')
默认情况下,属性标签会自动从属性名生成,自动将驼峰式大小写变量名转换为多个首字母大写的单词。
如果你不想用自动生成的标签,可以覆盖 [[yii\base\Model::attributeLabels()]] 方法明确指定属性标签
public function attributeLabels() { return [ 'name' => 'Your name', 'email' => 'Your email address', 'subject' => 'Subject', 'body' => 'Content', ]; }
应用支持多语言的情况下,可翻译属性标签, 可在[[yii\base\Model::attributeLabels()|attributeLabels()]] 方法中定义,如下所示:
public function attributeLabels() { return [ 'name' => \Yii::t('app', 'Your name'), 'email' => \Yii::t('app', 'Your email address'), 'subject' => \Yii::t('app', 'Subject'), 'body' => \Yii::t('app', 'Content'), ]; }
还可以根据条件定义标签,例如通过使用模型的scenario场景,对相同的属性返回不同的标签。
场景
模型可能在多个场景下使用,User模块可能在收集用户登录输入,也可能会在用户注册时使用。在不同的场景下,模型可能会使用不同的业务规则和逻辑,例如email属性在注册时强制要求有,但在登录时不需要。
模型使用yii\base\Model::scenario 属性保持使用场景的跟踪,默认情况下,模型支持一个名为default的场景。
设置场景的方法:
// 场景作为属性来设置 $model = new User; $model->scenario = 'login'; // 场景通过构造初始化配置来设置 $model = new User(['scenario' => 'login']);
默认情况下,模型支持的场景由模型中申明的验证规则来决定,但可以通过覆盖yii\base\Model::scenarios(),来自定义行为。
namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { public function scenarios() { return [ 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], ]; } }
scenarios() 方法返回一个数组,数组的键为场景名,值为对应的 active attributes 活动属性。
活动属性可被 块赋值 并遵循验证规则在上述例子中,username 和 password 在 login 场景中启用,在 register 场景中, 除了 username and password 外 email 也被启用。
scenarios() 方法默认实现会返回所有[[yii\base\Model::rules()]]方法申明的验证规则中的场景, 当覆盖 scenarios()时,如果你想在默认场景外使用新场景,可以编写类似如下代码:
namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { public function scenarios() { $scenarios = parent::scenarios(); $scenarios['login'] = ['username', 'password']; $scenarios['register'] = ['username', 'email', 'password']; return $scenarios; } }
场景特性主要在验证 和 属性块赋值 中使用。 你也可以用于其他目的,例如可基于不同的场景定义不同的 属性标签。
验证规则
当模型接收到终端用户输入的数据,数据应当满足某种规则(称为 验证规则, 也称为 业务规则)。
可调用 [[yii\base\Model::validate()]] 来验证接收到的数据, 该方法使用yii\base\Model::rules()申明的验证规则来验证每个相关属性, 如果没有找到错误,会返回 true,否则它会将错误保存在 [[yii\base\Model::errors]] 属性中并返回 false
$model = new \app\models\ContactForm; // 用户输入数据赋值到模型属性 $model->attributes = \Yii::$app->request->post('ContactForm'); if ($model->validate()) { // 所有输入数据都有效 all inputs are valid } else { // 验证失败:$errors 是一个包含错误信息的数组 $errors = $model->errors; }
通过覆盖 [[yii\base\Model::rules()]] 方法指定模型属性应该满足的规则来申明模型相关验证规则。下述例子显示 ContactForm 模型申明的验证规则:
public function rules() { return [ // name, email, subject 和 body 属性必须有值 [['name', 'email', 'subject', 'body'], 'required'], // email 属性必须是一个有效的电子邮箱地址 ['email', 'email'], ]; }
一条规则可用来验证一个或多个属性,一个属性可对应一条或多条规则。
有时你想一条规则只在某个 场景 下应用,为此你可以指定规则的 on 属性,如下所示:
public function rules() { return [ // 在"register" 场景下 username, email 和 password 必须有值 [['username', 'email', 'password'], 'required', 'on' => 'register'], // 在 "login" 场景下 username 和 password 必须有值 [['username', 'password'], 'required', 'on' => 'login'], ]; }
如果没有指定 on 属性,规则会在所有场景下应用,一个属性只会属于 scenarios()中定义的活动属性且在 rules()申明对应一条或多条活动规则的情况下被验证。
块赋值
块赋值只用一行代码将用户所有输入填充到一个模型,非常方便, 它直接将输入数据对应填充到yii\base\Model::attributes 属性。
$model = new \app\models\ContactForm; $model->attributes = \Yii::$app->request->post('ContactForm');
$model = new \app\models\ContactForm; $data = \Yii::$app->request->post('ContactForm', []); $model->name = isset($data['name']) ? $data['name'] : null; $model->email = isset($data['email']) ? $data['email'] : null; $model->subject = isset($data['subject']) ? $data['subject'] : null; $model->body = isset($data['body']) ? $data['body'] : null;
安全属性
块赋值只应用在模型当前场景列出的称之为安全属性的属性上,User模型申明的以下场景,当当前场景为login时候,只有username和password可被块赋值,其他属性不会被块赋值。
public function scenarios() { return [ 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], ]; }
非安全属性
如上所述,[[yii\base\Model::scenarios()]] 方法提供两个用处:定义哪些属性应被验证,定义哪些属性安全。 在某些情况下,你可能想验证一个属性但不想让他是安全的,可在 scenarios()方法中属性名加一个惊叹号 !。 例如像如下的 secret 属性。
public function scenarios() { return [ 'login' => ['username', 'password', '!secret'], ]; }
当模型在 login 场景下,三个属性都会被验证,但只有 username 和 password 属性会被块赋值, 要对 secret 属性赋值,必须像如下例子明确对它赋值。
$model->secret = $secret;
数据导出
将模型转换为数组最简单的方式是使用 yii\base\Model::attributes 属性,例如:
$post = \app\models\Post::findOne(100); $array = $post->attributes;
yii\base\Model::attributes 属性会返回 所有 yii\base\Model::attributes() 申明的属性的值。
更灵活和强大的将模型转换为数组的方式是使用 [[yii\base\Model::toArray()]] 方法, 它的行为默认和 [[yii\base\Model::attributes]] 相同, 但是它允许你选择哪些称之为字段的数据项放入到结果数组中并同时被格式化。
字段
字段是模型通过调用[[yii\base\Model::toArray()]]生成的数组的单元名
默认情况下,字段名对应属性名,但是你可以通过覆盖 [[yii\base\Model::fields()|fields()]] 和/或[[yii\base\Model::extraFields()|extraFields()]] 方法来改变这种行为, 两个方法都返回一个字段定义列表,fields() 方法定义的字段是默认字段,表示 toArray()方法默认会返回这些字段。 extraFields()方法定义额外可用字段,通过 toArray()方法指定$expand 参数来返回这些额外可用字段。
可通过覆盖 fields() 来增加、删除、重命名和重定义字段,fields() 方法返回值应为数组, 数组的键为字段名,数组的值为对应的可为属性名或匿名函数返回的字段定义对应的值。 特使情况下,如果字段名属性定义名相同,可以省略数组键,例如:
// 明确列出每个字段,特别用于你想确保数据表或模型属性改变不会导致你的字段改变(保证后端的 API 兼容). public function fields() { return [ // 字段名和属性名相同 'id', // 字段名为 "email",对应属性名为 "email_address" 'email' => 'email_address', // 字段名为 "name", 值通过 PHP 代码返回 'name' => function () { return $this->first_name . ' ' . $this->last_name; }, ]; } // 过滤掉一些字段,特别用于你想继承父类实现并不想用一些敏感字段 public function fields() { $fields = parent::fields(); // 去掉一些包含敏感信息的字段 unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']); return $fields; }
由于模型的所有属性会被包含在导出数组,最好检查数据确保没包含敏感数据, 如果有敏感数据,应覆盖 fields() 方法过滤掉,在上述列子中,我们选择过滤掉 auth_key, password_hashand password_reset_token。
•可包含属性来展示业务数据;
•可包含验证规则确保数据有效和完整;
•可包含方法实现业务逻辑;
•不应直接访问请求,session 和其他环境数据,这些数据应该由控制器传入到模型;
•应避免嵌入 HTML 或其他展示代码,这些代码最好在 视图中处理;
•单个模型中避免太多的 场景.
为确保模型好维护,最好使用以下策略:
•定义可被多个 应用主体 或 模块 共享的模型基类集合。 这些模型类应包含通用的最小规则集合和逻辑。
•在每个使用模型的 应用主体 或 模块中, 通过继承对应的模型基类来定义具体的模型类,具体模型类包含应用主体或模块指定的规则和逻辑。