模型类是数据模型的基类.此类继承了组件类,实现了3个接口

  1. 先介绍一下模型类前面的大量注释说了什么:
  2.  
  3. * 模型类是数据模型的基类.此类继承了组件类,实现了3个接口
  4. * 实现了IteratorAggregate(聚合式迭代器)接口,实现了ArrayAccess接口,可以像数组一样访问对象,这两个接口是php自带
  5. * Arrayable接口是yii2框架自带
  6. * 模型实现了以下常用功能:
  7. *
  8. * - 属性声明: 默认情况下,每个公共类成员都被认为是模型属性
  9. * - 属性标签: 每个属性可以与用于显示目的的标签相关联。
  10. * - 大量的属性分配
  11. * - 基于场景的验证
  12. *
  13. * 在执行数据验证时,模型还引发下列事件:
  14. *
  15. * - [[EVENT_BEFORE_VALIDATE]]: 在开始时提出的事件 [[validate()]]
  16. * - [[EVENT_AFTER_VALIDATE]]: 结束时提出的事件[[validate()]]
  17. *
  18. * 您可以直接使用模型存储模型数据, 或延长定制.
  1. <?php
  2. /**
  3. * @property \yii\validators\Validator[] $activeValidators 使用场景效验[[scenario]],此属性是只读的
  4. * @property array $attributes 属性值键值对方式 (name => value).
  5. * @property array $errors 所有属性的错误数组. 数组为空表示没有错误. 结果是一个二维数组
  6. * [[getErrors()]]获取详细错误信息,此属性是只读的
  7. * @property array $firstErrors 第一个错误,数组的键是属性名, 数组的值是错误信息,空数组表示没有错误,此属性只读
  8. * @property ArrayIterator $iterator 遍历列表中的项的迭代器,此属性只读.
  9. * @property string $scenario 模型所在的场景.默认是[[SCENARIO_DEFAULT]].
  10. * @property ArrayObject|\yii\validators\Validator[] $validators 在模型中定义的所有方法,此属性只读.
  11. *
  12. * @author Qiang Xue <qiang.xue@gmail.com>
  13. * @since 2.0
  14. */
  15. class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
  16. {
  17. //自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits,此处就使用了trait
  18. use ArrayableTrait;
  19.  
  20. /**
  21. * 默认场景名
  22. */
  23. const SCENARIO_DEFAULT = 'default';
  24. /**
  25. * @event 模型事件先被调用验证,调用[[validate()]]这个方法. You may set
  26. * 可以设置[[ModelEvent::isValid]] 的isValid属性为false来阻止验证.
  27. */
  28. const EVENT_BEFORE_VALIDATE = 'beforeValidate';
  29. /**
  30. * @event 验证[[validate()]]后执行的事件
  31. */
  32. const EVENT_AFTER_VALIDATE = 'afterValidate';
  33.  
  34. /**
  35. * @var array 存放验证错误的数组,键是属性名,值是有关错误的数组 (attribute name => array of errors)
  36. */
  37. private $_errors;
  38. /**
  39. * @var ArrayObject list 验证器集合
  40. */
  41. private $_validators;
  42. /**
  43. * @var string 当前场景
  44. */
  45. private $_scenario = self::SCENARIO_DEFAULT;
  46.  
  47. /**
  48. * 返回属性的验证规则.
  49. *
  50. * 验证规则通过 [[validate()]] 方法检验属性是否是有效的
  51. * 子类应该覆盖这个方法来声明不同的验证规则
  52. *
  53. * 每个验证规则都是下列结构的数组:
  54. *
  55. * ```php
  56. * [
  57. * ['attribute1', 'attribute2'],
  58. * 'validator type',
  59. * 'on' => ['scenario1', 'scenario2'],
  60. * //...other parameters...
  61. * ]
  62. * ```
  63. *
  64. * where
  65. *
  66. * - 属性集合: 必选, 指定要验证的属性数组, 对于单个属性,你可以直接传递字符串;
  67. * - 验证类型: 必选, 指定要使用的验证. 它可以是一个内置验证器的名字,
  68. * 模型类的方法名称, 匿名函数, 或验证器类的名称.
  69. * - on: 可选参数, 是一个数组,表示在指定场景使用,没设置,表示应用于所有的场景
  70. * - 额外的名称-值对可以指定初始化相应的验证特性.
  71. *
  72. * 一个验证器可以是一个类的对象延伸扩展[[Validator]], 或模型类方法*(*内置验证器*),具有以下特征:
  73. *
  74. * ```php
  75. * // $params 引用给验证规则的参数
  76. * function validatorName($attribute, $params)
  77. * ```
  78. *
  79. * 上面的 `$attribute` 指当前正在验证的属性 。。。
  80. * `$params` 包含一个数组验证配置选项,例如:当字符串验证时的max属性,验证当前的属性值
  81. * 可以访问为 `$this->$attribute`. 注意 `$` before `attribute`; 这是取变量$attribute的值和使用它作为属性的名称访问
  82. *
  83. * Yii提供了一套[[Validator::builtInValidators|built-in validators]].
  84. * 每一个都有别名,可以在指定验证规则时使用
  85. *
  86. * 看下面的一些例子:
  87. *
  88. * ```php
  89. * [
  90. * // 内置 "required" 验证器
  91. * [['username', 'password'], 'required'],
  92. * // 内置 "string" 验证器 用长度区间定制属性
  93. * ['username', 'string', 'min' => 3, 'max' => 12],
  94. * // 内置 "compare" 验证器,只能在 "register" 场景中使用
  95. * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
  96. * // 一个内置验证器 "authenticate()"方法被定义在模型类里
  97. * ['password', 'authenticate', 'on' => 'login'],
  98. * // 一个验证器的类 "DateRangeValidator"
  99. * ['dateRange', 'DateRangeValidator'],
  100. * ];
  101. * ```
  102. *
  103. * 注意,为了继承定义在父类中的规则, 一个子类应该使用函数合并父类的规则,例如array_merge()这个函数
  104. *
  105. * @return array validation rules返回验证规则数组
  106. * @see scenarios()
  107. */
  108. public function rules()
  109. {
  110. return [];
  111. }
  112.  
  113. /**
  114. * 返回一个场景列表和每个场景对应的属性,此属性是活动属性
  115. * 一个场景中的属性只在当前场景中被验证
  116. * 返回的数组应该是下列格式的:
  117. *
  118. * ```php
  119. * [
  120. * 'scenario1' => ['attribute11', 'attribute12', ...],
  121. * 'scenario2' => ['attribute21', 'attribute22', ...],
  122. * ...
  123. * ]
  124. * ```
  125. *
  126. * 默认情况下,活动属性被认为是安全的,并且可以被赋值
  127. * 如果一个属性不应该被赋值 (因此认为不安全),
  128. * 请用感叹号前缀属性 (例如: `'!rank'`).
  129. *
  130. * 此方法默认返回声明中的所有属性 [[rules()]]
  131. * 一个特殊的场景被称为默认场景[[SCENARIO_DEFAULT]] 将会包含在rules()规则里的所有的属性
  132. * 每个场景将与正在应用于场景的验证规则进行验证的属性关联
  133. *
  134. * @return array 返回一个数组和相应的属性列表
  135. */
  136. public function scenarios()
  137. {
  138. //在场景数组里先把默认场景放入
  139. $scenarios = [self::SCENARIO_DEFAULT => []];
  140. //获取所有场景迭代,getValidators()获取关于验证类对象的数组
  141. foreach ($this->getValidators() as $validator) {
  142. //$validator->on的值是数组,获得所有应用的场景名字
  143. foreach ($validator->on as $scenario) {
  144. $scenarios[$scenario] = [];
  145. }
  146. //$validator->on的值是数组,获得当前不使用的场景的名字
  147. foreach ($validator->except as $scenario) {
  148. $scenarios[$scenario] = [];
  149. }
  150. }
  151. //$names获得了所有的场景
  152. $names = array_keys($scenarios);
  153.  
  154. foreach ($this->getValidators() as $validator) {
  155. if (empty($validator->on) && empty($validator->except)) {
  156. //on为空数组,except也是空数组,表示验证规则中没有设置场景,则把验证规则运用到所有的场景
  157. foreach ($names as $name) {
  158. foreach ($validator->attributes as $attribute) {
  159. //把所有模型验证属性添加到每个场景
  160. $scenarios[$name][$attribute] = true;
  161. }
  162. }
  163. } elseif (empty($validator->on)) {
  164. //on为空,except不为空,表示除了except场景外,应用于所有的场景
  165. foreach ($names as $name) {
  166. if (!in_array($name, $validator->except, true)) {
  167. //找到不在except中的场景,放进场景属性数组,表示在其它场景中验证
  168. foreach ($validator->attributes as $attribute) {
  169. $scenarios[$name][$attribute] = true;
  170. }
  171. }
  172. }
  173. } else {
  174. //on不为空,在on场景中验证这些属性
  175. foreach ($validator->on as $name) {
  176. foreach ($validator->attributes as $attribute) {
  177. $scenarios[$name][$attribute] = true;
  178. }
  179. }
  180. }
  181. }
  182.  
  183. //使每个场景名对应一个属性数组,$scenarios的键是场景名
  184. foreach ($scenarios as $scenario => $attributes) {
  185. if (!empty($attributes)) {
  186. $scenarios[$scenario] = array_keys($attributes);
  187. }
  188. }
  189. //场景名对应属性名的数组
  190. return $scenarios;
  191. }
  192.  
  193. /**
  194. * 返回表单的名称,就是这个 model 的类名.
  195. *
  196. * 在一个模型中表单的name值经常被使用在 [[\yii\widgets\ActiveForm]] 决定如何命名属性的输入字段
  197. * 如果表单的name值是A,表单元素属性名是b,则表单元素的name值为"A[b]"
  198. * 如果表单的name值为空字符串,则表单元素的name为"b"
  199. *
  200. * 上述命名模式的目的是针对包含多个不同模型的表单,比较容易区分不同模型的不同属性
  201. * 每个模型的属性被分组在后的数据的子数组中,它是更容易区分它们。
  202. *
  203. * 默认情况下,此方法返回模型类名 (不包含命名空间)
  204. * 你可以覆盖此方法,当一个表单中有多个模型时
  205. *
  206. * @return string the form name of this model class.模型类的名字
  207. * @see load()
  208. */
  209. public function formName()
  210. {
  211. //ReflectionClass是php中的扩展反射类
  212. $reflector = new ReflectionClass($this);
  213. //getShortName()返回不带命名空间的类名
  214. return $reflector->getShortName();
  215. }
  216.  
  217. /**
  218. * 返回一个属性列表
  219. * 默认情况下,此方法返回类的所有公共非静态属性。
  220. * 可以覆盖此方法返回你想要的属性
  221. * @return array list of attribute names.
  222. */
  223. public function attributes()
  224. {
  225. //获取这个类的相关信息
  226. $class = new ReflectionClass($this);
  227. $names = [];
  228. //遍历这个类的每一个属性,如果这个属性是公共的,就把它放入name数组中
  229. foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  230. //不是public并且不是static
  231. if (!$property->isStatic()) {
  232. $names[] = $property->getName();
  233. }
  234. }
  235.  
  236. return $names;
  237. }
  238.  
  239. /**
  240. * 返回属性的标签
  241. *
  242. * 属性标签主要用于显示. 例如, `firstName`属性将会显示成`First Name`标签,可以有友好的展示给终端用户
  243. *
  244. * 默认的标签生成是使用 [[generateAttributeLabel()]]这个方法
  245. * 此方法允许您显式指定属性标签.
  246. *
  247. * 注意,为了继承父类中定义的标签, 子类标签可以使用`array_merge()`与父类标签合并
  248. *
  249. * @return array attribute labels (name => label)
  250. * @see generateAttributeLabel()
  251. */
  252. public function attributeLabels()
  253. {
  254. return [];
  255. }
  256.  
  257. /**
  258. * 返回属性提示
  259. *
  260. * 属性提示主要用于显示. 例如,`isPublic`这个属性可以用来描述“未登录用户的帖子是否应该可见”
  261. * 它提供了用户友好的描述属性的含义,并可以显示给最终用户.
  262. *
  263. * 如果省略了显式声明,则不会生成标记提示
  264. *
  265. * 注意,为了继承父类中定义的标签, 子类标签可以使用`array_merge()`与父类标签合并.
  266. *
  267. * @return array attribute hints (name => hint)
  268. * @since 2.0.4
  269. */
  270. public function attributeHints()
  271. {
  272. return [];
  273. }
  274.  
  275. /**
  276. * 执行数据验证.
  277. *
  278. * 此方法执行适用于当前场景的验证规则 [[scenario]].
  279. * 下列标准用于判断规则是否适用:
  280. *
  281. * - 规则必须与当前场景相关的属性关联;
  282. * - 规则在所处的情况下必须是有效的,
  283. *
  284. * validate()在执行前会先执行 [[beforeValidate()]] ,
  285. * validate()执行后会执行 [[afterValidate()]]
  286. * 如果[[beforeValidate()]] 返回false,接下来的验证将被取消
  287. *
  288. * 验证期间发现的错误可以通过 [[getErrors()]],[[getFirstErrors()]] and [[getFirstError()]]获得错误信息,
  289. *
  290. * @param array $attributeNames 一个应该被验证的属性列表
  291. * 若$attributeNames 为空,这意味着在适用的验证规则中列出的任何属性都应该经过验证。
  292. * @param boolean $clearErrors 表示在执行验证前是否先清除错误 [[clearErrors()]]
  293. * @return boolean 验证是否成功无任何错误.
  294. * @throws InvalidParamException 不知道当前场景时会抛出异常.
  295. */
  296. public function validate($attributeNames = null, $clearErrors = true)
  297. {
  298. if ($clearErrors) {
  299. //清除所有的错误
  300. $this->clearErrors();
  301. }
  302.  
  303. if (!$this->beforeValidate()) {
  304. //没通过before验证就返回false
  305. return false;
  306. }
  307. //返回当前活动的场景
  308. $scenarios = $this->scenarios();
  309. //返回此模型应用的场景
  310. $scenario = $this->getScenario();
  311. if (!isset($scenarios[$scenario])) {
  312. //若当前活动的场景不在此模型中,抛出异常
  313. throw new InvalidParamException("Unknown scenario: $scenario");
  314. }
  315.  
  316. if ($attributeNames === null) {
  317. //属性数组为空,自动查找当前场景下的安全属性,并返回这些属性
  318. $attributeNames = $this->activeAttributes();
  319. }
  320.  
  321. //$this->getActiveValidators()返回Validator对象数组
  322. foreach ($this->getActiveValidators() as $validator) {
  323. //通过Validator对象验证属性
  324. $validator->validateAttributes($this, $attributeNames);
  325. }
  326. //验证的后置方法
  327. $this->afterValidate();
  328. //没有错误就返回真
  329. return !$this->hasErrors();
  330. }
  331.  
  332. /**
  333. * 在验证前被验证
  334. * 默认的实现提出了一个` beforevalidate `的事件
  335. * 验证之前,您可以重写此方法进行初步检查。
  336. * 请确保调用父实现,然后就可以引发此事件。
  337. * @return boolean是否应执行接下来的验证,默认是真
  338. * 如果返回false,则验证将停止,该模型被认为是无效的
  339. */
  340. public function beforeValidate()
  341. {
  342. //这个不说了ModelEvent里一个方法都木有
  343. $event = new ModelEvent;
  344. $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
  345.  
  346. return $event->isValid;
  347. }
  348.  
  349. /**
  350. * 验证执行后被调用
  351. * 废话不多说了,可以覆盖,记得调用父类方法
  352. */
  353. public function afterValidate()
  354. {
  355. $this->trigger(self::EVENT_AFTER_VALIDATE);
  356. }
  357.  
  358. /**
  359. * 返回声明在 [[rules()]]中的验证.
  360. *
  361. * 此方法和 [[getActiveValidators()]] 不同,[[getActiveValidators()]]只返回当前场景的验证
  362. *
  363. * 由于该方法返回一个数组对象的对象,你可以操纵它通过插入或删除验证器(模型行为的有用)。
  364. * For example,
  365. *
  366. * ```php
  367. * $model->validators[] = $newValidator;
  368. * ```
  369. *
  370. * @return ArrayObject|\yii\validators\Validator[] 返回在模型中定义的所有验证器
  371. */
  372. public function getValidators()
  373. {
  374. if ($this->_validators === null) {
  375. $this->_validators = $this->createValidators();
  376. }
  377. return $this->_validators;
  378. }
  379.  
  380. /**
  381. * Returns the validators applicable to the current [[scenario]].
  382. * @param string $attribute the name of the attribute whose applicable validators should be returned.
  383. * If this is null, the validators for ALL attributes in the model will be returned.
  384. * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
  385. */
  386. public function getActiveValidators($attribute = null)
  387. {
  388. $validators = [];
  389. $scenario = $this->getScenario();
  390. foreach ($this->getValidators() as $validator) {
  391. if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
  392. $validators[] = $validator;
  393. }
  394. }
  395. return $validators;
  396. }
  397.  
  398. /**
  399. * 根据 [[rules()]]里的验证规则创建一个验证对象.
  400. * 和 [[getValidators()]]不一样, 每次调用此方法,一个新的列表验证器将返回。
  401. * @return ArrayObject validators
  402. * @throws InvalidConfigException 如果任何验证规则配置无效,抛出异常
  403. */
  404. public function createValidators()
  405. {
  406. $validators = new ArrayObject;
  407. foreach ($this->rules() as $rule) {
  408. //遍历规则中的每一项
  409. if ($rule instanceof Validator) {
  410. ///如果规则属于Validator对象,添加入数组对象
  411. $validators->append($rule);
  412. } elseif (is_array($rule) && isset($rule[0], $rule[1])) {
  413. //如果子规则是数组,创建一个验证器类,把验证的类型,模型,属性名,验证属性的初始值传入
  414. $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
  415. //把创建的对象加入数组对象
  416. $validators->append($validator);
  417. } else {
  418. //抛出规则必须包含属性名和验证类型
  419. throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
  420. }
  421. }
  422. return $validators;
  423. }
  424.  
  425. /**
  426. * 检查属性是否在当前场景中被应用
  427. * This is determined by checking if the attribute is associated with a
  428. * [[\yii\validators\RequiredValidator|required]] validation rule in the
  429. * current [[scenario]].
  430. *
  431. * 注意,当确认有条件验证的应用,使用
  432. * [[\yii\validators\RequiredValidator::$when|$when]] 这个方法将会返回
  433. * `false` 不管 `when` 条件, 因为它可能在模型加载数据前被调用
  434. *
  435. * @param string $attribute 属性名
  436. * @return boolean whether the attribute is required
  437. */
  438. public function isAttributeRequired($attribute)
  439. {
  440. foreach ($this->getActiveValidators($attribute) as $validator) {
  441. if ($validator instanceof RequiredValidator && $validator->when === null) {
  442. return true;
  443. }
  444. }
  445. return false;
  446. }
  447.  
  448. /**
  449. * 返回一个值,该值指示属性是否是安全的
  450. * @param string $attribute 属性名
  451. * @return boolean whether the attribute is safe for massive assignments
  452. * @see safeAttributes()
  453. */
  454. public function isAttributeSafe($attribute)
  455. {
  456. //判断属性是否在数组里,true表示全等(数值和类型都相等)
  457. return in_array($attribute, $this->safeAttributes(), true);
  458. }
  459.  
  460. /**
  461. * 返回一个值,该值指示当前场景中的属性是否处于活动状态。
  462. * @param string $attribute 属性名
  463. * @return boolean whether the attribute is active in the current scenario
  464. * @see activeAttributes()
  465. */
  466. public function isAttributeActive($attribute)
  467. {
  468. return in_array($attribute, $this->activeAttributes(), true);
  469. }
  470.  
  471. /**
  472. * 返回指定属性的文本标签
  473. * @param string $attribute 属性名
  474. * @return string 属性标签
  475. * @see generateAttributeLabel()
  476. * @see attributeLabels()
  477. */
  478. public function getAttributeLabel($attribute)
  479. {
  480. //获得所有属性标签,并给$lable这个数组
  481. $labels = $this->attributeLabels();
  482. //如果这个方法被子类重写了,直接返回自定义的标签,如果没有被重写,返回默认的
  483. return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
  484. }
  485.  
  486. /**
  487. * 返回指定属性的文本提示
  488. * @param string $attribute 属性名
  489. * @return string the attribute hint
  490. * @see attributeHints()
  491. * @since 2.0.4
  492. */
  493. public function getAttributeHint($attribute)
  494. {
  495. $hints = $this->attributeHints();
  496. //如果这个方法被子类重写了,直接返回自定义的文本提示,如果没有被重写,返回''
  497. return isset($hints[$attribute]) ? $hints[$attribute] : '';
  498. }
  499.  
  500. /**
  501. * 返回一个值,该值指示是否有任何验证错误
  502. * @param string|null $attribute attribute name. Use null to check all attributes.
  503. * @return boolean whether there is any error.
  504. */
  505. public function hasErrors($attribute = null)
  506. {
  507. //如果有错,_errors[$attribute]有值
  508. return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
  509. }
  510.  
  511. /**
  512. * 返回所有属性或单个属性的错误。
  513. * @param string $attribute attribute name. 使用NULL检索所有属性的错误
  514. * @property array An array of errors for all attributes. 其结果是二维数组,空数组意味着没有错误
  515. * See [[getErrors()]] for detailed description.
  516. * @return array 返回一个或多个属性所指定的错误。
  517. *
  518. * ```php
  519. * [
  520. * 'username' => [
  521. * 'Username is required.',
  522. * 'Username must contain only word characters.',
  523. * ],
  524. * 'email' => [
  525. * 'Email address is invalid.',
  526. * ]
  527. * ]
  528. * ```
  529. *
  530. * @see getFirstErrors()
  531. * @see getFirstError()
  532. */
  533. public function getErrors($attribute = null)
  534. {
  535. if ($attribute === null) {
  536. //如果属性为空,返回所有属性的验证结果
  537. return $this->_errors === null ? [] : $this->_errors;
  538. } else {
  539. //属性不为空,验证单个属性返回的错误结果
  540. return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
  541. }
  542. }
  543.  
  544. /**
  545. * 返回模型中每个属性的第一个错误
  546. * @return array the first errors. 数组的键是属性名, 数组的值是错误信息
  547. * 没有错误就返回空数组
  548. * @see getErrors()
  549. * @see getFirstError()
  550. */
  551. public function getFirstErrors()
  552. {
  553. if (empty($this->_errors)) {
  554. //没有错误,返回空数组
  555. return [];
  556. } else {
  557. $errors = [];
  558. //遍历所有属性的第一个错误,放进数组
  559. foreach ($this->_errors as $name => $es) {
  560. if (!empty($es)) {
  561. $errors[$name] = reset($es);
  562. }
  563. }
  564.  
  565. return $errors;
  566. }
  567. }
  568.  
  569. /**
  570. * 返回指定属性的第一个错误。
  571. * @param string $attribute 属性名
  572. * @return string 错误信息,空意味着没有错误信息
  573. * @see getErrors()
  574. * @see getFirstErrors()
  575. */
  576. public function getFirstError($attribute)
  577. {
  578. //如果这个属性的错误在验证时存在,则返回这个错误,否则返回null
  579. return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
  580. }
  581.  
  582. /**
  583. * 向指定属性添加新错误
  584. * @param string $attribute attribute name
  585. * @param string $error new error message
  586. */
  587. public function addError($attribute, $error = '')
  588. {
  589. //把错误信息添加入指定属性的数组
  590. $this->_errors[$attribute][] = $error;
  591. }
  592.  
  593. /**
  594. * 添加错误列表
  595. * @param array $items 错误信息列表. 数组的键必须是属性的名字
  596. * 数组的值是错误信息. 如果一个属性有很多错误,则错误需要是数组的形式,
  597. * 你可以使用 [[getErrors()]] 作为此参数的值
  598. * @since 2.0.2
  599. */
  600. public function addErrors(array $items)
  601. {
  602. //遍历属性和对应的错误
  603. foreach ($items as $attribute => $errors) {
  604. if (is_array($errors)) {
  605. foreach ($errors as $error) {
  606. $this->addError($attribute, $error);
  607. }
  608. } else {
  609. $this->addError($attribute, $errors);
  610. }
  611. }
  612. }
  613.  
  614. /**
  615. * 删除所有属性或单个属性的错误。
  616. * @param string $attribute attribute name. 使用NULL删除所有属性的错误。
  617. */
  618. public function clearErrors($attribute = null)
  619. {
  620. if ($attribute === null) {
  621. //如果没传属性,把所有的错误清除掉
  622. $this->_errors = [];
  623. } else {
  624. //删除对应属性的错误
  625. unset($this->_errors[$attribute]);
  626. }
  627. }
  628.  
  629. /**
  630. * 根据给定属性名称生成用户友好的属性标签。
  631. * 这是通过用空格替换下划线、破折号和圆点,并将每个单词的第一个字母替换为大写字母。
  632. * @param string $name the column name
  633. * @return string the attribute label
  634. */
  635. public function generateAttributeLabel($name)
  636. {
  637. //camel2words定义了一个正则来替换字符串
  638. return Inflector::camel2words($name, true);
  639. }
  640.  
  641. /**
  642. * 返回属性值
  643. * @param array $names 返回需要的属性列表
  644. * 默认为null, 意味着定义在 [[attributes()]] 里的所有属性都会被返回
  645. * 如果是数组,则只返回数组中的属性
  646. * @param array $except 不应返回值的属性列表
  647. * @return array attribute values (name => value).
  648. */
  649. public function getAttributes($names = null, $except = [])
  650. {
  651. $values = [];
  652. if ($names === null) {
  653. //$names为null,$names设置成所有的属性
  654. $names = $this->attributes();
  655. }
  656. foreach ($names as $name) {
  657. $values[$name] = $this->$name;
  658. }
  659. foreach ($except as $name) {
  660. //不返回哪个属性,从数组中删除哪个属性
  661. unset($values[$name]);
  662. }
  663.  
  664. return $values;
  665. }
  666.  
  667. /**
  668. * 以大量的方式设置属性值。
  669. * @param array $values 属性以 (name => value) 被分配到模型
  670. * @param boolean $safeOnly 赋值是否只对安全的属性进行
  671. * 安全属性是与当前中的验证规则关联的属性,定义在[[scenario]]中.
  672. * @see safeAttributes()
  673. * @see attributes()
  674. */
  675. public function setAttributes($values, $safeOnly = true)
  676. {
  677. if (is_array($values)) {
  678. //array_flip交换数组中的键和值,$safeOnly为true返回安全属性,否则返回所有的属性
  679. $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
  680. foreach ($values as $name => $value) {
  681. if (isset($attributes[$name])) {
  682. // 如果存在该属性,就直接赋值
  683. $this->$name = $value;
  684. } elseif ($safeOnly) {
  685. // 如果不存在,而且是 safeOnly 的话,就触发一下 onUnsafeAttribute 方法
  686. $this->onUnsafeAttribute($name, $value);
  687. }
  688. }
  689. }
  690. }
  691.  
  692. /**
  693. * 当一个不安全的属性被赋值时调用此方法
  694. * 如果是在yii_debug,默认实现会记录一个警告消息
  695. * @param string $name the unsafe attribute name
  696. * @param mixed $value the attribute value
  697. */
  698. public function onUnsafeAttribute($name, $value)
  699. {
  700. if (YII_DEBUG) {
  701. //debug模式,警告信息出现
  702. Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
  703. }
  704. }
  705.  
  706. /**
  707. * 返回在模型中应用的场景
  708. *
  709. * 场景影响如何进行验证,哪些属性可以大量分配。
  710. *
  711. * @return string the scenario that this model is in. 默认场景是 [[SCENARIO_DEFAULT]].
  712. */
  713. public function getScenario()
  714. {
  715. return $this->_scenario;
  716. }
  717.  
  718. /**
  719. * 为模型设置场景
  720. * 注意,此方法不检查场景是否存在
  721. * [[validate()]]会检查场景是否存在.
  722. * @param string $value 再模型中的场景名.
  723. */
  724. public function setScenario($value)
  725. {
  726. $this->_scenario = $value;
  727. }
  728.  
  729. /**
  730. * 返回当前场景中大量分配的安全的属性名称
  731. * @return string[] safe attribute names
  732. */
  733. public function safeAttributes()
  734. {
  735. // 获取当前的场景
  736. $scenario = $this->getScenario();
  737. // 获取所有场景及其属性
  738. $scenarios = $this->scenarios();
  739. if (!isset($scenarios[$scenario])) {
  740. // 场景不存在,就返回空
  741. return [];
  742. }
  743. $attributes = [];
  744. foreach ($scenarios[$scenario] as $attribute) {
  745. // 将开头不是!的属性才会放入到 $attributes 中
  746. if ($attribute[0] !== '!' && !in_array('!' . $attribute, $scenarios[$scenario])) {
  747. $attributes[] = $attribute;
  748. }
  749. }
  750.  
  751. return $attributes;
  752. }
  753.  
  754. /**
  755. * 返回当前场景中要受验证的属性名称
  756. * @return string[] 返回安全的属性名称
  757. */
  758. public function activeAttributes()
  759. {
  760. $scenario = $this->getScenario();
  761. $scenarios = $this->scenarios();
  762. if (!isset($scenarios[$scenario])) {
  763. return [];
  764. }
  765. // 获取当前场景中的所有属性
  766. $attributes = $scenarios[$scenario];
  767. foreach ($attributes as $i => $attribute) {
  768. if ($attribute[0] === '!') {
  769. // 如果属性名以!开头,就把!截取掉,并放入数组
  770. $attributes[$i] = substr($attribute, 1);
  771. }
  772. }
  773.  
  774. return $attributes;
  775. }
  776.  
  777. /**
  778. * 填充模型的输入数据(把数据加载到模型中)
  779. *
  780. * 这种方法提供了一个方便快捷的方式:
  781. *
  782. * ```php
  783. * if (isset($_POST['FormName'])) {
  784. * $model->attributes = $_POST['FormName'];
  785. * if ($model->save()) {
  786. * // handle success
  787. * }
  788. * }
  789. * ```
  790. *
  791. * 如果使用load方法
  792. *
  793. * ```php
  794. * if ($model->load($_POST) && $model->save()) {
  795. * // handle success
  796. * }
  797. * ```
  798. *
  799. * `load()` gets the `'FormName'` from the model's [[formName()]] method (which you may override), unless the
  800. * `$formName` parameter is given. If the form name is empty, `load()` populates the model with the whole of `$data`,
  801. * instead of `$data['FormName']`.
  802. *
  803. * 注意,被填充的数据将会接受[[setAttributes()]]的安全检查.
  804. *
  805. * @param array $data 模型加载的数组, 典型的是 `$_POST` or `$_GET`.
  806. * @param string $formName 用于将数据加载到模型中的表单名称
  807. * If not set, [[formName()]] is used.
  808. * @return boolean whether `load()` found the expected form in `$data`.
  809. */
  810. public function load($data, $formName = null)
  811. {
  812. //如果没有传表单名称,就取所在类的名称
  813. $scope = $formName === null ? $this->formName() : $formName;
  814. if ($scope === '' && !empty($data)) {
  815. //如果 $scope 为空字符串,且 $data不为空,就设置属性
  816. $this->setAttributes($data);
  817.  
  818. return true;
  819. } elseif (isset($data[$scope])) {
  820. // 存在 $data[$scope],使用 $data[$scope] 去设置属性
  821. $this->setAttributes($data[$scope]);
  822.  
  823. return true;
  824. } else {
  825. return false;
  826. }
  827. }
  828.  
  829. /**
  830. * 从终端用户获取数据,形成模型
  831. * 该方法主要用于收集表格数据输入
  832. * 为每个模型加载的数据`$data[formName][index]`, where `formName`
  833. * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
  834. * If [[formName()]] is empty, `$data[index]` 将用于填充每个模型.
  835. * 每个模型的数据都要经过安全检查 [[setAttributes()]].
  836. * @param array $models 要填充的模型 注意所有的模型都应该有相同的类
  837. * @param array $data the data array. 通常是 `$_POST` or `$_GET`, 也可以是最终用户提供的任何有效数组。
  838. * @param string $formName 要把数据加载到模型的表单名
  839. * If not set, it will use the [[formName()]] value of the first model in `$models`.
  840. * This parameter is available since version 2.0.1.
  841. * @return boolean whether at least one of the models is successfully populated.
  842. */
  843. public static function loadMultiple($models, $data, $formName = null)
  844. {
  845. if ($formName === null) {
  846. /* @var $first Model */
  847. //reset — 将数组的内部指针指向第一个单元
  848. $first = reset($models);
  849. if ($first === false) {
  850. // 不存在就返回 false
  851. return false;
  852. }
  853. // 拿到所在类的名称
  854. $formName = $first->formName();
  855. }
  856.  
  857. $success = false;
  858. // 遍历 $models,一个个 load 数据
  859. foreach ($models as $i => $model) {
  860. /* @var $model Model */
  861. if ($formName == '') {
  862. if (!empty($data[$i])) {
  863. // 数据不为空,就 load 到相应的 model 中
  864. $model->load($data[$i], '');
  865. $success = true;
  866. }
  867. } elseif (!empty($data[$formName][$i])) {
  868. // 存在 $formName,且数据不为空,就 load 到相应的 model 中
  869. $model->load($data[$formName][$i], '');
  870. $success = true;
  871. }
  872. }
  873.  
  874. return $success;
  875. }
  876.  
  877. /**
  878. * 验证多模型
  879. * 这种方法将验证每一个模型。被验证的模型可以是相同的或不同类型的。
  880. * @param array $models 要验证的模型
  881. * @param array $attributeNames 应该验证的属性名称列表。
  882. * 如果这个参数是空的,它意味着在适用的验证规则中列出的任何属性都应该被验证。
  883. * @return boolean 所有模型的验证规则是否有效. 一个或多个验证不通过都会返回false
  884. */
  885. public static function validateMultiple($models, $attributeNames = null)
  886. {
  887. $valid = true;
  888. /* @var $model Model */
  889. foreach ($models as $model) {
  890. //遍历$models 调用validate()方法
  891. $valid = $model->validate($attributeNames) && $valid;
  892. }
  893.  
  894. return $valid;
  895. }
  896.  
  897. /**
  898. * 当没有指定特定字段时,通过[[toArray()]] 方法返回默认情况下应返回的字段列表
  899. *
  900. * 此方法应返回字段名称或字段定义的数组
  901. * 当没有指定特定字段时,字段名称将被视为对象属性名称,其值将用作字段值
  902. * 当指定字段时,数组键应该是字段名,而数组值应该是相应的字段定义,它可以是对象属性名,也可以是PHP可调用返回相应字段值
  903. *
  904. *回调函数格式为:
  905. * ```php
  906. * function ($model, $field) {
  907. * // return field value
  908. * }
  909. * ```
  910. *
  911. * 例如,下面的代码声明四个字段:
  912. *
  913. * - `email`: 字段名称与属性名称相同`email`;
  914. * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
  915. * values are obtained from the `first_name` and `last_name` properties;
  916. * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
  917. * and `last_name`.
  918. *
  919. * ```php
  920. * return [
  921. * 'email',
  922. * 'firstName' => 'first_name',
  923. * 'lastName' => 'last_name',
  924. * 'fullName' => function ($model) {
  925. * return $model->first_name . ' ' . $model->last_name;
  926. * },
  927. * ];
  928. * ```
  929. *
  930. * 在此方法中,还可以根据一些上下文信息返回字段的不同列表
  931. * 例如,取决于场景[scenario]或当前应用程序用户的特权
  932. * 您可以返回不同的可见字段集或筛选一些字段。
  933. *
  934. * 此方法返回[[attributes()]] 的默认实现,索引是属性名
  935. *
  936. * @return array 返回字段名称或字段定义的列表.
  937. * @see toArray()
  938. */
  939. public function fields()
  940. {
  941. $fields = $this->attributes();
  942. //合并两个数组来创建一个新数组,其中的一个数组元素为键名,另一个数组元素为键值
  943. return array_combine($fields, $fields);
  944. }
  945.  
  946. /**
  947. * 返回用于遍历模型中的属性的迭代器
  948. * 此方法所需的接口[[\IteratorAggregate]].
  949. * @return ArrayIterator 遍历列表中的项的迭代器
  950. */
  951. public function getIterator()
  952. {
  953. $attributes = $this->getAttributes();
  954. return new ArrayIterator($attributes);
  955. }
  956.  
  957. /**
  958. * 返回指定偏移量是否有元素
  959. * 此方法需要SPL接口 [[\ArrayAccess]].
  960. * 它会隐式调用 `isset($model[$offset])`.
  961. * @param mixed $offset 检查的偏移量
  962. * @return boolean 是否存在偏移
  963. */
  964. public function offsetExists($offset)
  965. {
  966. return isset($this->$offset);
  967. }
  968.  
  969. /**
  970. * 返回指定偏移量的元素
  971. * 此方法需要SPL接口 [[\ArrayAccess]].
  972. * 会隐式调用 `$value = $model[$offset];`.
  973. * @param mixed $offset the offset to retrieve element.
  974. * @return mixed the element at the offset,如果在偏移处没有找到元素,返回null
  975. */
  976. public function offsetGet($offset)
  977. {
  978. return $this->$offset;
  979. }
  980.  
  981. /**
  982. * 设置指定偏移量的元素
  983. * 此方法需要SPL接口[[\ArrayAccess]].
  984. * 会被隐式调用 `$model[$offset] = $item;`.
  985. * @param integer $offset the offset to set element
  986. * @param mixed $item 节点的值
  987. */
  988. public function offsetSet($offset, $item)
  989. {
  990. $this->$offset = $item;
  991. }
  992.  
  993. /**
  994. * 将指定偏移量的元素值设置为空
  995. * 此方法需要SPL接口 [[\ArrayAccess]].
  996. * 会隐式调用 `unset($model[$offset])`.
  997. * @param mixed $offset the offset to unset element
  998. */
  999. public function offsetUnset($offset)
  1000. {
  1001. $this->$offset = null;
  1002. }
  1003. }

