php - id attribute returns value 0 inside a map function in laravel

Code

   $drillDownChart = Loan::select('loan_type AS name')
                           ->groupBy('loan_type')
                           ->where('transaction_year', $transyear)
                           ->where('invalid', false)
                           ->get();

    $drillDownChart->map(function($val)use($loanChartArr) {
        $val->id = $val->name;
        $val->data = $loanChartArr;
        return $val;
    });

Output var_dump(json_encode($drillDownChart));

string(340) "[{"name":"Hospitalization","id":0,"data":[[1,0],[2,0],[3,0],[4,0],[5,2],[6,1],[7,3],[8,1],[9,1],[10,0],[11,0],[12,0]]},{"name":"Salary","id":0,"data":[[1,0],[2,0],[3,0],[4,0],[5,2],[6,1],[7,3],[8,1],[9,1],[10,0],[11,0],[12,0]]},{"name":"Emergency","id":0,"data":[[1,0],[2,0],[3,0],[4,0],[5,2],[6,1],[7,3],[8,1],[9,1],[10,0],[11,0],[12,0]]}]"

The $val->id = $val->name; should return "Hospitalization", "Salary" and "Emergency" but instead it returns 0. Notice if I change "id" to "ID", it shows the correct output.

$drillDownChart->map(function($val)use($loanChartArr) {
    $val->ID = $val->name;
    $val->data = $loanChartArr;
    return $val;
});

Output:

string(373) "[{"name":"Hospitalization","ID":"Hospitalization","data":[[1,0],[2,0],[3,0],[4,0],[5,2],[6,1],[7,3],[8,1],[9,1],[10,0],[11,0],[12,0]]},{"name":"Salary","ID":"Salary","data":[[1,0],[2,0],[3,0],[4,0],[5,2],[6,1],[7,3],[8,1],[9,1],[10,0],[11,0],[12,0]]},{"name":"Emergency","ID":"Emergency","data":[[1,0],[2,0],[3,0],[4,0],[5,2],[6,1],[7,3],[8,1],[9,1],[10,0],[11,0],[12,0]]}]"

What is wrong in here? Is "id" restricted to be used in laravel in this case? Please help. Thank you.

Answer

Solution:

By default, Laravel assumes your model has a primary key named id with a value of type int.

Therefore, the id attribute is casted to an int. It is like having this default casts:

protected $casts = [
    'id' => 'int'
];

So when you access id, your string (e.g. "Hospitalization") is casted to int. The result is 0.

If you really want to keep your current code, you'll have to do some hacky stuff, like setting a accessor for the id attribute:

public function getIdAttribute($value)
{
    return $value;
}

This way, the cast will be overriden and you will be able to retrieve the name the way you want.

However, I would advice against manually changing the id at runtime, it is obviously a code smell, a sign that something isn't quite right with this part of your project.


For those who like digging further, this is where all what I explained happens:

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php

/**
* Get the casts array.
*
* @return array
*/
public function getCasts()
{
    if ($this->getIncrementing()) {
        return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
    }

    return $this->casts;
}

When you try to access an attribute ($this->id), Laravel will determine if it has an accessor, if this is a relationship or not... and at the end, will cast it to the correct type.

To get the casts, it will call ->getCasts().

First this method will check if your model is auto incrementing (which can be disabled by setting the property $incrementing to false).

If that's the case, it will get the primary key name (->getKeyName() which is id by default) and its type (->getKeyType() which is int by default).

This key pair will be merged with the $casts property of the model.

Source