尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Yii2 模型(Model)完全指南:属性、场景、验证规则与数据导出的源码级剖析

发布时间:2026/9/24 4:24:20

资讯中心
01
ARTICLE

Yii2 模型(Model)完全指南:属性、场景、验证规则与数据导出的源码级剖析

Yii2 模型(Model)完全指南:属性、场景、验证规则与数据导出的源码级剖析
后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载Model是 Yii2 框架中 MVC 架构的核心组件承载业务数据、业务规则与业务逻辑。本指南以官方文档 structure-models.md 为主线结合framework/base/Model.php、framework/base/ArrayableTrait.php的源码实现与 ModelTest.php 测试用例系统讲解如何定义模型属性、声明属性标签、使用多场景Scenario、编写验证规则Validation Rules、执行批量赋值Massive Assignment以及将模型导出为数组。读完本文你将掌握 Yii2 模型层的完整用法并能从源码层面理解安全属性活跃属性字段Field等关键概念背后的设计动机从而写出更安全、更易维护的模型代码。模型在 MVC 架构中的定位模型是 MVCModel-View-Controller 架构的一部分它们是代表业务数据、业务规则和业务逻辑的对象。在 Yii2 中你可以通过继承[[yii\base\Model]]或其子类来创建模型类。基类yii\base\Model提供了以下开箱即用的能力属性Attributes代表业务数据既可以像普通对象属性一样访问也可以像数组元素一样访问属性标签Attribute Labels为属性指定在界面展示时使用的友好名称批量赋值Massive Assignment用一行代码同时填充多个属性验证规则Validation Rules基于声明的规则确保输入数据的有效性数据导出Data Exporting将模型数据按可定制的格式导出为数组。Model类同时也是更高级模型如 Active Record的基类。[[yii\base\Model]]并不强制要求所有模型都必须继承它但由于 Yii2 的众多组件表单组件、验证器、数据提供器等都围绕它构建官方强烈建议将其作为模型的默认基类。Info本文及官方文档中的示例大量使用yii\db\ActiveRecord子类是因为多场景等高级用法通常出现在 Active Record 类中但本文讲述的所有机制属性、标签、场景、验证、批量赋值、导出在普通yii\base\Model子类上同样适用。属性Attributes属性的本质与访问方式模型通过属性Attribute表示业务数据每个属性都相当于模型的一个公开可访问的成员。方法[[yii\base\Model::attributes()]]定义了模型类拥有哪些属性。属性可以像普通对象属性一样访问$model new \app\models\ContactForm; // name 是 ContactForm 的一个属性 $model-name example; echo $model-name;得益于yii\base\Model对 ArrayAccess 中的getIterator()、offsetExists()、offsetGet()、offsetSet()、offsetUnset()属性还可以像数组元素一样读写并且模型可以直接被foreach遍历$model new \app\models\ContactForm; // 以数组元素方式访问属性 $model[name] example; echo $model[name]; // 遍历模型的所有属性 foreach ($model as $name $value) { echo $name: $value\n; }如何定义属性默认情况下如果你的模型类直接继承自yii\base\Model那么该类中所有非静态的 public 成员变量都会成为属性。例如下面的ContactForm模型拥有四个属性name、email、subject和body它通常用于表示从 HTML 表单接收的输入数据namespace app\models; use yii\base\Model; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }查看源码 Model.php 可以确认这一默认行为attributes()通过 PHP 反射ReflectionClass::getProperties(ReflectionProperty::IS_PUBLIC)遍历类的所有 public 属性并过滤掉静态属性public function attributes() { $class new ReflectionClass($this); $names []; foreach ($class-getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { if (!$property-isStatic()) { $names[] $property-getName(); } } return $names; }你也可以重写attributes()以其他方式定义属性该方法只需返回模型中的属性名列表。例如yii\db\ActiveRecord就是通过返回关联数据库表的列名作为属性名来实现的即列即属性。注意如果以这种方式定义属性通常还需要重写__get()和__set()等魔术方法使属性可以像普通对象属性一样被访问。属性标签Attribute Labels在展示属性值或为属性收集输入时往往需要显示与属性关联的标签。例如属性名为firstName你可能希望在表单输入框和错误消息中展示对终端用户更友好的First Name。调用[[yii\base\Model::getAttributeLabel()]]可以获取属性标签$model new \app\models\ContactForm; // 显示 Name echo $model-getAttributeLabel(name);标签的自动生成默认情况下属性标签由属性名自动生成生成逻辑位于[[yii\base\Model::generateAttributeLabel()]]。源码 Model.php 显示它实际委托给Inflector::camel2words($name, true)将 camelCase 风格的变量名拆分为多个单词并把每个单词首字母大写。例如username变成UsernamefirstName变成First Namedepartment_name也会变成Department Name。getAttributeLabel()的完整逻辑Model.php是先在attributeLabels()的返回数组中查找显式声明若未找到才回退到自动生成public function getAttributeLabel($attribute) { $labels $this-attributeLabels(); return isset($labels[$attribute]) ? $labels[$attribute] : $this-generateAttributeLabel($attribute); }显式声明标签如果不希望使用自动生成的标签可以重写[[yii\base\Model::attributeLabels()]]来显式声明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, ]; } }多语言标签对于支持多语言的应用程序可以直接在attributeLabels()中使用\Yii::t()进行国际化翻译完整的 i18n 机制参见 tutorial-i18n.md 或 guide 下的英文版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为同一个属性返回不同的标签。Info严格来说属性标签属于视图Views 的职责范畴。但在模型中声明标签通常非常方便且能使代码更简洁、更易复用因此成为 Yii2 的主流实践。场景Scenarios一个模型可能被用于不同的场景。例如User模型既可用于收集用户登录输入也可用于用户注册。在不同场景下模型可能使用不同的业务规则与业务逻辑——比如email属性在注册时是必填的在登录时却不是。模型通过[[yii\base\Model::scenario]]属性跟踪它当前所处的场景。默认情况下模型只支持一个名为default的场景即常量SCENARIO_DEFAULT。设置场景有两种等价方式// 方式一作为属性设置 $model new User; $model-scenario login; // 方式二通过构造函数配置 $model new User([scenario login]);为便于维护官方英文版文档推荐用类常量替代字符串字面量例如User::SCENARIO_LOGIN定义常量const SCENARIO_LOGIN login;。场景的默认推导逻辑默认情况下模型支持的场景由模型中声明的验证规则推导而来。查看源码 Model.php 中scenarios()的默认实现可以看到完整推导流程初始化default场景遍历所有验证器getValidators()收集每个验证器on/except属性中出现的场景名对每个验证器根据其on仅指定场景生效、except排除指定场景或两者皆空所有场景生效三种情况把验证器关联的属性分别归入对应场景最终返回场景名 活跃属性数组的映射。因此只要你通过rules()声明了带on的规则对应场景就会被自动创建这正是默认场景由验证规则决定的源码依据。重写 scenarios()你可以重写[[yii\base\Model::scenarios()]]来定制场景及其活跃属性Active Attributes。scenarios()返回一个数组键为场景名值为该场景下对应的活跃属性。活跃属性可以被批量赋值并且需要接受验证。例如namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { const SCENARIO_LOGIN login; const SCENARIO_REGISTER register; public function scenarios() { return [ self::SCENARIO_LOGIN [username, password], self::SCENARIO_REGISTER [username, email, password], ]; } }上述例子中username和password是login场景的活跃属性而在register场景中email也是活跃属性。在重写scenarios()时如果你想在默认场景之外新增场景而不是完全替换默认实现推导出的场景需要先调用父类实现再合并例如namespace app\models; use yii\db\ActiveRecord; class User extends ActiveRecord { const SCENARIO_LOGIN login; const SCENARIO_REGISTER register; public function scenarios() { $scenarios parent::scenarios(); $scenarios[self::SCENARIO_LOGIN] [username, password]; $scenarios[self::SCENARIO_REGISTER] [username, email, password]; return $scenarios; } }场景机制主要用于验证和批量赋值但也可用于其他目的例如根据当前场景返回不同的属性标签。验证规则Validation Rules当模型的数据来自终端用户时必须经过验证以确保其满足特定规则即验证规则也称业务规则。例如对于ContactForm模型你可能希望确保所有属性均非空且email属性是合法的电子邮件地址。当某些属性的值不满足对应业务规则时应显示合适的错误消息帮助用户修正。validate() 的调用与流程调用[[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()) { // 所有输入均有效 } else { // 验证失败$errors 是包含错误消息的数组 $errors $model-errors; }查看 validate() 的源码 可以理解完整的执行链路默认先调用clearErrors()清空旧错误可通过$clearErrors false关闭触发beforeValidate事件beforeValidate()若返回false则验证中止获取当前场景并检查它是否存在于scenarios()中若当前场景未知会抛出InvalidArgumentException对应测试 testValidateWithUnknownScenario若未指定属性名则取当前场景的activeAttributes()遍历getActiveValidators()返回的活跃验证器逐个调用$validator-validateAttributes($this, $attributeNames)触发afterValidate事件返回!$this-hasErrors()。验证错误可通过getErrors()返回二维数组属性 错误消息数组、getFirstErrors()每个属性仅第一条错误和getFirstError($attribute)获取。声明验证规则要声明与模型关联的验证规则需要重写[[yii\base\Model::rules()]]返回模型属性必须满足的规则。下面的示例展示了为ContactForm声明的验证规则public function rules() { return [ // name、email、subject 和 body 均为必填 [[name, email, subject, body], required], // email 必须是合法的电子邮件地址 [email, email], ]; }一条规则可以校验一个或多个属性一个属性也可以被一条或多条规则校验。关于如何声明各种验证规则内建验证器、行内验证器、自定义验证器、when条件等详见 input-validation.md。从源码 createValidators() 可以看出rules()返回的每条规则都会被转换为一个yii\validators\Validator对象规则必须是Validator实例或满足[0]为属性、[1]为验证器类型的数组格式否则抛出InvalidConfigException。子类若需继承父类的规则应使用array_merge()合并父类规则。按场景限定规则有时你希望某条规则只在特定场景中生效。此时可以为规则指定on属性public function rules() { return [ // username、email 和 password 在 register 场景中均为必填 [[username, email, password], required, on register], // username 和 password 在 login 场景中均为必填 [[username, password], required, on login], // 未指定 on 的规则在所有场景中生效 [[username], string], ]; }如果未指定on属性规则将在所有场景中生效。能够应用于当前场景的规则称为活跃规则Active Rule。结合scenarios()的源码可知属性只有在同时满足以下两个条件时才会被验证它是scenarios()中当前场景声明的活跃属性它与rules()中一条或多条活跃规则关联。批量赋值Massive Assignment批量赋值是一种用一行代码将用户输入填充到模型中的便捷方式。它通过把输入数据直接赋给[[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;除了直接给$attributes赋值更常用的做法是调用[[yii\base\Model::load()]]源码。load()会根据formName()返回的表单名默认是类名去掉命名空间的短类名见 formName() 源码从$_POST/$_GET等数据数组中取出对应子数组并执行批量赋值内部同样经过setAttributes()的安全检查if ($model-load(\Yii::$app-request-post()) $model-validate()) { // 处理成功提交 }此外还有loadMultiple()批量填充多个模型常用于表格行输入和validateMultiple()批量验证多个模型两个静态工具方法均位于 Model.php。安全属性Safe Attributes批量赋值只作用于所谓的安全属性Safe Attributes——即[[yii\base\Model::scenarios()]]中当前场景所列出的属性。例如如果User模型声明了如下场景那么当当前场景为login时只有username和password可以被批量赋值其他所有属性都会被忽略public function scenarios() { return [ self::SCENARIO_LOGIN [username, password], self::SCENARIO_REGISTER [username, email, password], ]; }Info批量赋值只作用于安全属性的原因是你需要控制哪些属性可以被终端用户数据修改。例如如果User模型有一个决定用户权限的permission属性你必然希望该属性只能由管理员通过后台界面修改而绝不能让普通用户通过表单提交来篡改。由于scenarios()的默认实现会从rules()中推导出所有场景及属性只要属性出现在某条活跃验证规则中它默认就是安全的。setAttributes()的源码清楚地展示了这一机制默认$safeOnly true仅当属性名存在于safeAttributes()返回值中时才被赋值否则调用onUnsafeAttribute()源码在YII_DEBUG开启时会记录调试日志。对应地safeAttributes()的源码会跳过!前缀的属性并返回安全属性列表activeAttributes()的源码则返回当前场景下需要验证的活跃属性会去掉!前缀。这两个方法的分工正是验证哪些属性与允许批量赋值哪些属性两件事的分离点。为此框架专门提供了别名为safe的特殊验证器让你可以把属性声明为安全属性而不实际验证它。例如下面的规则把title和description都声明为安全属性public function rules() { return [ [[title, description], safe], ]; }isAttributeSafe($attribute)源码可用于在代码中判断某属性当前是否安全。非安全属性Unsafe Attributes如前所述scenarios()方法承担两个职责决定哪些属性需要验证、决定哪些属性是安全的。在少数情况下你可能希望验证某个属性但又不把它标记为安全属性。此时可以在scenarios()中为该属性名加上感叹号前缀!如下例中的secret属性public function scenarios() { return [ self::SCENARIO_LOGIN [username, password, !secret], ]; }当模型处于login场景时三个属性都会被验证但只有username和password可以被批量赋值。要为secret属性赋值必须显式地写$model-secret $secret;同样的技巧也可以在rules()中实现public function rules() { return [ [[username, password, !secret], required, on login], ]; }此时username、password和secret均为必填但secret必须显式赋值。相关行为在测试 testSetAttributesUnsafeIsIgnored、testIsAttributeSafe 与 testActiveAttributes 中均有覆盖。数据导出Data Exporting模型经常需要导出为各种格式例如把一组模型转换为 JSON 或 Excel。导出过程可以拆分为两个相互独立的步骤将模型转换为数组将数组转换为目标格式。你只需关注第一步因为第二步可以由通用的数据格式化器完成例如yii\web\JsonResponseFormatter。使用 $attributes 属性导出把模型转换为数组的最简单方式是使用[[yii\base\Model::$attributes]]属性$post \app\models\Post::findOne(100); $array $post-attributes;默认情况下$attributes属性会返回attributes()声明的所有属性的值对应源码getAttributes()Model.php。使用 toArray() 导出更灵活、更强大的方式是调用[[yii\base\Model::toArray()]]。它的默认行为与$attributes一致但它允许你选择将哪些数据项称为字段Field放入结果数组以及如何格式化它们。事实上它是 RESTful Web 服务开发中导出模型的默认方式见 rest-response-formatting.md。toArray()的实现在yii\base\ArrayableTrait中ArrayableTrait.php核心流程是通过resolveFields()把请求的字段与fields()/extraFields()的声明合并解析为字段名 定义映射resolveFields() 源码若定义是字符串则取该属性值若是闭包则调用它若$recursive为true嵌套的Arrayable对象会被递归转换为数组字段名支持用点号item.field嵌套选择。字段Fields字段Field就是调用toArray()所得到数组中的一个具名元素。默认情况下字段名与属性名一一对应。但你可以通过重写[[yii\base\Model::fields()]]和/或[[yii\base\Model::extraFields()]]来改变这一行为。两者都应返回字段定义列表fields()定义的是默认字段即toArray()默认返回的字段extraFields()定义的是额外可用字段只有通过$expand参数显式指定时才会被返回。例如下面的代码会返回fields()定义的所有字段再加上extraFields()中定义的prettyName和fullAddress字段若存在$array $model-toArray([], [prettyName, fullAddress]);重写fields()可以新增、删除、重命名或重新定义字段。fields()的返回值必须是数组键为字段名值为对应的字段定义——可以是属性名也可以是返回字段值的匿名函数。特殊情况下当字段名与定义它的属性名一致时可以省略数组键。例如// 显式列出每个字段——最适合用于确保数据库表或模型属性的变更不会导致 API 字段变化保持向后兼容 public function fields() { return [ // 字段名与属性名相同 id, // 字段名为 email对应的属性名为 email_address email email_address, // 字段名为 name其值由匿名函数定义 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()的默认实现Model.php返回attributes()并以同名索引即默认导出全部属性extraFields()的默认实现ArrayableTrait.php返回空数组。此外从 Model.php 的 fields() 文档注释 可以看到你还可以根据场景或当前用户权限等上下文信息返回不同的字段集合例如为普通用户过滤掉敏感字段。Warning因为默认情况下模型的所有属性都会被包含在导出的数组中你必须检查你的数据确保其中不包含敏感信息。如果存在此类信息应重写fields()将其过滤掉。上例中就特意过滤了auth_key、password_hash和password_reset_token三个字段。最佳实践Best Practices模型是表示业务数据、业务规则和业务逻辑的核心位置它们经常需要在不同地方被复用。在设计良好的应用中模型通常比控制器更胖。总体而言模型应该可以包含用于表示业务数据的属性可以包含用于确保数据有效性和完整性的验证规则可以包含实现业务逻辑的方法不应该直接访问请求Request、会话Session或其他环境数据——这些数据应由控制器注入模型应该避免内嵌 HTML 或其他表现层代码——这更适合放在视图Views 中应该避免在单个模型中定义过多场景。最后一条建议尤其适用于大型复杂系统在这些系统中模型可能因为被多处使用而变得非常胖包含大量规则和业务逻辑这往往导致维护噩梦——一次小小的代码改动就可能影响到多个不同的地方。为了让模型代码更易维护可以采取如下策略定义一组被不同应用Applications或模块Modules共享的基础模型类。这些基础模型类应包含所有使用场景中共同的最小规则集和逻辑集在每个使用该模型的应用或模块中定义继承自对应基础模型类的具体模型类。具体模型类只包含该应用或模块特有的规则和逻辑。例如在 Yii2 高级项目模板Advanced Project Template中你可以定义一个基础模型类common\models\Post然后为 front-end 应用定义并使用继承自它的具体模型类frontend\models\Post类似地为 back-end 应用定义backend\models\Post。采用这种策略你可以确信frontend\models\Post中的代码只对 front-end 应用有效修改它时不必担心会破坏 back-end 应用。源码与测试对照进一步阅读本文所有结论均可直接在仓库中验证建议按以下路径深入模型基类实现framework/base/Model.phpattributes()、attributeLabels()、scenarios()、rules()、validate()、setAttributes()、safeAttributes()、activeAttributes()、fields()、load()等全部核心方法数组导出实现framework/base/ArrayableTrait.phptoArray()、fields()、extraFields()、resolveFields()与 framework/base/Arrayable.php 接口测试用例tests/framework/base/ModelTest.php涵盖testSetAttributes、testActiveAttributes、testIsAttributeSafe、testValidateWithUnknownScenario、testFields、testSetAttributesUnsafeIsIgnored、testValidateMultiple等关键行为以及 tests/framework/base/DynamicModelTest.phpyii\base\DynamicModel动态模型见 framework/base/DynamicModel.php可在不定义类的情况下临时创建带验证规则的模型适合在控制器中做轻量数据校验进阶主题验证器全览见 tutorial-core-validators.md表单输入处理见 input-validation.md基于表的模型见 db-active-record.mdREST 响应导出见 rest-response-formatting.md。掌握属性、场景、验证与导出这四大机制你就抓住了 Yii2 模型层的核心。它们协同工作共同构成了 Yii2 中数据入批量赋值 验证与数据出导出与序列化的完整闭环也是后续理解 Active Record、表单组件yii\widgets\ActiveForm与 REST 控制器的前提。赞分享后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载相关推荐Yii2 模型Model完全指南属性、场景、验证、批量赋值与数据导出的源码级实战详解Yii2 模型Model完全指南属性、场景、验证、批量赋值与数据导出的源码级实战详解 导读 本文基于 Yii2 官方指南的「Models模型」章节后端Web框架Yii 2 模型Model完全指南属性、场景、验证与数据导出的源码级解析Yii 2 模型Model完全指南属性、场景、验证与数据导出的源码级解析 模型Model是 Yii 2 MVC 架构中承载业务数据、规则与逻辑的核心类后端Web框架Yii 2 模型Model完全指南属性、场景、验证规则与数据导出的深度解析Yii 2 模型Model完全指南属性、场景、验证规则与数据导出的深度解析 本文以 docs/guide ja/structure models.md h后端Web框架创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。