0

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.

Sheph
  • 625
  • 1
  • 6
  • 19
  • If you need STI (Single Table Inheritance) take a look here: https://github.com/Nanigans/single-table-inheritance here: https://github.com/tightenco/parental and here https://stackoverflow.com/questions/26691577/how-can-i-implement-single-table-inheritance-using-laravels-eloquent – dparoli Aug 19 '19 at 19:11

1 Answers1

1

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;
            }
        }
    }
}
Namoshek
  • 6,394
  • 2
  • 19
  • 31
  • Would there be a way to transform this to something more generic in case you have 5-10 fields that gets overriden like this? Otherwise, thank you, was exactly what I was looking for. – Sheph Aug 20 '19 at 19:45
  • @Sheph I updated my answer. The code is untested, but it should do what you expect. It allows you to use the normal properties like `$child->info` or a getter of the form `$child->getInfo()`. I hope it helps you out! – Namoshek Aug 21 '19 at 05:12