如何将现有模型插入关系

How to insert existing model into relation

我经常有两个模特处于 1:x 关系中:

class Child extends Model 
{
  public function parent() 
  {
    return $this->belongsTo(Parent::class);
  }
}

现在有时我已经在一个函数中有一个相关模型的实例:

function f(Parent $parent): Child
{
  // ...
  $child = $parent->createChild();
  // ...
  return $child
}

一个不同的函数将 Child 作为参数并需要 Parent class 的属性:

function g(Child $child)
{
  $child->update([
    'attribute' => h($child->parent->attribute)  
  ]);
}

现在,如果使用从 f 返回的 Child 实例调用 g,那么 Parent 实例将从数据库中检索两次。除非我们添加一个Parent参数并将实例传递给g,或者修改f如下:

function f(Parent $parent): Child
{
  // ...
  $child = $parent->createChild();
  // ...
  $child->parent = $parent;
  return $child
}

我更喜欢第二种方法而不是添加另一个参数,因为从中调用 g 的上下文可能无法访问 Parent 实例。而且它通常工作正常。但是,它在 g:

中的更新语句失败
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'parent' in 'field list' (SQL: update `children`...

所以我的问题是:

有没有一种方法可以将现有模型实例插入到适当的关系中,而无需模型将分配注册为列更新?

调用 $child->parent = $parent 尝试在 $child 对象上设置 parent field 属性。但是,您想设置 parent relationship 属性。为此,您需要使用 setRelation() 方法:

function f(Parent $parent): Child
{
    // ...
    $child = $parent->createChild();
    // ...
    $child->setRelation('parent', $parent);
    return $child;
}

但是,此时您需要注意不要创建循环引用。如果 $parent->child 关系设置为 $child 实例,然后将 $child->parent 关系设置为 $parent 实例,则您创建了一个循环引用,该循环引用将被破坏如果您尝试将任一实例转换为 json(或数组)。

也就是说,如果 $parent->child->parent === $parent,则存在循环引用,为此创建 json/array 将是一个无限循环。

在这种情况下,如果您想格外小心,可以使用withoutRelations()方法将关系分配给父级的克隆,不会加载任何关系。

function f(Parent $parent): Child
{
    // ...
    $child = $parent->createChild();
    // ...
    $child->setRelation('parent', $parent->withoutRelations());
    return $child;
}

此后,$parent->is($parent->child->parent)为真,$parent->child->parent === $parent为假,无循环引用。