[13.x] Fix appended accessors receiving null in toArray() - #60921
Conversation
hamdyelbatal122
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
| $attributes[$key] = $this->mutateAttributeForArray($key, $this->getAttributeFromArray($key)); | |
| $attributes[$key] = $this->mutateAttributeForArray( | |
| $key, $attributes[$key] ?? $this->getAttributeFromArray($key) | |
| ); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
When an accessor is defined for an existing attribute and that attribute is also listed in
$appends,toArray()returnsnullfor it instead of the accessor's value:The appends loop in
attributesToArray()callsmutateAttributeForArray($key, null), so the accessor always receivesnullno matter what is stored on the model. Reading the same attribute directly goes throughgetAttributeFromArray(), which is why$model->priceand$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