Laravel检查相关模型是否存在

200

我有一个 Eloquent 模型,它有一个相关的模型:

public function option() {
    return $this->hasOne('RepairOption', 'repair_item_id');
}

public function setOptionArrayAttribute($values)
{
    $this->option->update($values);
}

我创建模型时,不一定会有关联的模型。当我更新它时,可能会添加一个选项,也可能不添加。

因此,我需要检查相关模型是否存在,以相应地更新它或创建它:

$model = RepairItem::find($id);
if (Input::has('option')) {
    if (<related_model_exists>) {
        $option = new RepairOption(Input::get('option'));
        $option->repairItem()->associate($model);
        $option->save();
        $model->fill(Input::except('option');
    } else {
       $model->update(Input::all());
    }
};

我正在寻找的代码是<related_model_exists>


4
非常感谢您的精彩问题!以下各位朋友提供的优秀答案真是太棒了。这些回答为我在项目上节省了不少时间。 - Rafael
12个回答

289
php 7.2+中,您不能在关系对象上使用count,因此没有适用于所有关系的通用方法。请改用查询方法,如下面@tremby所提供的方法:
$model->relation()->exists()

适用于所有关系类型的通用解决方案(PHP 7.2之前版本):

if (count($model->relation))
{
  // exists
}
这对于每种关系都适用,因为动态属性会返回ModelCollection。两者都实现了ArrayAccess

所以它的使用方式如下:

单一关系:hasOne/belongsTo/morphTo/morphOne

// no related model
$model->relation; // null
count($model->relation); // 0 evaluates to false

// there is one
$model->relation; // Eloquent Model
count($model->relation); // 1 evaluates to true

一对多关系: hasMany / belongsToMany / morphMany / morphToMany / morphedByMany

// no related collection
$model->relation; // Collection with 0 items evaluates to true
count($model->relation); // 0 evaluates to false

// there are related models
$model->relation; // Collection with 1 or more items, evaluates to true as well
count($model->relation); // int > 0 that evaluates to true

8
@CurvianVynes,不是的。Collection有自己的方法isEmpty,但通用的empty函数对于对象返回false(因此对于空集合无效)。 - Jarek Tkaczyk
2
当关系没有设置关联时,count($model->relation)morphTo 上无法工作。外键和类型为空,由 Laravel 构建的数据库查询是虚假的并引发异常。我使用 $model->relation()->getOtherKey() 作为解决方法。 - Jocelyn
1
@Jocelyn 是的,这是Eloquent的一个bug。不幸的是,针对多态关系至少还有一些这样的问题,所以显然你无法依赖它们。 - Jarek Tkaczyk
1
要准确地说,对于Pivot关系,value('id')将失败,因此我宁愿使用exists()。至于这个问题-那就要看情况而定了。它确实会运行查询并加载关系,所以如果您不关心关系本身,而只关心它是否存在,那么这样做更好。 - Jarek Tkaczyk
4
它将在PHP 7.2上崩溃,返回错误信息:count(): Parameter must be an array or an object that implements Countable - CodeGodie
显示剩余12条评论

101

一个关联对象将未知的方法调用传递给一个Eloquent查询生成器, 它被设置为仅选择相关的对象。该生成器进一步将未知的方法调用传递给它的底层查询生成器

这意味着您可以直接从关联对象使用exists()count()方法:

$model->relation()->exists(); // bool: true if there is at least one row
$model->relation()->count(); // int: number of related rows

请注意relation后面的括号:->relation()是一个函数调用(获取关系对象),而不是->relation,这是Laravel为您设置的魔术属性获取器(获取相关对象/对象)。
在关系对象上使用count方法(即使用括号)比执行$model->relation->count()count($model->relation)要快得多(除非关系已经被预加载),因为它运行计数查询,而不是从数据库中拉取任何相关对象的所有数据,仅仅是为了计算它们的数量。同样,使用exists也不需要拉取模型数据。 exists()count()都适用于我尝试过的所有关系类型,因此至少适用于belongsTohasOnehasManybelongsToMany

在Lumen中不存在exists函数,不确定为什么。 - briankip
@briankip -- 应该可以。你确定是通过调用方法获取关联对象而不是使用魔术属性获取集合吗? - tremby
3
至少在 Laravel 6.x 中,exists 不适用于不存在的 morphTo 关系。这会导致 SQL 错误。 - Charles Wood
请注意,在关系上调用 exists()count() 需要先将相关模型保存到数据库中。如果您需要在相关模型保存之前检查其是否存在(例如,如果您使用了 setRelation),那么您应该使用 is_nullempty - alexw
当我们使用查询构建器时,无法访问我们在模型中定义的关系 :( - Orman Faghihi Mohaddes
1
@OrmanFaghihiMohaddes:我的答案中关于查询构建器的文本只是对其如何工作的解释的一部分。通过模型上定义的关系访问查询构建器,因此您绝对仍在使用模型上定义的关系。 - tremby

23

我更喜欢使用exists方法:

RepairItem::find($id)->option()->exists()

来检查相关的模型是否存在。在Laravel 5.2上能够正常工作。


2
+1; 在Laravel 5.2中,即使关联表中没有任何项,$model->relation的count($model->relation)也会返回true。使用 ->exists() 可以解决这个问题。 - Ben Wilson

17

Php 7.1之后,被接受的答案不适用于所有类型的关系。

因为取决于关系的类型,Eloquent将返回一个Collection,一个ModelNull。而在Php 7.1中,count(null)会抛出一个error

因此,要检查关系是否存在,您可以使用:

对于单个关系:例如hasOnebelongsTo

if(!is_null($model->relation)) {
   ....
}

关于多个关系:例如:hasManybelongsToMany

if ($model->relation->isNotEmpty()) {
   ....
}

2
对我来说完美地工作了!如果我在结果的foreach循环中急切加载关系并执行->count(),即使它们在模型中被急切加载,也会为每个项目生成一个SQL查询。使用!is_null($model->relation)是最快和SQL友好的方法。非常感谢。 - raphjutras

6
我用于单一关系的是: hasOnebelongsTomorphs
if($model->relation){ 
 ....
}

因为如果条件为空,这个语句将会返回 false。
对于多重关系: hasMany, belongsToManymorphs
if ($model->relation->isNotEmpty()) {
   ....
}

4

正如Hemerson Varela在Php 7.1中所说,count(null)会抛出一个error,如果没有行存在,hasOne将返回null。因为你有一个hasOne关系,我建议使用empty方法进行检查:

$model = RepairItem::find($id);
if (!empty($temp = $request->input('option'))) {
   $option = $model->option;

   if(empty($option)){
      $option = $model->option()->create();
   }

   $option->someAttribute = temp;
   $option->save();
};

但这是多余的。没有必要检查关系是否存在,确定您是否应该执行update还是create调用。只需使用updateOrCreate方法。这等效于上面的内容:

$model = RepairItem::find($id);
if (!empty($temp = $request->input('option'))) {  
   $model->option()
         ->updateOrCreate(['repair_item_id' => $model->id],
                          ['option' => $temp]);
}

4

您可以在模型对象上使用relationLoaded方法。这个方法帮了我大忙,希望它也能帮助其他人。当我在Laracasts上问同样的问题时,有人给了我这个建议


3

不确定 Laravel 5 是否已经改变,但使用 count($data->$relation) 的答案并没有对我起作用,因为访问关联属性的行为会导致其被加载。

最终,一个简单的 isset($data->$relation) 对我很有帮助。


我相信应该是 $data->relation 而不是 $(因为有6个字符的限制,无法编辑)。 - Zanshin13
2
啊,$relation 就是你的关系名称,比如 $data->posts 或者类似的。如果有造成困惑,抱歉,我想要明确 relation 不是一个具体的模型属性 :P - Dave Stewart
这个之前一直是好的,但在我将 Laravel 从5.2.29更新到5.2.45后就不行了。有任何想法为什么会这样或者如何修复它吗?现在它似乎导致关联数据被加载。 - Anthony
我添加了一个包含解决方法的答案。 - Anthony
这在较新版本的Laravel中不起作用。自Laravel 5.8以来,Model::__isset方法被重载,即使没有相关实体,它也会返回true。您需要使用!is_null来避免魔术isset逻辑。他们确认这是Laravel中已知的错误,将在Laravel 8中修复:https://github.com/laravel/framework/issues/31793 - alexw

1
当我将我的PHP版本更新到7.2+时,由于对count($x)函数的错误使用,我不得不完全重构我的代码。这真的很麻烦,也非常可怕,因为有数百种用法,在不同的场景中,并且没有一条规则适用于所有情况。
我遵循的规则,例如: $x = Auth :: user() -&gt; posts-&gt; find(6); (使用 -&gt; find()检查用户是否具有帖子id = 6)
[FAILS] if(count($x)) { return 'Found'; } 
[GOOD] if($x) { return 'Found'; }

$x = Auth::user()->profile->departments;(检查个人资料是否有一些部门,可以有多个部门)

[FAILS] if(count($x)) { return 'Found'; }
[GOOD] if($x->count()) { return 'Found'; }

$x = Auth::user()->profile->get();(在使用 ->get() 后检查用户是否具有个人资料)

[FAILS] if(count($x)) { return 'Found'; }
[GOOD] if($x->count()) { return 'Found'; }

希望这能有所帮助,即使是在问题被提出5年后,这个stackoverflow的帖子对我帮助很大!

0
如果您使用模型类并使用Eloquent ORM,则创建一个新方法并返回bool数据,例如:
public function hasPosts(): bool
{
    return $this->posts()->exists();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接