Home > Article > Backend Development > Learning tutorial for Model model in PHP's Yii framework, yiimodel_PHP tutorial
model is part of the MVC pattern and is an object that represents business data, rules and logic.
A model is an instance of CModel or its subclass. Models are used to hold data and the business logic associated with it.
Models are separate data objects. It can be a row in a data table, or a user-entered form. Each field of the data object corresponds to an attribute in the model. Each attribute has a label and can be verified through a series of rules.
Yii implements two types of models: form model and Active Record. Both inherit from the same base class CModel.
Form models are instances of CFormModel. The form model is used to hold data obtained from the user's input. This data is often acquired, used, and then discarded. For example, in a login page, we can use the form model to represent the username and password information provided by the end user.
Active Record (AR) is a design pattern for abstracting database access in an object-oriented style. Each AR object is an instance of CActiveRecord or one of its subclasses. Represents a row in the data table. The fields in the row correspond to properties in the AR object.
Model classes can be defined by inheriting yiibaseModel or its subclasses. The base class yiibaseModel supports many practical features:
Attributes
The model represents business data through attributes. Each attribute is like a publicly accessible attribute of the model. yiibaseModel::attributes() specifies the attributes owned by the model.
A model's properties can be accessed just like an object's properties:
$model = new \app\models\ContactForm; // "name" 是ContactForm模型的属性 $model->name = 'example'; echo $model->name;
Properties can also be accessed like array cell items, thanks to yiibaseModel's support for ArrayAccess and ArrayIterator array iterators:
$model = new \app\models\ContactForm; // 像访问数组单元项一样访问属性 $model['name'] = 'example'; echo $model['name']; // 迭代器遍历模型 foreach ($model as $name => $value) { echo "$name: $value\n"; }
Define attributes
By default, your model class inherits directly from yiibaseModel, and all non-static public non-static public member variables are properties. For example, the following ContactForm model class has four attributes name, email, subject and body. The ContactForm model is used to represent input data obtained from an HTML form.
namespace app\models; use yii\base\Model; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }
Another way is to override yiibaseModel::attributes() to define attributes. This method returns the attribute name of the model. For example, yiidbActiveRecord returns the corresponding data table column name as its attribute name. Note that you may need to override magic methods such as __get(), __set() to make the attributes be accessed like ordinary object attributes.
Attribute tag
When an attribute is displayed or input is obtained, it is often necessary to display attribute-related labels. For example, assuming an attribute is named firstName, in some places such as form input or error messages, you may want to display a label that is more friendly to the end user. First Name tag.
You can call yiibaseModel::getAttributeLabel() to get the label of the attribute, for example:
$model = new \app\models\ContactForm; // 显示为 "Name" echo $model->getAttributeLabel('name');
By default, attribute labels are automatically generated from attribute names through the yiibaseModel::generateAttributeLabel() method. It will automatically convert camel case variable names into multiple words with the first letter capitalized, for example, username is converted to Username, firstName Convert to First Name.
If you don’t want to use automatically generated labels, you can override the yiibaseModel::attributeLabels() method to specify attribute labels explicitly, for example:
namespace app\models; use yii\base\Model; class ContactForm extends Model { public $name; public $email; public $subject; public $body; public function attributeLabels() { return [ 'name' => 'Your name', 'email' => 'Your email address', 'subject' => 'Subject', 'body' => 'Content', ]; } }
When the application supports multiple languages, the attribute labels can be translated and can be defined in the yiibaseModel::attributeLabels() method, as shown below:
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'), ]; }
You can even define labels based on conditions, such as by using a model's scenario to return different labels for the same property.
Supplement: Attribute labels are part of the view, but declaring labels in the model is usually very convenient and can lead to very concise and reusable code.
Scene
The model may be used in multiple scenarios. For example, the User module may collect user login input or may be used when the user registers. In different scenarios, the model may use different business rules and logic. For example, the email attribute is mandatory during registration, but not required during login.
The model uses the yiibaseModel::scenario attribute to keep track of usage scenarios. By default, the model supports a scenario named default. Two methods of setting the scenario are shown below:
// 场景作为属性来设置 $model = new User; $model->scenario = 'login'; // 场景通过构造初始化配置来设置 $model = new User(['scenario' => 'login']);
By default, the scenarios supported by the model are determined by the validation rules declared in the model, but you can customize the behavior by overriding the yiibaseModel::scenarios() method, as shown below:
namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { public function scenarios() { return [ 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], ]; } }
补充:在上述和下述的例子中,模型类都是继承yii\db\ActiveRecord, 因为多场景的使用通常发生在Active Record 类中.
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; } }
场景特性主要在验证 和 属性块赋值 中使用。 你也可以用于其他目的,例如可基于不同的场景定义不同的 属性标签。
验证规则
当模型接收到终端用户输入的数据,数据应当满足某种规则(称为 验证规则, 也称为 业务规则)。 例如假定ContactForm模型,你可能想确保所有属性不为空且 email 属性包含一个有效的邮箱地址, 如果某个属性的值不满足对应的业务规则,相应的错误信息应显示,以帮助用户修正错误。
可调用 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 属性,规则会在所有场景下应用, 在当前yii\base\Model::scenario 下应用的规则称之为 active rule活动规则。
一个属性只会属于scenarios()中定义的活动属性且在rules()申明对应一条或多条活动规则的情况下被验证。
块赋值
块赋值只用一行代码将用户所有输入填充到一个模型,非常方便, 它直接将输入数据对应填充到 yii\base\Model::attributes 属性。 以下两段代码效果是相同的,都是将终端用户输入的表单数据赋值到 ContactForm 模型的属性, 明显地前一段块赋值的代码比后一段代码简洁且不易出错。
$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;
安全属性
块赋值只应用在模型当前yii\base\Model::scenario场景yii\base\Model::scenarios()方法 列出的称之为 安全属性 的属性上,例如,如果User模型申明以下场景, 当当前场景为login时候,只有username and password 可被块赋值,其他属性不会被赋值。
public function scenarios() { return [ 'login' => ['username', 'password'], 'register' => ['username', 'email', 'password'], ]; }
补充: 块赋值只应用在安全属性上,因为你想控制哪些属性会被终端用户输入数据所修改, 例如,如果 User 模型有一个permission属性对应用户的权限, 你可能只想让这个属性在后台界面被管理员修改。
由于默认yii\base\Model::scenarios()的实现会返回yii\base\Model::rules()所有属性和数据, 如果不覆盖这个方法,表示所有只要出现在活动验证规则中的属性都是安全的。
为此,提供一个特别的别名为 safe 的验证器来申明哪些属性是安全的不需要被验证, 如下示例的规则申明 title 和 description都为安全属性。
public function rules() { return [ [['title', 'description'], 'safe'], ]; }
非安全属性
如上所述,yii\base\Model::scenarios() 方法提供两个用处:定义哪些属性应被验证,定义哪些属性安全。 在某些情况下,你可能想验证一个属性但不想让他是安全的,可在scenarios()方法中属性名加一个惊叹号 !。 例如像如下的secret属性。
public function scenarios() { return [ 'login' => ['username', 'password', '!secret'], ]; }
当模型在 login 场景下,三个属性都会被验证,但只有 username和 password 属性会被块赋值, 要对secret属性赋值,必须像如下例子明确对它赋值。
$model->secret = $secret;
数据导出
模型通常要导出成不同格式,例如,你可能想将模型的一个集合转成JSON或Excel格式, 导出过程可分解为两个步骤,第一步,模型转换成数组;第二步,数组转换成所需要的格式。 你只需要关注第一步,因为第二步可被通用的数据转换器如yii\web\JsonResponseFormatter来完成。
将模型转换为数组最简单的方式是使用 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 相同, 但是它允许你选择哪些称之为字段的数据项放入到结果数组中并同时被格式化。 实际上,它是导出模型到 RESTful 网页服务开发的默认方法,详情请参阅响应格式.
字段
字段是模型通过调用yii\base\Model::toArray()生成的数组的单元名。
默认情况下,字段名对应属性名,但是你可以通过覆盖 yii\base\Model::fields() 和/或 yii\base\Model::extraFields() 方法来改变这种行为, 两个方法都返回一个字段定义列表,fields() 方法定义的字段是默认字段,表示toArray()方法默认会返回这些字段。extraFields()方法定义额外可用字段,通过toArray()方法指定$expand参数来返回这些额外可用字段。 例如如下代码会返回fields()方法定义的所有字段和extraFields()方法定义的prettyName and fullAddress字段。
$array = $model->toArray([], ['prettyName', 'fullAddress']);
可通过覆盖 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_hash and password_reset_token。
最佳实践
模型是代表业务数据、规则和逻辑的中心地方,通常在很多地方重用, 在一个设计良好的应用中,模型通常比控制器代码多。
归纳起来,模型:
在开发大型复杂系统时应经常考虑最后一条建议, 在这些系统中,模型会很大并在很多地方使用,因此会包含需要规则集和业务逻辑, 最后维护这些模型代码成为一个噩梦,因为一个简单修改会影响好多地方, 为确保模型好维护,最好使用以下策略:
定义可被多个 应用主体 或 模块 共享的模型基类集合。 这些模型类应包含通用的最小规则集合和逻辑。
在每个使用模型的 应用主体 或 模块中, 通过继承对应的模型基类来定义具体的模型类,具体模型类包含应用主体或模块指定的规则和逻辑。
例如,在高级应用模板,你可以定义一个模型基类common\models\Post, 然后在前台应用中,定义并使用一个继承common\models\Post的具体模型类frontend\models\Post, 在后台应用中可以类似地定义backend\models\Post。 通过这种策略,你清楚frontend\models\Post只对应前台应用,如果你修改它,就无需担忧修改会影响后台应用。