Inconsistent Accessor Attribute Name Conversion Solution#54793
Inconsistent Accessor Attribute Name Conversion Solution#54793ksaif534 wants to merge 1 commit intolaravel:11.xfrom ksaif534:inconsistent-accessor-name-conversion
Conversation
|
I frankly don't understand what this is trying to solve. Sorry! |
|
@taylorotwell I also struggled a bit to understand the explanation, and then I ran the test case locally and figured it out. Currently, having the attribute defined as an accessor allows one to access it either by But, again currently, if one calls This PR would allow both snake case forms to work when appending that accessor for serialization. Here is some test code you can test on a current Laravel 12 application: <?php // ./routes/console.php
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Artisan;
Artisan::command('app:test', function () {
$model = new class() extends Model {
protected function foo1Bar(): Attribute
{
return Attribute::get(fn () => 'yay');
}
};
// both `foo1_bar` and `foo_1_bar` work as accessors
dump($model->foo1_bar, $model->foo_1_bar);
// appending the attribute as `foo1_bar` works
$model->append('foo1_bar');
dump($model->toArray());
// appending the attribute as `foo_1_bar` doesn't
$model->append('foo_1_bar');
dump($model->toArray()); // error!
});By the way, already migrated 3 of my apps to Laravel 12 and all went smoothly! Thanks! EDIT: adding the console output for convenience: $ php artisan app:test
"yay" // routes/console.php:16
"yay" // routes/console.php:16
array:1 [
"foo1_bar" => "yay"
] // routes/console.php:20
BadMethodCallException
Call to undefined method Illuminate\Database\Eloquent\Model@anonymous\/home/rodrigo/code/playground/issue-54771/routes/console.php:8$9b::getFoo1BarAttribute()
at vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php:67
63▕ * @throws \BadMethodCallException
64▕ */
65▕ protected static function throwBadMethodCallException($method)
66▕ {
➜ 67▕ throw new BadMethodCallException(sprintf(
68▕ 'Call to undefined method %s::%s()', static::class, $method
69▕ ));
70▕ }
71▕ }
+9 vendor frames
10 routes/console.php:24
Illuminate\Database\Eloquent\Model::toArray()
+13 vendor frames
24 artisan:16
Illuminate\Foundation\Application::handleCommand() |
Hello.
This PR is a solution to the issue https://github.com/laravel/framework/issues/54570 where a custom model attribute is converted into different types of snake cases from camel cases. For example, a custom model attribute
foo1Baris converted into both the normalfoo1_bar&foo_1_barwhere the number is between the two underscores(_).Purpose
Developers very often use custom attribute accesors to access existing model attributes & their values. The custom attribute names(methods) are usually written in camel cases inside the Eloquent Model. However, they're usually accessed as snake cases because database table column names are usually accessed as snake cases. Hence it's quite convenient for developers if the camel case name in the Eloquent Model is converted into all types of snake cases when their values are accessed.
Usage
This is how we can use the functionality. First make a custom attribute in the Model:
Then I can append the snake case, convert it to array and access the snake case like this:
Code Changes Explanation
In the following, I show the file
HasAttributes.phpchanges inside theIlluminate/Database/Eloquent/Concernsdirectory and the Integration test fileDatabaseEloquentModelAttributeCastingTest.phpinside thetests/Integration/Databasedirectory:HasAttributes.phpHere,
$standardSnake = lcfirst(static::$snakeAttributes ? Str::snake($match) : $match);converts the attribute name to snake_case if $snakeAttributes is true &lcfirstmakes the first character lowercase.$alternativeSnake = preg_replace('/(\d+)/', '_$1_', $standardSnake);Takes any numbers in the string and wraps them with underscores.$alternativeSnake = str_replace('__', '_', $alternativeSnake);replaces any double underscores with single underscores.This is the Integration Test File:
DatabaseEloquentModelAttributeCastingTest.phpInside the
TestEloquentModelWithAttributeCastClass:This checks whether both type of snake case conversions from camel case are done when you run
./vendor/bin/phpunit tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php --filter testInconsistentAccessorAttributeNameConversionWrap up:
Overall, I think it is very handy if all types of snake case names in custom model attributes can be used when their values are accessed. I'd like to hear your thoughts.
Regards,
Saif