zoukankan      html  css  js  c++  java
  • yii2 源码分析 model类分析 (五)

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

    先介绍一下模型类前面的大量注释说了什么:
    
     * 模型类是数据模型的基类.此类继承了组件类,实现了3个接口
     * 实现了IteratorAggregate(聚合式迭代器)接口,实现了ArrayAccess接口,可以像数组一样访问对象,这两个接口是php自带
     * Arrayable接口是yii2框架自带
     * 模型实现了以下常用功能:
     *
     * - 属性声明: 默认情况下,每个公共类成员都被认为是模型属性
     * - 属性标签: 每个属性可以与用于显示目的的标签相关联。
     * - 大量的属性分配
     * - 基于场景的验证
     *
     * 在执行数据验证时,模型还引发下列事件:
     *
     * - [[EVENT_BEFORE_VALIDATE]]: 在开始时提出的事件 [[validate()]]
     * - [[EVENT_AFTER_VALIDATE]]: 结束时提出的事件[[validate()]]
     *
     * 您可以直接使用模型存储模型数据, 或延长定制.
    <?php
    /**
     * @property yiivalidatorsValidator[] $activeValidators 使用场景效验[[scenario]],此属性是只读的
     * @property array $attributes 属性值键值对方式 (name => value).
     * @property array $errors 所有属性的错误数组. 数组为空表示没有错误. 结果是一个二维数组
     * [[getErrors()]]获取详细错误信息,此属性是只读的
     * @property array $firstErrors 第一个错误,数组的键是属性名, 数组的值是错误信息,空数组表示没有错误,此属性只读
     * @property ArrayIterator $iterator 遍历列表中的项的迭代器,此属性只读. 
     * @property string $scenario 模型所在的场景.默认是[[SCENARIO_DEFAULT]].
     * @property ArrayObject|yiivalidatorsValidator[] $validators 在模型中定义的所有方法,此属性只读.
     *
     * @author Qiang Xue <qiang.xue@gmail.com>
     * @since 2.0
     */
    class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
    {
         //自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits,此处就使用了trait
        use ArrayableTrait;
    
        /**
         * 默认场景名
         */
        const SCENARIO_DEFAULT = 'default';
        /**
         * @event 模型事件先被调用验证,调用[[validate()]]这个方法. You may set
         * 可以设置[[ModelEvent::isValid]] 的isValid属性为false来阻止验证.
         */
        const EVENT_BEFORE_VALIDATE = 'beforeValidate';
        /**
         * @event 验证[[validate()]]后执行的事件
         */
        const EVENT_AFTER_VALIDATE = 'afterValidate';
    
        /**
         * @var array 存放验证错误的数组,键是属性名,值是有关错误的数组 (attribute name => array of errors)
         */
        private $_errors;
        /**
         * @var ArrayObject list 验证器集合
         */
        private $_validators;
        /**
         * @var string 当前场景
         */
        private $_scenario = self::SCENARIO_DEFAULT;
    
    
        /**
         * 返回属性的验证规则.
         *
         * 验证规则通过 [[validate()]] 方法检验属性是否是有效的
         * 子类应该覆盖这个方法来声明不同的验证规则
         *
         * 每个验证规则都是下列结构的数组:
         *
         * ```php
         * [
         *     ['attribute1', 'attribute2'],
         *     'validator type',
         *     'on' => ['scenario1', 'scenario2'],
         *     //...other parameters...
         * ]
         * ```
         *
         * where
         *
         *  - 属性集合: 必选, 指定要验证的属性数组, 对于单个属性,你可以直接传递字符串;
         *  - 验证类型: 必选, 指定要使用的验证. 它可以是一个内置验证器的名字,
         *    模型类的方法名称, 匿名函数, 或验证器类的名称.
         *  - on: 可选参数, 是一个数组,表示在指定场景使用,没设置,表示应用于所有的场景
         *  - 额外的名称-值对可以指定初始化相应的验证特性.
         *
         * 一个验证器可以是一个类的对象延伸扩展[[Validator]], 或模型类方法*(*内置验证器*),具有以下特征:
         *
         * ```php
         * // $params 引用给验证规则的参数
         * function validatorName($attribute, $params)
         * ```
         *
         * 上面的 `$attribute` 指当前正在验证的属性 。。。 
         * `$params` 包含一个数组验证配置选项,例如:当字符串验证时的max属性,验证当前的属性值
         * 可以访问为 `$this->$attribute`. 注意 `$` before `attribute`; 这是取变量$attribute的值和使用它作为属性的名称访问
         *
         * Yii提供了一套[[Validator::builtInValidators|built-in validators]].
         * 每一个都有别名,可以在指定验证规则时使用
         *
         * 看下面的一些例子:
         *
         * ```php
         * [
         *     // 内置 "required" 验证器
         *     [['username', 'password'], 'required'],
         *     // 内置 "string" 验证器 用长度区间定制属性
         *     ['username', 'string', 'min' => 3, 'max' => 12],
         *     // 内置 "compare" 验证器,只能在 "register" 场景中使用
         *     ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
         *     // 一个内置验证器 "authenticate()"方法被定义在模型类里
         *     ['password', 'authenticate', 'on' => 'login'],
         *     // 一个验证器的类 "DateRangeValidator"
         *     ['dateRange', 'DateRangeValidator'],
         * ];
         * ```
         *
         * 注意,为了继承定义在父类中的规则, 一个子类应该使用函数合并父类的规则,例如array_merge()这个函数
         *
         * @return array validation rules返回验证规则数组
         * @see scenarios()
         */
        public function rules()
        {
            return [];
        }
    
        /**
         * 返回一个场景列表和每个场景对应的属性,此属性是活动属性
         * 一个场景中的属性只在当前场景中被验证
         * 返回的数组应该是下列格式的:
         *
         * ```php
         * [
         *     'scenario1' => ['attribute11', 'attribute12', ...],
         *     'scenario2' => ['attribute21', 'attribute22', ...],
         *     ...
         * ]
         * ```
         *
         * 默认情况下,活动属性被认为是安全的,并且可以被赋值
         * 如果一个属性不应该被赋值 (因此认为不安全),
         * 请用感叹号前缀属性 (例如: `'!rank'`).
         *
         * 此方法默认返回声明中的所有属性 [[rules()]]
         * 一个特殊的场景被称为默认场景[[SCENARIO_DEFAULT]] 将会包含在rules()规则里的所有的属性
         * 每个场景将与正在应用于场景的验证规则进行验证的属性关联
         *
         * @return array 返回一个数组和相应的属性列表
         */
        public function scenarios()
        {
            //在场景数组里先把默认场景放入
            $scenarios = [self::SCENARIO_DEFAULT => []];
            //获取所有场景迭代,getValidators()获取关于验证类对象的数组
            foreach ($this->getValidators() as $validator) {
                //$validator->on的值是数组,获得所有应用的场景名字
                foreach ($validator->on as $scenario) {
                    $scenarios[$scenario] = [];
                }
                //$validator->on的值是数组,获得当前不使用的场景的名字
                foreach ($validator->except as $scenario) {
                    $scenarios[$scenario] = [];
                }
            }
            //$names获得了所有的场景
            $names = array_keys($scenarios);
    
            foreach ($this->getValidators() as $validator) {
                if (empty($validator->on) && empty($validator->except)) {
                    //on为空数组,except也是空数组,表示验证规则中没有设置场景,则把验证规则运用到所有的场景
                    foreach ($names as $name) {
                        foreach ($validator->attributes as $attribute) {
                            //把所有模型验证属性添加到每个场景
                            $scenarios[$name][$attribute] = true;
                        }
                    }
                } elseif (empty($validator->on)) {
                    //on为空,except不为空,表示除了except场景外,应用于所有的场景
                    foreach ($names as $name) {
                        if (!in_array($name, $validator->except, true)) {
                            //找到不在except中的场景,放进场景属性数组,表示在其它场景中验证
                            foreach ($validator->attributes as $attribute) {
                                $scenarios[$name][$attribute] = true;
                            }
                        }
                    }
                } else {
                    //on不为空,在on场景中验证这些属性
                    foreach ($validator->on as $name) {
                        foreach ($validator->attributes as $attribute) {
                            $scenarios[$name][$attribute] = true;
                        }
                    }
                }
            }
    
            //使每个场景名对应一个属性数组,$scenarios的键是场景名
            foreach ($scenarios as $scenario => $attributes) {
                if (!empty($attributes)) {
                    $scenarios[$scenario] = array_keys($attributes);
                }
            }
            //场景名对应属性名的数组
            return $scenarios;
        }
    
        /**
         * 返回表单的名称,就是这个 model 的类名.
         *
         * 在一个模型中表单的name值经常被使用在 [[yiiwidgetsActiveForm]] 决定如何命名属性的输入字段
         * 如果表单的name值是A,表单元素属性名是b,则表单元素的name值为"A[b]"
         * 如果表单的name值为空字符串,则表单元素的name为"b"
         *
         * 上述命名模式的目的是针对包含多个不同模型的表单,比较容易区分不同模型的不同属性
         * 每个模型的属性被分组在后的数据的子数组中,它是更容易区分它们。
         *
         * 默认情况下,此方法返回模型类名 (不包含命名空间)
         * 你可以覆盖此方法,当一个表单中有多个模型时
         *
         * @return string the form name of this model class.模型类的名字
         * @see load()
         */
        public function formName()
        {
            //ReflectionClass是php中的扩展反射类
            $reflector = new ReflectionClass($this);
            //getShortName()返回不带命名空间的类名
            return $reflector->getShortName();
        }
    
        /**
         * 返回一个属性列表
         * 默认情况下,此方法返回类的所有公共非静态属性。
         * 可以覆盖此方法返回你想要的属性
         * @return array list of attribute names.
         */
        public function attributes()
        {
            //获取这个类的相关信息
            $class = new ReflectionClass($this);
            $names = [];
            //遍历这个类的每一个属性,如果这个属性是公共的,就把它放入name数组中
            foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
                //不是public并且不是static
                if (!$property->isStatic()) {
                    $names[] = $property->getName();
                }
            }
    
            return $names;
        }
    
        /**
         * 返回属性的标签
         *
         * 属性标签主要用于显示. 例如, `firstName`属性将会显示成`First Name`标签,可以有友好的展示给终端用户
         *
         * 默认的标签生成是使用 [[generateAttributeLabel()]]这个方法
         * 此方法允许您显式指定属性标签.
         *
         * 注意,为了继承父类中定义的标签, 子类标签可以使用`array_merge()`与父类标签合并
         *
         * @return array attribute labels (name => label)
         * @see generateAttributeLabel()
         */
        public function attributeLabels()
        {
            return [];
        }
    
        /**
         * 返回属性提示
         *
         * 属性提示主要用于显示. 例如,`isPublic`这个属性可以用来描述“未登录用户的帖子是否应该可见”
         * 它提供了用户友好的描述属性的含义,并可以显示给最终用户.
         *
         * 如果省略了显式声明,则不会生成标记提示
         *
         * 注意,为了继承父类中定义的标签, 子类标签可以使用`array_merge()`与父类标签合并.
         *
         * @return array attribute hints (name => hint)
         * @since 2.0.4
         */
        public function attributeHints()
        {
            return [];
        }
    
        /**
         * 执行数据验证.
         *
         * 此方法执行适用于当前场景的验证规则 [[scenario]].
         * 下列标准用于判断规则是否适用:
         *
         * - 规则必须与当前场景相关的属性关联;
         * - 规则在所处的情况下必须是有效的,
         *
         * validate()在执行前会先执行 [[beforeValidate()]] ,
         * validate()执行后会执行 [[afterValidate()]] 
         * 如果[[beforeValidate()]] 返回false,接下来的验证将被取消
         *
         * 验证期间发现的错误可以通过 [[getErrors()]],[[getFirstErrors()]] and [[getFirstError()]]获得错误信息,
         *
         * @param array $attributeNames 一个应该被验证的属性列表
         * 若$attributeNames 为空,这意味着在适用的验证规则中列出的任何属性都应该经过验证。
         * @param boolean $clearErrors 表示在执行验证前是否先清除错误 [[clearErrors()]]
         * @return boolean 验证是否成功无任何错误.
         * @throws InvalidParamException 不知道当前场景时会抛出异常.
         */
        public function validate($attributeNames = null, $clearErrors = true)
        {
            if ($clearErrors) {
                //清除所有的错误
                $this->clearErrors();
            }
    
            if (!$this->beforeValidate()) {
                //没通过before验证就返回false
                return false;
            }
            //返回当前活动的场景
            $scenarios = $this->scenarios();
            //返回此模型应用的场景
            $scenario = $this->getScenario();
            if (!isset($scenarios[$scenario])) {
                //若当前活动的场景不在此模型中,抛出异常
                throw new InvalidParamException("Unknown scenario: $scenario");
            }
    
            if ($attributeNames === null) {
                //属性数组为空,自动查找当前场景下的安全属性,并返回这些属性
                $attributeNames = $this->activeAttributes();
            }
    
            //$this->getActiveValidators()返回Validator对象数组
            foreach ($this->getActiveValidators() as $validator) {
                //通过Validator对象验证属性
                $validator->validateAttributes($this, $attributeNames);
            }
            //验证的后置方法
            $this->afterValidate();
            //没有错误就返回真
            return !$this->hasErrors();
        }
    
        /**
         * 在验证前被验证
         * 默认的实现提出了一个` beforevalidate `的事件
         * 验证之前,您可以重写此方法进行初步检查。
         * 请确保调用父实现,然后就可以引发此事件。
         * @return boolean是否应执行接下来的验证,默认是真
         * 如果返回false,则验证将停止,该模型被认为是无效的
         */
        public function beforeValidate()
        {
            //这个不说了ModelEvent里一个方法都木有
            $event = new ModelEvent;
            $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
    
            return $event->isValid;
        }
    
        /**
         * 验证执行后被调用
         * 废话不多说了,可以覆盖,记得调用父类方法
         */
        public function afterValidate()
        {
            $this->trigger(self::EVENT_AFTER_VALIDATE);
        }
    
        /**
         * 返回声明在 [[rules()]]中的验证.
         *
         * 此方法和 [[getActiveValidators()]] 不同,[[getActiveValidators()]]只返回当前场景的验证
         *
         * 由于该方法返回一个数组对象的对象,你可以操纵它通过插入或删除验证器(模型行为的有用)。
         * For example,
         *
         * ```php
         * $model->validators[] = $newValidator;
         * ```
         *
         * @return ArrayObject|yiivalidatorsValidator[] 返回在模型中定义的所有验证器
         */
        public function getValidators()
        {
            if ($this->_validators === null) {
                $this->_validators = $this->createValidators();
            }
            return $this->_validators;
        }
    
        /**
         * Returns the validators applicable to the current [[scenario]].
         * @param string $attribute the name of the attribute whose applicable validators should be returned.
         * If this is null, the validators for ALL attributes in the model will be returned.
         * @return yiivalidatorsValidator[] the validators applicable to the current [[scenario]].
         */
        public function getActiveValidators($attribute = null)
        {
            $validators = [];
            $scenario = $this->getScenario();
            foreach ($this->getValidators() as $validator) {
                if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
                    $validators[] = $validator;
                }
            }
            return $validators;
        }
    
        /**
         * 根据 [[rules()]]里的验证规则创建一个验证对象.
         * 和 [[getValidators()]]不一样, 每次调用此方法,一个新的列表验证器将返回。
         * @return ArrayObject validators
         * @throws InvalidConfigException 如果任何验证规则配置无效,抛出异常
         */
        public function createValidators()
        {
            $validators = new ArrayObject;
            foreach ($this->rules() as $rule) {
                //遍历规则中的每一项
                if ($rule instanceof Validator) {
                    ///如果规则属于Validator对象,添加入数组对象
                    $validators->append($rule);
                } elseif (is_array($rule) && isset($rule[0], $rule[1])) { 
                    //如果子规则是数组,创建一个验证器类,把验证的类型,模型,属性名,验证属性的初始值传入
                    $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
                    //把创建的对象加入数组对象
                    $validators->append($validator);
                } else {
                    //抛出规则必须包含属性名和验证类型
                    throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
                }
            }
            return $validators;
        }
    
        /**
         * 检查属性是否在当前场景中被应用
         * This is determined by checking if the attribute is associated with a
         * [[yiivalidatorsRequiredValidator|required]] validation rule in the
         * current [[scenario]].
         *
         * 注意,当确认有条件验证的应用,使用
         * [[yiivalidatorsRequiredValidator::$when|$when]] 这个方法将会返回
         * `false` 不管 `when` 条件, 因为它可能在模型加载数据前被调用
         *
         * @param string $attribute 属性名
         * @return boolean whether the attribute is required
         */
        public function isAttributeRequired($attribute)
        {
            foreach ($this->getActiveValidators($attribute) as $validator) {
                if ($validator instanceof RequiredValidator && $validator->when === null) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 返回一个值,该值指示属性是否是安全的
         * @param string $attribute 属性名
         * @return boolean whether the attribute is safe for massive assignments
         * @see safeAttributes()
         */
        public function isAttributeSafe($attribute)
        {
            //判断属性是否在数组里,true表示全等(数值和类型都相等)
            return in_array($attribute, $this->safeAttributes(), true);
        }
    
        /**
         * 返回一个值,该值指示当前场景中的属性是否处于活动状态。
         * @param string $attribute 属性名
         * @return boolean whether the attribute is active in the current scenario
         * @see activeAttributes()
         */
        public function isAttributeActive($attribute)
        {
            return in_array($attribute, $this->activeAttributes(), true);
        }
    
        /**
         * 返回指定属性的文本标签
         * @param string $attribute 属性名
         * @return string 属性标签
         * @see generateAttributeLabel()
         * @see attributeLabels()
         */
        public function getAttributeLabel($attribute)
        {
            //获得所有属性标签,并给$lable这个数组
            $labels = $this->attributeLabels();
            //如果这个方法被子类重写了,直接返回自定义的标签,如果没有被重写,返回默认的
            return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
        }
    
        /**
         * 返回指定属性的文本提示
         * @param string $attribute 属性名
         * @return string the attribute hint
         * @see attributeHints()
         * @since 2.0.4
         */
        public function getAttributeHint($attribute)
        {
            $hints = $this->attributeHints();
            //如果这个方法被子类重写了,直接返回自定义的文本提示,如果没有被重写,返回''
            return isset($hints[$attribute]) ? $hints[$attribute] : '';
        }
    
        /**
         * 返回一个值,该值指示是否有任何验证错误
         * @param string|null $attribute attribute name. Use null to check all attributes.
         * @return boolean whether there is any error.
         */
        public function hasErrors($attribute = null)
        {
            //如果有错,_errors[$attribute]有值
            return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
        }
    
        /**
         * 返回所有属性或单个属性的错误。
         * @param string $attribute attribute name. 使用NULL检索所有属性的错误
         * @property array An array of errors for all attributes. 其结果是二维数组,空数组意味着没有错误
         * See [[getErrors()]] for detailed description.
         * @return array 返回一个或多个属性所指定的错误。
         *
         * ```php
         * [
         *     'username' => [
         *         'Username is required.',
         *         'Username must contain only word characters.',
         *     ],
         *     'email' => [
         *         'Email address is invalid.',
         *     ]
         * ]
         * ```
         *
         * @see getFirstErrors()
         * @see getFirstError()
         */
        public function getErrors($attribute = null)
        {
            if ($attribute === null) {
                //如果属性为空,返回所有属性的验证结果
                return $this->_errors === null ? [] : $this->_errors;
            } else {
                //属性不为空,验证单个属性返回的错误结果
                return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
            }
        }
    
        /**
         * 返回模型中每个属性的第一个错误
         * @return array the first errors. 数组的键是属性名, 数组的值是错误信息
         * 没有错误就返回空数组
         * @see getErrors()
         * @see getFirstError()
         */
        public function getFirstErrors()
        {
            if (empty($this->_errors)) {
                //没有错误,返回空数组
                return [];
            } else {
                $errors = [];
                //遍历所有属性的第一个错误,放进数组
                foreach ($this->_errors as $name => $es) {
                    if (!empty($es)) {
                        $errors[$name] = reset($es);
                    }
                }
    
                return $errors;
            }
        }
    
        /**
         * 返回指定属性的第一个错误。
         * @param string $attribute 属性名
         * @return string 错误信息,空意味着没有错误信息
         * @see getErrors()
         * @see getFirstErrors()
         */
        public function getFirstError($attribute)
        {
            //如果这个属性的错误在验证时存在,则返回这个错误,否则返回null
            return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
        }
    
        /**
         * 向指定属性添加新错误
         * @param string $attribute attribute name
         * @param string $error new error message
         */
        public function addError($attribute, $error = '')
        {
            //把错误信息添加入指定属性的数组
            $this->_errors[$attribute][] = $error;
        }
    
        /**
         * 添加错误列表
         * @param array $items 错误信息列表. 数组的键必须是属性的名字
         * 数组的值是错误信息. 如果一个属性有很多错误,则错误需要是数组的形式,
         * 你可以使用 [[getErrors()]] 作为此参数的值
         * @since 2.0.2
         */
        public function addErrors(array $items)
        {
            //遍历属性和对应的错误
            foreach ($items as $attribute => $errors) {
                if (is_array($errors)) {
                    foreach ($errors as $error) {
                        $this->addError($attribute, $error);
                    }
                } else {
                    $this->addError($attribute, $errors);
                }
            }
        }
    
        /**
         * 删除所有属性或单个属性的错误。
         * @param string $attribute attribute name. 使用NULL删除所有属性的错误。
         */
        public function clearErrors($attribute = null)
        {
            if ($attribute === null) {
                //如果没传属性,把所有的错误清除掉
                $this->_errors = [];
            } else {
                //删除对应属性的错误
                unset($this->_errors[$attribute]);
            }
        }
    
        /**
         * 根据给定属性名称生成用户友好的属性标签。
         * 这是通过用空格替换下划线、破折号和圆点,并将每个单词的第一个字母替换为大写字母。
         * @param string $name the column name
         * @return string the attribute label
         */
        public function generateAttributeLabel($name)
        {
            //camel2words定义了一个正则来替换字符串
            return Inflector::camel2words($name, true);
        }
    
        /**
         * 返回属性值
         * @param array $names 返回需要的属性列表
         * 默认为null, 意味着定义在 [[attributes()]] 里的所有属性都会被返回
         * 如果是数组,则只返回数组中的属性
         * @param array $except 不应返回值的属性列表
         * @return array attribute values (name => value).
         */
        public function getAttributes($names = null, $except = [])
        {
            $values = [];
            if ($names === null) {
                //$names为null,$names设置成所有的属性
                $names = $this->attributes();
            }
            foreach ($names as $name) {
                $values[$name] = $this->$name;
            }
            foreach ($except as $name) {
                //不返回哪个属性,从数组中删除哪个属性
                unset($values[$name]);
            }
    
            return $values;
        }
    
        /**
         * 以大量的方式设置属性值。
         * @param array $values 属性以 (name => value) 被分配到模型
         * @param boolean $safeOnly 赋值是否只对安全的属性进行
         * 安全属性是与当前中的验证规则关联的属性,定义在[[scenario]]中.
         * @see safeAttributes()
         * @see attributes()
         */
        public function setAttributes($values, $safeOnly = true)
        {
            if (is_array($values)) {
                //array_flip交换数组中的键和值,$safeOnly为true返回安全属性,否则返回所有的属性
                $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
                foreach ($values as $name => $value) {
                    if (isset($attributes[$name])) {
                        // 如果存在该属性,就直接赋值
                        $this->$name = $value;
                    } elseif ($safeOnly) {
                         // 如果不存在,而且是 safeOnly 的话,就触发一下 onUnsafeAttribute 方法
                        $this->onUnsafeAttribute($name, $value);
                    }
                }
            }
        }
    
        /**
         * 当一个不安全的属性被赋值时调用此方法
         * 如果是在yii_debug,默认实现会记录一个警告消息
         * @param string $name the unsafe attribute name
         * @param mixed $value the attribute value
         */
        public function onUnsafeAttribute($name, $value)
        {
            if (YII_DEBUG) {
                //debug模式,警告信息出现
                Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
            }
        }
    
        /**
         * 返回在模型中应用的场景
         *
         * 场景影响如何进行验证,哪些属性可以大量分配。
         *
         * @return string the scenario that this model is in. 默认场景是 [[SCENARIO_DEFAULT]].
         */
        public function getScenario()
        {
            return $this->_scenario;
        }
    
        /**
         * 为模型设置场景
         * 注意,此方法不检查场景是否存在
         * [[validate()]]会检查场景是否存在.
         * @param string $value 再模型中的场景名.
         */
        public function setScenario($value)
        {
            $this->_scenario = $value;
        }
    
        /**
         * 返回当前场景中大量分配的安全的属性名称
         * @return string[] safe attribute names
         */
        public function safeAttributes()
        {
            // 获取当前的场景
            $scenario = $this->getScenario();
             // 获取所有场景及其属性
            $scenarios = $this->scenarios();
            if (!isset($scenarios[$scenario])) {
                // 场景不存在,就返回空
                return [];
            }
            $attributes = [];
            foreach ($scenarios[$scenario] as $attribute) {
                // 将开头不是!的属性才会放入到 $attributes 中
                if ($attribute[0] !== '!' && !in_array('!' . $attribute, $scenarios[$scenario])) {
                    $attributes[] = $attribute;
                }
            }
    
            return $attributes;
        }
    
        /**
         * 返回当前场景中要受验证的属性名称
         * @return string[] 返回安全的属性名称
         */
        public function activeAttributes()
        {
            $scenario = $this->getScenario();
            $scenarios = $this->scenarios();
            if (!isset($scenarios[$scenario])) {
                return [];
            }
            // 获取当前场景中的所有属性
            $attributes = $scenarios[$scenario];
            foreach ($attributes as $i => $attribute) {
                if ($attribute[0] === '!') {
                    // 如果属性名以!开头,就把!截取掉,并放入数组
                    $attributes[$i] = substr($attribute, 1);
                }
            }
    
            return $attributes;
        }
    
        /**
         * 填充模型的输入数据(把数据加载到模型中)
         *
         * 这种方法提供了一个方便快捷的方式:
         *
         * ```php
         * if (isset($_POST['FormName'])) {
         *     $model->attributes = $_POST['FormName'];
         *     if ($model->save()) {
         *         // handle success
         *     }
         * }
         * ```
         *
         * 如果使用load方法
         *
         * ```php
         * if ($model->load($_POST) && $model->save()) {
         *     // handle success
         * }
         * ```
         *
         * `load()` gets the `'FormName'` from the model's [[formName()]] method (which you may override), unless the
         * `$formName` parameter is given. If the form name is empty, `load()` populates the model with the whole of `$data`,
         * instead of `$data['FormName']`.
         *
         * 注意,被填充的数据将会接受[[setAttributes()]]的安全检查.
         *
         * @param array $data 模型加载的数组, 典型的是 `$_POST` or `$_GET`.
         * @param string $formName 用于将数据加载到模型中的表单名称
         * If not set, [[formName()]] is used.
         * @return boolean whether `load()` found the expected form in `$data`.
         */
        public function load($data, $formName = null)
        {
            //如果没有传表单名称,就取所在类的名称
            $scope = $formName === null ? $this->formName() : $formName;
            if ($scope === '' && !empty($data)) {
                 //如果 $scope 为空字符串,且 $data不为空,就设置属性
                $this->setAttributes($data);
    
                return true;
            } elseif (isset($data[$scope])) {
                // 存在 $data[$scope],使用 $data[$scope] 去设置属性
                $this->setAttributes($data[$scope]);
    
                return true;
            } else {
                return false;
            }
        }
    
        /**
         * 从终端用户获取数据,形成模型
         * 该方法主要用于收集表格数据输入
         * 为每个模型加载的数据`$data[formName][index]`, where `formName`
         * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
         * If [[formName()]] is empty, `$data[index]` 将用于填充每个模型.
         * 每个模型的数据都要经过安全检查 [[setAttributes()]].
         * @param array $models 要填充的模型 注意所有的模型都应该有相同的类
         * @param array $data the data array. 通常是 `$_POST` or `$_GET`, 也可以是最终用户提供的任何有效数组。
         * @param string $formName 要把数据加载到模型的表单名
         * If not set, it will use the [[formName()]] value of the first model in `$models`.
         * This parameter is available since version 2.0.1.
         * @return boolean whether at least one of the models is successfully populated.
         */
        public static function loadMultiple($models, $data, $formName = null)
        {
            if ($formName === null) {
                /* @var $first Model */
                //reset — 将数组的内部指针指向第一个单元
                $first = reset($models);
                if ($first === false) {
                     // 不存在就返回 false
                    return false;
                }
                // 拿到所在类的名称
                $formName = $first->formName();
            }
    
            $success = false;
            // 遍历 $models,一个个 load 数据
            foreach ($models as $i => $model) {
                /* @var $model Model */
                if ($formName == '') {
                    if (!empty($data[$i])) {
                        // 数据不为空,就 load 到相应的 model 中
                        $model->load($data[$i], '');
                        $success = true;
                    }
                } elseif (!empty($data[$formName][$i])) {
                    // 存在 $formName,且数据不为空,就 load 到相应的 model 中
                    $model->load($data[$formName][$i], '');
                    $success = true;
                }
            }
    
            return $success;
        }
    
        /**
         * 验证多模型
         * 这种方法将验证每一个模型。被验证的模型可以是相同的或不同类型的。
         * @param array $models 要验证的模型
         * @param array $attributeNames 应该验证的属性名称列表。
         * 如果这个参数是空的,它意味着在适用的验证规则中列出的任何属性都应该被验证。
         * @return boolean 所有模型的验证规则是否有效. 一个或多个验证不通过都会返回false
         */
        public static function validateMultiple($models, $attributeNames = null)
        {
            $valid = true;
            /* @var $model Model */
            foreach ($models as $model) {
                //遍历$models 调用validate()方法
                $valid = $model->validate($attributeNames) && $valid;
            }
    
            return $valid;
        }
    
        /**
         * 当没有指定特定字段时,通过[[toArray()]] 方法返回默认情况下应返回的字段列表
         *
         * 此方法应返回字段名称或字段定义的数组
         * 当没有指定特定字段时,字段名称将被视为对象属性名称,其值将用作字段值 
         * 当指定字段时,数组键应该是字段名,而数组值应该是相应的字段定义,它可以是对象属性名,也可以是PHP可调用返回相应字段值
         * 
         *回调函数格式为:
         * ```php
         * function ($model, $field) {
         *     // return field value
         * }
         * ```
         *
         * 例如,下面的代码声明四个字段:
         *
         * - `email`: 字段名称与属性名称相同`email`;
         * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
         *   values are obtained from the `first_name` and `last_name` properties;
         * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
         *   and `last_name`.
         *
         * ```php
         * return [
         *     'email',
         *     'firstName' => 'first_name',
         *     'lastName' => 'last_name',
         *     'fullName' => function ($model) {
         *         return $model->first_name . ' ' . $model->last_name;
         *     },
         * ];
         * ```
         *
         * 在此方法中,还可以根据一些上下文信息返回字段的不同列表
         * 例如,取决于场景[scenario]或当前应用程序用户的特权
         * 您可以返回不同的可见字段集或筛选一些字段。
         *
         * 此方法返回[[attributes()]] 的默认实现,索引是属性名
         *
         * @return array 返回字段名称或字段定义的列表.
         * @see toArray()
         */
        public function fields()
        {
            $fields = $this->attributes();
            //合并两个数组来创建一个新数组,其中的一个数组元素为键名,另一个数组元素为键值
            return array_combine($fields, $fields);
        }
    
        /**
         * 返回用于遍历模型中的属性的迭代器
         * 此方法所需的接口[[IteratorAggregate]].
         * @return ArrayIterator 遍历列表中的项的迭代器
         */
        public function getIterator()
        {
            $attributes = $this->getAttributes();
            return new ArrayIterator($attributes);
        }
    
        /**
         * 返回指定偏移量是否有元素
         * 此方法需要SPL接口 [[ArrayAccess]].
         * 它会隐式调用 `isset($model[$offset])`.
         * @param mixed $offset 检查的偏移量
         * @return boolean 是否存在偏移
         */
        public function offsetExists($offset)
        {
            return isset($this->$offset);
        }
    
        /**
         * 返回指定偏移量的元素
         * 此方法需要SPL接口 [[ArrayAccess]].
         * 会隐式调用 `$value = $model[$offset];`.
         * @param mixed $offset the offset to retrieve element.
         * @return mixed the element at the offset,如果在偏移处没有找到元素,返回null
         */
        public function offsetGet($offset)
        {
            return $this->$offset;
        }
    
        /**
         * 设置指定偏移量的元素
         * 此方法需要SPL接口[[ArrayAccess]].
         * 会被隐式调用 `$model[$offset] = $item;`.
         * @param integer $offset the offset to set element
         * @param mixed $item 节点的值
         */
        public function offsetSet($offset, $item)
        {
            $this->$offset = $item;
        }
    
        /**
         * 将指定偏移量的元素值设置为空
         * 此方法需要SPL接口 [[ArrayAccess]].
         * 会隐式调用 `unset($model[$offset])`.
         * @param mixed $offset the offset to unset element
         */
        public function offsetUnset($offset)
        {
            $this->$offset = null;
        }
    }
  • 相关阅读:
    1230 jquery
    1221 监听事件
    1218 dom表格元素操作
    1216 DOM
    Java中对小数的向下取整,向上取整
    Mysql中 在SQL语句里进行日期格式转换
    一些常用格式化。价格、日期等 持续更新
    List对象里面根据某一字段去重
    java 后端 初始化图片像素(1980 x 1080)大小
    swagger里面测试List数据格式
  • 原文地址:https://www.cnblogs.com/liuwanqiu/p/6747629.html
Copyright © 2011-2022 走看看