yii2 源码分析 model类分析 (五)的更多相关文章

  1. JDK源码之Integer类分析

    一 简介 Integer是int基本类型的包装类,同样继承了Number类,实现了Comparable接口,String类中的一些转化方法就使用了Integer类中的一些API,且fianl修饰不可继 ...

  2. [Android FrameWork 6.0源码学习] LayoutInflater 类分析

    LayoutInflater是用来解析XML布局文件,然后生成对象的ViewTree的工具类.是这个工具类的存在,才能让我们写起Layout来那么省劲. 我们接下来进去刨析,看看里边的奥秘 //调用i ...

  3. JDK源码之Byte类分析

    一 简介 byte,即字节,由8位的二进制组成.在Java中,byte类型的数据是8位带符号的二进制数,以二进制补码表示的整数 取值范围:默认值为0,最小值为-128(-2^7);最大值是127(2^ ...

  4. JDK源码之Boolean类分析

    一 简介 boolean类型的封装类,将基本类型为boolean的值包装在一个对象中,实现序列化接口,和Comparable接口 额外提供了许多便捷方法,比较简单,直接贴代码分析 二 源码分析 //t ...

  5. JDK源码之AbstractStringBuilder类分析

    一 概述 二 实现接口 AbstractStringBuilder实现了两个接口: Appendable 概述: Appendable的实现类的对象可以附加字符序列和值. 要追加的字符应该是Unico ...

  6. Yii2 源码分析 入口文件执行流程

    Yii2 源码分析  入口文件执行流程 1. 入口文件:web/index.php,第12行.(new yii\web\Application($config)->run()) 入口文件主要做4 ...

  7. Yii2源码分析(一):入口

    写在前面,写这些随笔是记录下自己看Yii2源码的过程,可能会有些流水账,大部分解析放在注释里说明,由于个人水平有限,有不正确的地方还望斧正. web入口文件Index.php // 定义全局的常量,Y ...

  8. Okhttp3源码解析(3)-Call分析(整体流程)

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  9. Okhttp3源码解析(2)-Request分析

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

随机推荐

  1. How to bypass Win10 logon password?

    Usually we will use LiveView or VFC to "boot up" the evidence files acquired from suspect' ...

  2. 基于TI CC2650的IPv6 over BLE(BLEach) demo

    虽然BLE 5.0协议理论上已经开始支持IPv6了,但是目前市面上还没有可用的实现IPv6通信的BLE产品. 最近在网上看到一个开源的基于contiki系统,在CC2650上实现的IPv6 over ...

  3. mybatis if条件查询 及<号的问题

    摘录自:http://flt95.blog.163.com/blog/static/12736128920136185841551/ <if test="p=='1'"> ...

  4. Linuxc - define 与 typedef的区别

    预处理就是讲一些头文件展开. 预处理还会将使用到宏定义的值替换为真正的值.宏只是单纯的字符串的替换. #define 宏定义 眼里没有语法,不用分号结尾. typedef 定义别名,是有语法的,要用分 ...

  5. cpuimage 开源之

    前年学习opengl做的一个小东西. 原本计划将gpuimage 的算法一个一个转写成cpu版本 c,c++ 版本. gpuimage 项目参考: https://github.com/BradLar ...

  6. JavaScript var的作用域和提升

    在ES6标准之前,var 作为唯一的声明变量关键字,本篇将着重介绍var的作用域和变量提升. 1. var Hoisting(变量提升) va rHoisting:使用var在函数或全局内任何地方声明 ...

  7. linux_inode和block

    linux里一切皆文件 什么是文件属性? 文件本身带有的信息, 包括:索引节点编号. 文件类型以及权限.硬链接个数(备份作用).所有者.所属组.文件大小.修改月.修改日.时分 151387 -rw-- ...

  8. char (*p)[]和char *p[]的区别

    理解的关键在于: 1. []的优先级高于*,(*p)[]理解为指向一个数组,*(p[])存放指针的数组 2. char (*p)[SIZE]:指向一维数组的指针,一维数组只能有SIZE个元素 char ...

  9. JavaScript之图片懒加载的实现

    图片懒加载指的是在浏览过程中随着需要才被加载出来,例如某宝上面浏览商品时,会伴随很多的图片,如果一次全部加载出来的话,显然资源有些浪费,并且加载速度也会相对降低,那么懒加载的实现很重要.即随着浏览翻阅 ...

  10. MyEclipse设置jsp页默认打开方式

    可以用来设置jsp页默认打开是代码编辑模式而不是半视图半代码的模式. 1.选择菜单Window→Preferences. 2.选择General→Editors→File Associations.在 ...