Laravel Eloquent使用join时如何自动过滤关联表deleted_at=null数据?
解决Laravel中Join软删除模型自动过滤的问题
下面提供几种无需手动添加where('tb_b.deleted_at', null)的方案,保留原有join代码结构的同时自动过滤已删除数据:
方案1:全局扩展查询构建器的Join逻辑
在项目的App\Providers\AppServiceProvider的boot方法中,重写查询构建器的join方法,自动检测关联表对应的模型是否使用SoftDeletes trait,若使用则自动添加过滤条件:
use Illuminate\Database\Query\Builder; use Illuminate\Support\Str; use Illuminate\Database\Eloquent\SoftDeletes; public function boot() { // 先保存原生join方法 Builder::macro('originalJoin', Builder::getMacro('join')); // 重写join方法,自动处理软删除过滤 Builder::macro('join', function ($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) { // 执行原生join逻辑 $builder = $this->originalJoin($table, $first, $operator, $second, $type, $where); // 仅当查询来自模型实例时,检查关联表的软删除状态 if (property_exists($this, 'model') && $this->model) { $modelClass = $this->getModelForTable($table); // 如果关联表对应的模型使用了SoftDeletes,自动添加过滤条件 if ($modelClass && in_array(SoftDeletes::class, class_uses_recursive($modelClass))) { $builder->whereNull("{$table}.deleted_at"); } } return $builder; }); // 同步处理leftJoin等其他join类型 Builder::macro('leftJoin', function ($table, $first, $operator = null, $second = null) { return $this->join($table, $first, $operator, $second, 'left'); }); } // 辅助方法:根据表名匹配对应的模型类(可根据项目结构调整) private function getModelForTable(string $table): ?string { // 默认规则:表名复数转单数驼峰,对应App\Models下的模型 $modelName = Str::studly(Str::singular($table)); $modelClass = "App\\Models\\{$modelName}"; if (class_exists($modelClass)) { return $modelClass; } // 自定义表名-模型映射(处理特殊命名的表) $customMap = [ 'tb_b' => \App\Models\TbB::class, // 其他特殊表映射... ]; return $customMap[$table] ?? null; }
配置完成后,原有代码TabelA::join('tb_b', 'tb_a.id_b', '=', 'tb_b.id_b')->get()会自动过滤tb_b中已删除的数据,完全无需修改业务代码。
方案2:模型内定义带软删除过滤的Join Scope
如果不想全局修改查询构建器,可以在项目的基础模型(如App\Models\Model)中定义自定义Scope,专门用于关联软删除模型的join:
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Str; public function scopeJoinSoftDeletedTable(Builder $query, string $table, string $first, string $operator = null, string $second = null, string $type = 'inner') { $query->join($table, $first, $operator, $second, $type); // 检查关联表模型是否使用SoftDeletes $modelClass = $this->getModelForTable($table); if ($modelClass && in_array(SoftDeletes::class, class_uses_recursive($modelClass))) { $query->whereNull("{$table}.deleted_at"); } return $query; } // 辅助方法:匹配表名到模型 private function getModelForTable(string $table): ?string { $modelName = Str::studly(Str::singular($table)); $modelClass = "App\\Models\\{$modelName}"; if (class_exists($modelClass)) { return $modelClass; } $customMap = [ 'tb_b' => \App\Models\TbB::class, ]; return $customMap[$table] ?? null; }
使用时只需将原有join替换为自定义Scope:
$data = TabelA::joinSoftDeletedTable('tb_b', 'tb_a.id_b', '=', 'tb_b.id_b')->get();
特殊场景:临时取消软删除过滤
如果某些业务需要查询包含已删除的数据,可以添加自定义Scope移除过滤条件:
public function scopeWithTrashedJoin(Builder $query, string $table) { // 移除指定表的deleted_at过滤条件 $query->getQuery()->wheres = collect($query->getQuery()->wheres)->filter(function ($where) use ($table) { return !($where['column'] === "{$table}.deleted_at" && $where['operator'] === 'is null'); })->values()->all(); return $query; }
使用示例:
// 查询包含tb_b已删除数据的结果 $data = TabelA::join('tb_b', 'tb_a.id_b', '=', 'tb_b.id_b')->withTrashedJoin('tb_b')->get();
内容的提问来源于stack exchange,提问作者Aditya Pratama
相关产品推荐
相关产品推荐

