framework icon indicating copy to clipboard operation
framework copied to clipboard

Inconsistent accessor attribute name conversion

Open Propaganistas opened this issue 11 months ago • 7 comments

Laravel Version

11.42.0

PHP Version

8.4.3

Database Driver & Version

No response

Description

Define a custom attribute with a convoluted name using the Attribute syntax:

Example:

protected function foo1Bar(): Attribute
{
    return Attribute::make(
        get: fn () => 'yay',
    );
}

Accessing a custom attribute is internally done by a snake-to-camel case conversion. So this means both foo1_bar and foo_1_bar will yield the expected value.

However, some features in Eloquent do the inverse by calling upon the attribute cache ($getAttributeMutatorCache): a camel-to-snake case conversion. This means that foo1Bar now only translates to foo1_bar, and not anymore to foo_1_bar even though its value would get returned when retrieving it.

echo $model->foo1_bar; // "yay"
$model->append('foo1_bar');
$model->toArray(); // Works fine.

echo $model->foo_1_bar; // "yay"
$model->append('foo_1_bar');
$model->toArray(); // Call to undefined method Model::getFoo1BarAttribute() 

On the contrary, when resorting back to the good old getFoo1BarAttribute() everything works as expected for both foo1_bar and foo_1_bar.

protected function getFoo1BarAttribute()
{
    return 'yay';
}
echo $model->foo1_bar; // "yay"
$model->append('foo1_bar');
$model->toArray(); // Works fine.

echo $model->foo_1_bar; // "yay"
$model->append('foo_1_bar');
$model->toArray(); // Works fine.

So, because of internal two-way case conversions to support the newer attribute syntax some unexpected and disfunctional ambiguity gets introduced.

I think both syntaxes (Attribute vs getXXXAttribute()) should behave equally. But I'm not quite sure how to proceed with this. Should foo_1_bar get blocked for access to prevent this kind of expectations further down the line? Or should it also resolve properly when doing the camel-to-snake conversion? Or...?

Steps To Reproduce

protected function foo1Bar(): Attribute
{
    return Attribute::make(
        get: fn () => 'yay',
    );
}
$model->append('foo_1_bar');
$model->toArray();

Propaganistas avatar Feb 11 '25 20:02 Propaganistas

Because it actually gets appended, I'd expect it to work when you do ->toArray();

it looks like its down to static::$getAttributeMutatorCache

The key you enter into the append method won't match because it does Str::snake(foo1Bar) internally which gives foo1_bar which then doesn't match the key you've entered.

Will see if I can PR a fix :)

jackbayliss avatar Feb 12 '25 10:02 jackbayliss

@Propaganistas @jackbayliss the issue was reolved in below PR

https://github.com/laravel/framework/pull/54578

thanks!

pandiselvamm avatar Feb 12 '25 13:02 pandiselvamm

@pandiselvamm Nice, beat me to it! 👍🏻

jackbayliss avatar Feb 12 '25 13:02 jackbayliss

Thank you for reporting this issue!

As Laravel is an open source project, we rely on the community to help us diagnose and fix issues as it is not possible to research and fix every issue reported to us via GitHub.

If possible, please make a pull request fixing the issue you have described, along with corresponding tests. All pull requests are promptly reviewed by the Laravel team.

Thank you!

github-actions[bot] avatar Feb 13 '25 10:02 github-actions[bot]

Hey, can you check this PR whether this approach solves your problem or not? My PR Description might have been unclear, but as you already know the context, please do check the PR if it helps to solve your issue:

PR: https://github.com/laravel/framework/pull/54793

ksaif534 avatar Feb 26 '25 05:02 ksaif534

@ laravel maintainers: some community members have provided PRs for this, but they got rejected.

Please could you direct us on how to tackle this issue instead?

Propaganistas avatar Mar 27 '25 10:03 Propaganistas

Maybe my specific case is a different issue, but for me

protected function fooBar(): Attribute
{
    return Attribute::make(
        get: fn () => 'yay',
    );
}
$model->append('foo_bar');
$model->toArray();  // Call to undefined method Model::getFooBarAttribute() 

$model->append('fooBar');
$model->toArray();  // works, but now the appended attribute is in camel case, and other model attributes are in snake case.

Laravel 12.12.0

nicekiwi avatar May 12 '25 02:05 nicekiwi