Sheph
Sheph

Reputation: 675

Laravel Eloquent, "inheritance" override fields from parent

Is there any way in Eloquent to have a model which has some sort of parent model, where both have an identical field, nullable on the child. And if I get the value $child->field I get the childs value if it's not null, otherwise I get the parent value? Something like this:

$parent = new Parent();
$parent->info = 'parent';
$parent->save();

$child = new Child();
$child->info = 'child';
$child->parent()->associate($parent);
$child->save();

echo $child->info
Prints 'child'

And opposite:

$parent = new Parent();
$parent->info = 'parent';
$parent->save();

$child = new Child();
$child->parent()->associate($parent);
$child->info = null;
$child->save();

echo $child->info
Prints 'parent'

It must be a pattern somewhere to have one table rows values 'overrule' another, I just can't seem to find what to search for.

Upvotes: 0

Views: 1403

Answers (1)

Namoshek
Namoshek

Reputation: 6544

You simply need a custom accessor on the model of your choice:

class Child
{
    public function getInfoAttribute($value)
    {
        if ($value === null) {
            return $this->parent->info;
        }

        return $value;
    }
}

This will allow you to still access the property via $child->info.

Please be aware that this will not cast the attribute value according to the $casts array. If you need this casting logic as well, you should use a custom getter method instead of the magic accessor:

class Child
{
    public function getInfo()
    {
        $info = $this->info;

        if ($info === null) {
            $info = $this->parent->info;
        }

        return $info;
    }
}

If you need to multiple properties, you can either duplicate the code and put it into a trait to remove the clutter from your model. Or instead, you can try overriding the magic __get($key) method:

class Child
{
    $parentInheritedAttributes = ['info', 'description'];

    // solution 1: using normal model properties like $child->info
    public function __get($key)
    {
        if (in_array($key, $this->parentInheritedAttributes)) {
            $value = parent::__get($key);

            if ($value === null) {
                // this will implicitely use __get($key) of parent
                $value = $this->parent->$key;
            }

            return $value;
        }

        return parent::__get($key);
    }

    // solution 2: using getters like $child->getInfo()
    public function __call($method, $parameters)
    {
        if (\Illuminate\Support\Str::startsWith($method, 'get')) {
            $attribute = \Illuminate\Support\Str::snake(lcfirst(substr($method, 3)));
            in_array($attribute, $this->parentInheritedAttributes)) {
                $value = $this->$attribute;

                if ($value === null) {
                    $value = $this->parent->$attribute;
                }

                return $value;
            }
        }
    }
}

Upvotes: 1

Related Questions