问题描述
A 有两个 Eloquent 模型 Album
和 Photo
。相册可以包含更多子相册或照片。因此,所有专辑的集合是一个有向树。照片具有 taken_at
属性,用于存储照片拍摄时的时间戳。
对于每个相册,我希望有两个“瞬态”或“虚拟”属性 max_taken_at
和 min_taken_at
,它们为相册中的所有照片保留 taken_at
的最小值和最大值及其子专辑递归。所以它不像 Album:query()->withMin('photos','taken_at')->withMax('photos','taken_at')
那样容易,因为那只会考虑相册的直接子级照片,但我也想考虑间接子级。
还有两个要求:
- 我希望
Album::query()
始终默认包含这些属性,而无需记住任何复杂的构造。 - 出于效率原因,
max_taken_at
和min_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_at
和 max_taken_at
保存到数据库中。当然,这会因 sql 异常而失败,因为专辑关系中不存在这些列。
我该如何解决最后一个问题?有没有办法告诉 Laraval 在 INSERT/UPDATE 期间忽略某些属性?
当然,我知道我滥用了 attributes
数组。在数组中根本没有属性 min_taken_at
和 max_taken_at
可能会更清晰,因为它们不是 Album
模型的真实属性,而是计算值。也许有一种完全不同的方法来解决我的“虚拟”属性问题。但是,请注意,不能简单地定义两个访问器 getMinTakenAt
和 getMaxTakenAt
并在 PHP 中迭代所有子相册及其照片来计算这些值。这对于内存消耗和计算时间来说是令人望而却步的。 DB 可以在
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)