Error with attribute mutators
Bug Report
Q | A |
---|---|
PHP version | 7.2 |
Package version | 2.x |
Framework | Laravel |
Framework version | 7.x |
Actual Behaviour
When having an attribute mutator on an model and refering to an other attribute of the model instance I get an 'undefined index {column name}' error
Expected Behaviour
Have all the other model attributes of the intsance correctly loaded and available
Steps to Reproduce
As I found out the problem is this code here in Ledger.php->getProperty
if (Str::startsWith($key, 'recordable_')) {
return $this->getFormattedProperty($this->getRecordableInstance(), \mb_substr($key, 11), $value);
}
In the method $this->getRecordableInstance()
it initializes an empty shell of the morphed model and further in getFormattedProperty
it searches for possible attribute mutators and applies them.
In my case I have the following mutator
public function getAddressAttribute()
{
switch (strtoupper($this->type)) {
case 'ADDR':
return json_decode($this->attributes['address']);
break;
default:
return $this->attributes['address'];
}
}
Yes, I know, it isn't pretty to have json and non json values in a column but the problem here is, that because of the "empty shell" the actual model gets never retrieved from the database and so $this->attributes
gets never populated (also $this->type is always null).
Therefore $this->attributes
is an empty array with no 'address' key and the 'undefined index' error gets triggered,
Relevant information, logs and/or screenshots
none
Possible Solutions
A quick fix could be in Ledger.php->getProperty
to check for the return value null
and if this is the case instead returns the contents of the variable $value
like this
$formatted = $this->getFormattedProperty($this->getRecordableInstance(), \mb_substr($key, 11), $value);
return $formatted !== null ? formatted : $value;
I think null
maybe a common mutator return value so the quick fix will most likely not be perfect for all scenarios.
An alternate solution could be to define a constant in the Ledger model like this (or somewhere else ;) )
public const USE_RAW_VALUE = 'accountant_ledgers_use_raw_value';
In the mutator I could then use this
public function getAddressAttribute()
{
if (! $this->exists) {
return Ledger::USE_RAW_VALUE;
}
switch (strtoupper($this->type)) {
case 'ADDR':
return json_decode($this->attributes['address']);
break;
default:
return $this->attributes['address'];
}
}
And in Ledger.php->getProperty
this
$formatted = $this->getFormattedProperty($this->getRecordableInstance(), \mb_substr($key, 11), $value);
return $formatted !== self::USE_RAW_VALUE ? formatted : $value;