Eloquent 模型:防止将瞬态或“虚拟”属性保存到数据库

问题描述

A 有两个 Eloquent 模型 AlbumPhoto。相册可以包含更多子相册或照片。因此,所有专辑的集合是一个有向树。照片具有 taken_at 属性,用于存储照片拍摄时的时间戳。

对于每个相册,我希望有两个“瞬态”或“虚拟”属性 max_taken_atmin_taken_at,它们为相册中的所有照片保留 taken_at 的最小值和最大值及其子专辑递归。所以它不像 Album:query()->withMin('photos','taken_at')->withMax('photos','taken_at') 那样容易,因为那只会考虑相册的直接子级照片,但我也想考虑间接子级。

还有两个要求:

  • 我希望 Album::query() 始终认包含这些属性,而无需记住任何复杂的构造。
  • 出于效率原因,max_taken_atmin_taken_at 的计算应直接在数据库后端进行。我不希望 Eloquent 首先从数据库获取所有(直接和间接)子照片,然后在 PHP 中计算最小值/最大值。这对于拥有超过 5 万张照片的大型装置来说是令人望而却步的。

我已经走了多远:

我利用 Eloquent 的作用域来修改专辑的查询,并将所需的属性包含到查询中,如下所示:

class Album extends Model {
  protected static function booted() {
    parent::booted();
    // normally "scopes" are used to restrict the result of the query
    // to a particular subset through adding additional WHERE-clauses
    // to the default query.
    // However,"scopes" can be used to manipulate the query in any way.
    // Here we add to additional "virtual" columns to the query.
    static::addGlobalScope('calc_minmax_taken_at',function (Builder $builder) {
      $builder->addSelect([
        'max_taken_at' => Photo::query()
          ->leftJoin(...)
          // left out complicated sql query here to recursively include photos from all sub-albums
          ->orderBy('taken_at','desc')
          ->limit(1),'min_taken_at' => Photo::query()
          ->leftJoin('albums as a','a.id','=','album_id')
          // left out complicated sql query here to recursively include photos from all sub-albums
          ->orderBy('taken_at','asc')
          ->limit(1),]);
    });
  }
}

基本上,它类似于可以通过 CREATE VIEW 创建然后查询视图而不是表的 sql 视图。结果正如预期的那样,速度很快(即使对于大型安装也小于 10 毫秒)。通过这种方式,我在 attributes 对象的数组 Album 中获得了另外两个属性。为了避免意外修改值,我添加了以下修改

protected function setMaxTakenAtAttribute($value): void {
  throw new \BadMethodCall('"max_taken_at" must not be set,because this attribute is virtual');
}

protected function setMinTakenAtAttribute($value): void {
  throw new \BadMethodCall('"min_taken_at" must not be set,because this attribute is virtual');
}

不幸的是,还有一个问题。有时 Eloquent 还是想将属性 min_taken_atmax_taken_at 保存到数据库中。当然,这会因 sql 异常而失败,因为专辑关系中不存在这些列。

我该如何解决最后一个问题?有没有办法告诉 Laraval 在 INSERT/UPDATE 期间忽略某些属性

当然,我知道我滥用了 attributes 数组。在数组中根本没有属性 min_taken_atmax_taken_at 可能会更清晰,因为它们不是 Album 模型的真实属性,而是计算值。也许有一种完全不同的方法解决我的“虚拟”属性问题。但是,请注意,不能简单地定义两个访问器 getMinTakenAtgetMaxTakenAt 并在 PHP 中迭代所有子相册及其照片来计算这些值。这对于内存消耗和计算时间来说是令人望而却步的。 DB 可以在

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)