最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

php - Does toArray() in Yii2 ActiveRecord always return only explicitly set attributes? - Stack Overflow

programmeradmin1浏览0评论

I'm working with Yii2 ActiveRecord and noticed an interesting behavior with the toArray() method.

Consider the following example:

$model = new UserModel; 
print_r($model->toArray());

This returns an empty array ([]), even though UserModel has defined attributes in the database schema.

My usage case is something like

$model = new UserModel; 
$model->name = 'name'; // Only setting one attribute

// somewhere later in code
$existingModel = UserModel::findOne($id);
if ($existingModel) {
    // overwriting explicitly set attributes
    $existingModel->setAttributes($model->toArray());
    $existingModel->save();
}

My question is: does toArray() always return only the explicitly set attributes (e.g. $model->name = '...' or $model->setAttribute(...))

If so, is there any documentation that confirms this behavior? I couldn't find a clear explanation in the official Yii2 documentation.

I'm working with Yii2 ActiveRecord and noticed an interesting behavior with the toArray() method.

Consider the following example:

$model = new UserModel; 
print_r($model->toArray());

This returns an empty array ([]), even though UserModel has defined attributes in the database schema.

My usage case is something like

$model = new UserModel; 
$model->name = 'name'; // Only setting one attribute

// somewhere later in code
$existingModel = UserModel::findOne($id);
if ($existingModel) {
    // overwriting explicitly set attributes
    $existingModel->setAttributes($model->toArray());
    $existingModel->save();
}

My question is: does toArray() always return only the explicitly set attributes (e.g. $model->name = '...' or $model->setAttribute(...))

If so, is there any documentation that confirms this behavior? I couldn't find a clear explanation in the official Yii2 documentation.

Share Improve this question edited Mar 29 at 17:58 user2531657 asked Mar 29 at 17:41 user2531657user2531657 4734 silver badges10 bronze badges 0
Add a comment  | 

2 Answers 2

Reset to default 0

toArray calls resolveFields in order to determine what fields are needed.

This is what the documentation says:

This method will first extract the root fields from the given fields. Then it will check the requested root fields against those declared in fields() and extraFields() to determine which fields can be returned. This is the source-code of getFields:

    /**
     * Determines which fields can be returned by [[toArray()]].
     * This method will first extract the root fields from the given fields.
     * Then it will check the requested root fields against those declared in [[fields()]] and [[extraFields()]]
     * to determine which fields can be returned.
     * @param array $fields the fields being requested for exporting
     * @param array $expand the additional fields being requested for exporting
     * @return array the list of fields to be exported. The array keys are the field names, and the array values
     * are the corresponding object property names or PHP callables returning the field values.
     */
    protected function resolveFields(array $fields, array $expand)
    {
        $fields = $this->extractRootFields($fields);
        $expand = $this->extractRootFields($expand);
        $result = [];

        foreach ($this->fields() as $field => $definition) {
            if (is_int($field)) {
                $field = $definition;
            }
            if (empty($fields) || in_array($field, $fields, true)) {
                $result[$field] = $definition;
            }
        }

        if (empty($expand)) {
            return $result;
        }

        foreach ($this->extraFields() as $field => $definition) {
            if (is_int($field)) {
                $field = $definition;
            }
            if (in_array($field, $expand, true)) {
                $result[$field] = $definition;
            }
        }

        return $result;
    }

and of extractRootFields:

    /**
     * Extracts the root field names from nested fields.
     * Nested fields are separated with dots (.). e.g: "item.id"
     * The previous example would extract "item".
     *
     * @param array $fields The fields requested for extraction
     * @return array root fields extracted from the given nested fields
     * @since 2.0.14
     */
    protected function extractRootFields(array $fields)
    {
        $result = [];

        foreach ($fields as $field) {
            $result[] = current(explode('.', $field, 2));
        }

        if (in_array('*', $result, true)) {
            $result = [];
        }

        return array_unique($result);
    }

We see that if any field equals *, then it returns []. And of fields():

    /**
     * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
     *
     * A field is a named element in the returned array by [[toArray()]].
     *
     * This method should return an array of field names or field definitions.
     * If the former, the field name will be treated as an object property name whose value will be used
     * as the field value. If the latter, the array key should be the field name while the array value should be
     * the corresponding field definition which can be either an object property name or a PHP callable
     * returning the corresponding field value. The signature of the callable should be:
     *
     * ```php
     * function ($model, $field) {
     *     // return field value
     * }
     * ```
     *
     * For example, the following code declares four fields:
     *
     * - `email`: the field name is the same as the property name `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 () {
     *         return $this->first_name . ' ' . $this->last_name;
     *     },
     * ];
     * ```
     *
     * In this method, you may also want to return different lists of fields based on some context
     * information. For example, depending on the privilege of the current application user,
     * you may return different sets of visible fields or filter out some fields.
     *
     * The default implementation of this method returns the public object member variables indexed by themselves.
     *
     * @return array the list of field names or field definitions.
     * @see toArray()
     */
    public function fields()
    {
        $fields = array_keys(Yii::getObjectVars($this));
        return array_combine($fields, $fields);
    }

As we can see, fields() takes into account all fields. resolveFields will add all fields that were either explicitly specified, or, if we received empty array, then all of them. So since your UserModel behaves differently, it's highly probable that it overrides toArray. If not, then you can debug framework/base/ArrayableTrait.php and see how and where is it ending up with no fields.

The behavior of the toArray() method in Yii2 depends on whether your model extends from \yii\db\ActiveRecord or from \yii\base\Model.

  • For base models (i.e., those that extend from \yii\base\Model), toArray() will return all defined attributes, regardless of whether they have been explicitly set or not.

  • For ActiveRecord models, toArray() will only include attributes that have been explicitly set. This is because ActiveRecord inherits from \yii\base\DynamicModel, where attributes are stored internally in the _attributes array only when they are actually set.

This behavior is not directly related to how toArray() itself is implemented, but rather to how attributes are managed internally by different model types in Yii2.

So if you see toArray() behaving differently between two models, check whether they're extending ActiveRecord or just the base Model.

发布评论

评论列表(0)

  1. 暂无评论