You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
                    });
                }),
        ]);
}

当前代码存在的问题

  1. 仅在date_to字段添加验证,修改date_from时可能绕过检查
  2. 未处理编辑场景:编辑现有记录时,会把当前记录纳入重叠判断,导致误触发验证
  3. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 01:30:32