firstOrFail 从 Eloquent Query Builder 返回错误的记录


firstOrFail returning the wrong record from Eloquent Query Builder

我的数据库中有一个名为tallies的表,用于跟踪特定实体的count。表中的两个关键字段是:

  • 类型:实体的名称
  • 计数
  • :实体的计数

现在在表中我有两条记录,第一条记录具有hardDrives类型,第二条记录具有monitors类型。

我的存储库中有一个方法,用于增加tallies表中特定记录的计数:

public function decreaseCountBy($type, $number){
    $countCollection = $this->tally->where('type', '=', $type )->firstOrFail();
    $record = $countCollection->first();
    // Troubleshooting output
    die(var_dump([$type, $record->toArray()]));
    // The rest of the method
    $record->count -= $number;
    $result = $record->save();
    if(!$result){
        throw new 'Exception('Error saving decrementation');
    }
    return $record->count;
}

当我发送请求以递增monitors并查看此方法中故障排除模具和转储的输出并得到以下输出时:

array (size=2)
  0 => string 'monitors' (length=8)
  1 => 
    array (size=5)
      'id' => int 4
      'type' => string 'hardDrives' (length=10)
      'count' => int 15
      'created_at' => string '2014-12-21 03:50:04' (length=19)
      'updated_at' => string '2014-12-21 14:35:28' (length=19)

即使我在查询中使用 monitors 作为$type的值,我也会获得 hardDrives 的记录。

在此之后,我尝试更改方法来触发查询:

$countCollection = $this->tally->where('type', $type )->get();

然后我得到正确的结果:

array (size=2)
  0 => string 'monitors' (length=8)
  1 => 
    array (size=5)
      'id' => int 5
      'type' => string 'monitors' (length=8)
      'count' => int 3
      'created_at' => string '2014-12-21 03:50:04' (length=19)
      'updated_at' => string '2014-12-21 03:50:04' (length=19)

如果查找记录时出错,我可以在这里停下来并添加我自己的异常抛出,但是当我阅读 Builder 类方法的 API 文档时firstOrFail()(抱歉我无法直接链接到它(,该方法描述为:

执行查询并获取第一个结果或引发异常。

我想使用内置的 Laravel 异常,当找不到记录时会抛出它,而不是使用我自己的。

我在这里缺少什么吗?当我在 laravel Eloquent 文档中查找其他示例时,看起来我正在正确构建查询。

最重要的是,我想知道为什么它会失败,而不仅仅是解决方法

分辨率

这是该方法的最终版本,只是为了向大家展示它是如何结束的:

public function decreaseCountBy($type, $number){
    $record = $this->tally->where('type', '=', $type )->firstOrFail();
    $record->count -= $number;
    $result = $record->save();
    if(!$result){
        throw new 'Exception('Error saving decrementation');
    }
    return $record->count;
}

通常,当您执行->get();来检索数据时,结果是一个包含多个记录的雄辩Collection实例。从那里,如果您只想检索第一条记录,则可以使用 Collection 类的 ->first() 方法来获取包含该记录信息的雄辩Model类实例。

firstOrFail() 的情况下,您告诉查询生成器的是,如果找到第一条记录,您只需要第一条记录。由于您只会收到一条记录的数据,因此 eloquent 会跳过集合并返回一个模型实例。

在上面的代码中,我删除了"抓取第一条记录的模型"的行,即 $record = $countCollection->first(); ,并重命名变量以更好地拟合预期结果,即 $record代替$countCollection

已经调用firstOrFail()之后,无需调用first()。 firstOrFail(( 已经返回单个模型而不是集合,在模型上调用first()会触发一个全新的 select 语句(这次没有where(

正如 @Jarek Tkaczyk 在下面指出的那样,使用您的代码,两个查询将针对数据库运行

  1. select * from tallies where type = ?
  2. select * from tallies

这意味着在您的情况下,第一个查询的结果会被第二个查询覆盖。

一些背景资料

firstOrFail()只执行任何其他操作,只是调用first(),然后在first()返回时引发异常null

public function firstOrFail($columns = array('*'))
{
    if ( ! is_null($model = $this->first($columns))) return $model;
    throw (new ModelNotFoundException)->setModel(get_class($this->model));
}