Gazz
Gazz

Reputation: 1075

Model accessors not working when response is JSON and field contains a number

I have many "get" mutators which decrypt data so that it can be readable in the view. These mutators are the names of the fields in the DB. Most of these work except for 2. The only difference with these 2 mutators is that the mutator function names contains numbers.

Example of a mutator which works:

public function getDateOfBirthAttribute($value)
{
    return empty($value) ? '' : decrypt($value);
}

Examples of mutators which do NOT work:

public function getAddressLine1Attribute($value)
{
    return empty($value) ? '' : decrypt($value);
}

public function getAddressLine2Attribute($value)
{
    return empty($value) ? '' : decrypt($value);
}

Name of fields in table:

The very strange thing about all this is when the response is NOT json, the mutator works as expected and decrypts the field. (e.g. using return view('view.name', compact($user))), however when I put this data into a JSON response (e.g. return response()->json([$user]);, the 2 address line mutators don't work and returns the field untouched.

I have tried adding return "test" to these 2 mutators to see if it is even hitting the function but it is not.

Why in this instance does JSON stop the mutator from working? Could it be an issue with numbers in the function name? May I have to rename my fields in the DB?

Upvotes: 2

Views: 644

Answers (1)

gbalduzzi
gbalduzzi

Reputation: 10176

I can't verify this now, but i think it may be related to the way laravel computes snake_case and camel_case.

Basically, when you request a field like $user->address_line_1, laravel transforms the field in camelCase (AddressLine1) and checks for a custom accessors, it finds it and returns the correct modified value.

However, when the model is serialized into Json, it does the opposing operation: it tries to detect which field needs to be modified by looking at the accessors. So, it finds the getAddressLine1Attribute and transforms it into snake_case.. yielding address_line1, a wrong field.

The basic problem here is that both address_line_1 and address_line1 have the same camelCase representation, so it's impossible to reliably reverse the transformation to camelCase.

An hack you could try is to define the accessor as getAddressLine_1Attribute, but it won't work when accessing the field directly ($user->address_line_1)

You could define it to use the previously defined accessors, so you don't have code repetition:

public function getAddressLine_1Attribute($value)
{
    return $this->getAddressLine1Attribute($value);
}

public function getAddressLine1Attribute($value)
{
    return empty($value) ? '' : decrypt($value);
}

EDIT: my theory has been confirmed by some testing with the camel_case() and snake_case() helper functions

Upvotes: 2

Related Questions