I often have two models in a 1:x relationship:
class Child extends Model
{
public function parent()
{
return $this->belongsTo(Parent::class);
}
}
Now there are times when I already have an instance of one of the related models within a function:
function f(Parent $parent): Child
{
// ...
$child = $parent->createChild();
// ...
return $child
}
A different function takes Child
as a parameter and requires an attribute of the Parent
class:
function g(Child $child)
{
$child->update([
'attribute' => h($child->parent->attribute)
]);
}
Now if g is called with the Child
instance returned from f
, then the Parent
instance will be retrieved from the DB twice. Unless we add a Parent
parameter and pass in the instance to g
, or alternatively modify f
as follows:
function f(Parent $parent): Child
{
// ...
$child = $parent->createChild();
// ...
$child->parent = $parent;
return $child
}
I prefer this second approach to adding another parameter because the context from which g
is called might not have access to the Parent
instance. And it often works fine. However, it fails on the update statement in g
:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'parent' in 'field list' (SQL: update `children`...
So my question is:
Calling $child->parent = $parent
attempts to set a parent
field attribute on the $child
object. However, you want to set the parent
relationship attribute. In order to do this, you need to use the setRelation()
method:
function f(Parent $parent): Child
{
// ...
$child = $parent->createChild();
// ...
$child->setRelation('parent', $parent);
return $child;
}
At this point, however, you will want to be careful that you're not creating a circular reference. If the $parent->child
relationship is set to the $child
instance, and then you set the $child->parent
relationship to the $parent
instance, you've created a circular reference that will blow up if you attempt to convert either instance to json (or an array).
That is, if $parent->child->parent === $parent
, then a circular reference exists, and creating the json/array for that will be an infinite loop.
In this case, if you want to be extra careful, you can use the withoutRelations()
method to assign the relationship to a clone of the parent, which will not have any relationships loaded.
function f(Parent $parent): Child
{
// ...
$child = $parent->createChild();
// ...
$child->setRelation('parent', $parent->withoutRelations());
return $child;
}
After this, then $parent->is($parent->child->parent)
will be true, but $parent->child->parent === $parent
will be false, so no circular reference.