Laravel 10 + FilamentPHP 3:日期范围重叠验证实现
解决Laravel Filament中日期范围重叠的验证问题
需求说明
需要为SchoolPeriod模型的date_from和date_to字段添加验证,确保新提交的日期范围不会与数据库中已有记录的日期范围重叠,在Filament表单提交时弹出明确的验证提示。
现有代码
1. 数据表迁移文件
public function up(): void { Schema::create('school_periods', function (Blueprint $table) { $table->id(); $table->date('date_from'); $table->date('date_to'); $table->timestamps(); $table->unique(['date_from', 'date_to']); }); }
2. SchoolPeriod模型可填充字段
protected $fillable = [ 'date_from', 'date_to', 'name', 'is_active', 'created_by', ];
3. 当前Filament表单代码
public static function form(Form $form): Form { return $form ->schema([ Forms\Components\DatePicker::make('date_from') ->label('开始日期') ->required() ->beforeOrEqual('date_to'), Forms\Components\DatePicker::make('date_to') ->label('结束日期') ->required() ->afterOrEqual('date_from') ->unique(modifyRuleUsing: function(Unique $rule, callable $get){ $start = $get('date_from'); $end = $get('date_to'); return $rule->where(function ($q) use ($start, $end) { $q->where('date_from', '>=', $start) ->where('date_from', '<=', $end); }) ->orWhere(function ($q) use ($start, $end) { $q->where('date_to', '>=', $start) ->where('date_to', '<=', $end); }) ->orWhere(function ($q) use ($start, $end) { $q->where('date_from', '<', $start) ->where('date_to', '>', $end); }); }), ]); }
当前代码存在的问题
- 仅在
date_to字段添加验证,修改date_from时可能绕过检查 - 未处理编辑场景:编辑现有记录时,会把当前记录纳入重叠判断,导致误触发验证
- 使用
unique规则做范围重叠验证不够直观,错误提示不符合需求场景
修正后的实现方案
步骤1:创建自定义验证规则
执行命令生成规则类:
php artisan make:rule DateRangeNotOverlapping
编辑app/Rules/DateRangeNotOverlapping.php:
<?php namespace App\Rules; use App\Models\SchoolPeriod; use Illuminate\Contracts\Validation\Rule; class DateRangeNotOverlapping implements Rule { protected $ignoreId; // 编辑时传入当前记录ID,排除自身 public function __construct($ignoreId = null) { $this->ignoreId = $ignoreId; } public function passes($attribute, $value) { $startDate = request()->get('date_from'); $endDate = request()->get('date_to'); // 覆盖所有日期重叠场景 $query = SchoolPeriod::where(function ($query) use ($startDate, $endDate) { // 新范围的开始日期落在已有范围内 $query->where('date_from', '<=', $startDate) ->where('date_to', '>=', $startDate) // 新范围的结束日期落在已有范围内 ->orWhere('date_from', '<=', $endDate) ->where('date_to', '>=', $endDate) // 新范围完全包含已有范围 ->orWhere('date_from', '>=', $startDate) ->where('date_to', '<=', $endDate); }); // 编辑时排除当前记录 if ($this->ignoreId) { $query->where('id', '!=', $this->ignoreId); } return !$query->exists(); } public function message() { return '选择的日期范围与已有记录重叠,请重新选择。'; } }
步骤2:在Filament表单中应用规则
修改SchoolPeriodResource的表单代码,给两个日期字段都添加自定义规则:
use App\Rules\DateRangeNotOverlapping; use Filament\Forms\Components\DatePicker; public static function form(Form $form): Form { return $form ->schema([ DatePicker::make('date_from') ->label('开始日期') ->required() ->beforeOrEqual('date_to') // 根据是否为编辑场景传入记录ID ->rule(function ($record) { return new DateRangeNotOverlapping($record?->id); }), DatePicker::make('date_to') ->label('结束日期') ->required() ->afterOrEqual('date_from') ->rule(function ($record) { return new DateRangeNotOverlapping($record?->id); }), // 其他字段(如name、is_active等)保持原有配置 ]); }
可选:数据库层面双重保障
为避免程序验证失效,可在模型的saving事件中再次检查:
// 在SchoolPeriod模型中添加 protected static function booted() { static::saving(function ($period) { $overlap = SchoolPeriod::where(function ($query) use ($period) { $query->where('date_from', '<=', $period->date_from) ->where('date_to', '>=', $period->date_from) ->orWhere('date_from', '<=', $period->date_to) ->where('date_to', '>=', $period->date_to) ->orWhere('date_from', '>=', $period->date_from) ->where('date_to', '<=', $period->date_to); }) ->when($period->id, function ($query) use ($period) { $query->where('id', '!=', $period->id); }) ->exists(); if ($overlap) { throw new \Exception('日期范围与已有记录重叠,无法保存'); } }); }
内容的提问来源于stack exchange,提问作者Mr.Jepoyyy
相关产品推荐
相关产品推荐

