Skip to content

[13.x] Fix appended accessors receiving null in toArray() - #60921

Merged
taylorotwell merged 1 commit into
laravel:13.xfrom
lazerg:fix/issue-60917-appends-accessor-null
Aug 2, 2026
Merged

[13.x] Fix appended accessors receiving null in toArray()#60921
taylorotwell merged 1 commit into
laravel:13.xfrom
lazerg:fix/issue-60917-appends-accessor-null

Conversation

@lazerg

@lazerg lazerg commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

When an accessor is defined for an existing attribute and that attribute is also listed in $appends, toArray() returns null for it instead of the accessor's value:

protected $appends = ['price'];

protected function price(): Attribute
{
    return Attribute::get(fn ($price) => $price);
}

$model->price;              // 100
$model->toArray()['price']; // null

The appends loop in attributesToArray() calls mutateAttributeForArray($key, null), so the accessor always receives null no matter what is stored on the model. Reading the same attribute directly goes through getAttributeFromArray(), which is why $model->price and $model->toArray()['price'] disagree.

This passes the raw attribute value into the accessor, the same way direct access already does. Appends that have no backing attribute still receive null, so their behavior is unchanged.

Fixes #60917

@hamdyelbatal122 hamdyelbatal122 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great fix!

One small optimization regarding attributes that may have already been processed/formatted earlier in attributesToArray() (e.g., dates via addDateAttributesToArray or casts via addCastAttributesToArray):

foreach ($this->getArrayableAppends() as $key) {
    $attributes[$key] = $this->mutateAttributeForArray(
        $key, $attributes[$key] ?? $this->getAttributeFromArray($key)
    );
}

Using $attributes[$key] ?? $this->getAttributeFromArray($key) reuses any value already populated in $attributes before falling back to reading the raw attribute from the model.

@hamdyelbatal122 hamdyelbatal122 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refined attribute array mutation inline suggestion

// when we need to array or JSON the model for convenience to the coder.
foreach ($this->getArrayableAppends() as $key) {
$attributes[$key] = $this->mutateAttributeForArray($key, null);
$attributes[$key] = $this->mutateAttributeForArray($key, $this->getAttributeFromArray($key));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$attributes[$key] = $this->mutateAttributeForArray($key, $this->getAttributeFromArray($key));
$attributes[$key] = $this->mutateAttributeForArray(
$key, $attributes[$key] ?? $this->getAttributeFromArray($key)
);

@lazerg lazerg Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking at this, but I don't think this is safe. mutateAttributeForArray always expects the raw, pre-mutation value, that's exactly how addMutatedAttributesToArray calls it a few lines earlier for the normal attributes.

If the append key also has an accessor mutator, addMutatedAttributesToArray already ran the accessor on it and stored the mutated output in $attributes[$key] before we get to the appends loop. Using $attributes[$key] here would feed that already-mutated value back into mutateAttributeForArray, so the accessor runs twice: once on the raw value, once on its own output.

getAttributeFromArray gives the raw stored value, the same thing a normal (non-appended) access to that attribute passes into its mutator. That's what keeps accessors getting what they expect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes complete sense! Excellent point regarding avoiding double-mutation when addMutatedAttributesToArray has already processed the attribute into $attributes[$key].

Passing the raw value via $this->getAttributeFromArray($key) indeed guarantees that the accessor receives the pristine stored value, exactly matching direct property access.

Appreciate the detailed explanation! Looks solid.

@taylorotwell
taylorotwell merged commit 7063440 into laravel:13.x Aug 2, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accessors receive null when added to appends and toArray is called

3 participants