首页
/ Coolify 的 Laravel 表单验证最佳实践:Form Request、规则记法与条件验证

Coolify 的 Laravel 表单验证最佳实践:Form Request、规则记法与条件验证

2026-09-05 11:19:27作者:凤尚柏Louis

本文基于 Coolify 仓库内置的 Laravel 技能规则文档 validation.md,系统讲解 Laravel 应用(Coolify 基于 Laravel 12 构建)中表单验证与数据清洗的五条核心实践:用 Form Request 类收敛验证逻辑、数组/字符串两种规则记法的选择、validated()all() 的安全差异、Rule::when() 条件验证以及 after() 自定义验证钩子,并结合 Coolify 源码中的真实验证代码印证各条规则的实际应用。读完本文,你能在自己基于 Laravel 的项目中建立一套可复用、可审查、可测试的验证层写法。

规则文档的定位:先保持一致,再谈最佳实践

这条规则文件位于 .agents/skills/laravel-best-practices/rules/validation.md,是 Coolify 仓库为 AI 辅助开发与代码评审内置的 laravel-best-practices 技能包的一部分。技能包的总纲 SKILL.md 中有一条"Consistency First(一致性优先)"原则:

应用任何规则之前,先查看应用已经在做什么。Laravel 提供多种有效方案——最好的选择是代码库已经在用的那一种。不一致比次优模式更糟糕。

这一点在验证规则中体现得最为具体:规则文档明确要求"在新代码中优先数组语法,但先检查现有 Form Request,并匹配项目已经使用的记法"。也就是说,这些规则是没有既有模式时的默认值,而不是覆盖既有约定的指令

Coolify 代码库中验证逻辑的实际分布

在展开五条实践之前,先看 Coolify 当前代码库中验证逻辑长什么样,这决定了规则的落地语境。从源码结构看:

  • 没有独立的 Form Request 目录app/ 下未检索到继承 FormRequest 的类,验证逻辑以内联 $request->validate([...]) 的形式分布在 API 控制器(如 ApplicationsController.php)与 Livewire 组件(如 app/Livewire/ 下各组件的 validate() 调用)中;
  • 规则数组被抽成共享函数复用bootstrap/helpers/api.php 中的 sharedDataApplications() 返回一整套应用配置的验证规则数组,供多个 API 端点复用,其中大量使用 Rule::enum() 对象约束枚举字段:
// bootstrap/helpers/api.php(节选)
function sharedDataApplications()
{
    return [
        'git_repository' => 'string',
        'git_branch' => ['string', new ValidGitBranch],
        'build_pack' => Rule::enum(BuildPackTypes::class),
        // ...
        'static_image' => Rule::enum(StaticImageTypes::class),
        'redirect' => Rule::enum(RedirectTypes::class),
        'health_check_port' => 'integer|nullable|min:1|max:65535',
        'ports_mappings' => 'string|regex:/^(\d+:\d+)(,\d+:\d+)*$/|nullable',
    ];
}
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],

而 Fortify 认证动作中则是纯数组记法 + Rule::unique() 对象,见 CreateNewUser.phpRule::unique(User::class)UpdateUserProfileInformation.phpRule::unique('users')->ignore($user->id)

  • 框架版本支持所有新特性composer.json 声明 laravel/framework: ^12.65.0,因此 Rule::when()Rule::unique()->ignore()、Form Request 的 after() 方法等 API 均可直接使用。

这种"内联验证 + 共享规则数组"的现状,正是规则文档中"检查项目既有记法并保持一致"原则的现实注脚。

实践一:用 Form Request 类替代控制器内联验证

规则文档的第一条是把验证从控制器中抽离到专用的 Form Request 类。反例是控制器方法里直接内联 validate()

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|max:255',
        'body' => 'required',
    ]);
}

推荐写法是通过类型提示注入 Form Request,Laravel 会在控制器方法执行前自动完成验证:

public function store(StorePostRequest $request)
{
    Post::create($request->validated());
}

Form Request 的价值在于:

  1. 控制器方法变薄。方法体只保留业务动作(本例一行),方法名与行为一一对应,这也是技能包路由规则中"控制器方法不超过 10 行、超出部分抽到 Action/Service"的自然延伸;
  2. 验证逻辑可独立测试。Form Request 是独立类,可以在测试中直接构造并断言其 rules() 输出,而不必走完整的 HTTP 请求链路;
  3. 验证与授权同处一个类。Form Request 的 authorize() 方法让"这个用户能否执行该操作"与"这个请求是否合法"集中在一个文件里,配合技能包安全规则中"所有动作都必须通过 policy/gate 授权"的要求形成闭环。

对照 Coolify 的现状:其 API 控制器目前以内联 validate() 为主(如 Api/DeployController.phpApi/OtherController.php),共享规则通过 sharedDataApplications() 之类的 helper 函数组织。从源码结构看,这是一个可行的渐进式重构方向——将反复出现的规则数组封装为 Form Request,但依据"一致性优先"原则,重构时应整组推进、保持记法统一,避免新旧两套模式长期并存。

实践二:数组记法 vs 字符串记法

规则文档给出的取舍是:新代码优先数组语法,因为它可读性更强、且能自然地与 Rule:: 对象组合;但项目已有字符串记法时应先保持一致。两种写法的对照:

// 新代码的推荐写法
'email' => ['required', 'email', Rule::unique('users')],

// 若项目已使用字符串记法,则沿用
'email' => 'required|email|unique:users',

选择数组记法的关键动因是 Rule:: 对象带来的表达能力:

  • Rule::enum():把字段值约束到 PHP 枚举类,Coolify 用它验证 build_packstatic_imageredirect 等字段,枚举类定义见 app/Enums/ 目录(如 BuildPackTypes.phpStaticImageTypes.php);
  • Rule::unique()->ignore($id)->where(...)unique 规则链式附加条件,Coolify 的 Fortify 更新用户资料动作即用它排除当前用户自身(UpdateUserProfileInformation.php);
  • 自定义 Rule 对象:数组中可混入实现了 Illuminate\Contracts\Validation\Rule 接口的自定义验证器。Coolify 的 app/Rules/ 目录提供了大量实例,如 ValidGitBranchDockerImageFormatSafeWebhookUrlValidServerIp 等,sharedDataApplications()'git_branch' => ['string', new ValidGitBranch] 就是数组记法承载自定义 Rule 的直接证据;
  • 字符串记法的局限:字符串规则无法携带复杂对象参数,Rule:: 对象一旦介入就必须转为数组,这也是"新代码优先数组"的根本原因。

值得注意的一个细节:Coolify 现有代码中同样存在 'required', Rule::enum(...) 这种"数组里混字符串规则"的混合写法(ApplicationsController.php)。这属于规则文档所说的"既有约定"范畴——在该项目内新增验证时应沿用此风格,而不是强推纯对象风格或退回纯字符串风格。

实践三:永远用 validated() 获取数据

规则文档强调:批量操作只取已验证的数据,永远不要用 $request->all()

// 错误:把客户端提交的所有字段(含未声明的字段)灌进模型
Post::create($request->all());

// 正确:只取通过验证的白名单字段
Post::create($request->validated());

差异的本质是字段白名单

  • $request->all() 返回请求体中的全部键值对。如果客户端在正常字段之外夹带任意键(如 is_adminrole_idcreated_at),这些键会原样进入 create()。能否造成实际危害取决于模型的 $fillable/$guarded 配置——技能包的安全规则要求"每个模型必须定义 $fillable$guarded",正是为防御此类越权赋值;
  • $request->validated() 只返回在规则中声明且通过验证的字段,天然形成第二道白名单,即使模型 mass assignment 配置疏漏,未声明字段也进不来。

两层防线(规则白名单 + 模型 fillable 白名单)叠加后,验证失败与赋值越权都能被拦截。对 Coolify 这类管理面板项目尤为关键——其 API 端点接收大量配置字段(域名、端口映射、Docker 选项等),bootstrap/helpers/api.php 的规则数组本身就充当了"哪些字段可被该端点接受"的声明,validated() 是这套声明在数据写入侧的落地方式。

实践四:Rule::when() 做条件验证

当某个字段的验证要求取决于另一个字段的取值时,规则文档推荐 Rule::when()

'company_name' => [
    Rule::when($this->account_type === 'business', ['required', 'string', 'max:255']),
],

Rule::when($condition, $rules, $unlessRules) 的语义是:条件为真时应用第二组规则,否则应用第三组(可省略)。相比在控制器里先判断、再动态拼接规则数组的写法,Rule::when() 把条件直接内联到规则声明中,规则数组依然是一个完整、可审查的静态结构,不需要在方法体里写 if/else 分支。

Coolify 的历史代码中存在同类需求的旧式写法——docs/v5/archive/app/Http/Controllers/V5/ApplicationController.php.txt 中使用 Rule::requiredIf(fn () => $request->boolean('ingress_enabled')) 实现"条件必填"。requiredIf/requiredUnlessRule::when() 解决的是同一类问题,但在 Laravel 12 的新代码中,Rule::when() 是规则文档钦定的表达方式,条件与规则组一目了然。

实践五:after() 方法承载跨字段自定义验证

对于依赖多个字段、无法用单字段规则表达的验证逻辑,规则文档要求用 Form Request 的 after() 方法替代已被取代的 withValidator()

public function after(): array
{
    return [
        function (Validator $validator) {
            if ($this->quantity > Product::find($this->product_id)?->stock) {
                $validator->errors()->add('quantity', 'Not enough stock.');
            }
        },
    ];
}

三个要点:

  1. 返回的是闭包数组,框架在标准规则执行完毕后依次调用每个闭包,闭包拿到 Validator 实例,可随时通过 $validator->errors()->add('field', 'message') 追加错误,错误会走正常的错误响应与重定向回显流程;
  2. 闭包中直接引用 Form Request 自身属性(示例中的 $this->quantity$this->product_id),不需要从 $request 里再取一次,因为 Form Request 本身实现了数组访问接口;
  3. 适用前提after() 是 Laravel 11 引入、取代 withValidator() 的新 API。Coolify 的 composer.json 锁定 laravel/framework: ^12.65.0,属于该 API 的完整支持范围内,新代码应一律采用 after()

after() 的典型适用场景包括:两个字段必须同时提供或同时缺省、基于数据库状态的合法性校验(如示例中的库存检查)、跨字段的格式一致性检查。凡是需要"先看别的字段再下结论"的逻辑,都应收敛到这里,而不是散落在控制器方法体内。

五条实践的完整形态

把五条规则组合起来,一个遵循本文实践的 Form Request 大致是:

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string'],
            'company_name' => [
                Rule::when($this->account_type === 'business', ['required', 'string', 'max:255']),
            ],
        ];
    }

    public function after(): array
    {
        return [
            function (Validator $validator) {
                // 跨字段/跨模型的自定义校验
            },
        ];
    }
}

控制器侧只剩:public function store(StorePostRequest $request) { Post::create($request->validated()); }

落地建议:与既有代码库保持一致

最后回到规则文档反复强调的一致性原则,以及 SKILL.md 中"应用方式"一节给出的操作顺序:

  1. 先读同目录代码。给 Coolify 或同类 Laravel 项目新增验证前,先看兄弟控制器/组件用的是内联 validate() 还是 Form Request、规则是字符串还是数组记法,沿用既有风格;
  2. 在规则文档没有覆盖的场景再引入新模式。例如 Coolify 中共享规则数组(bootstrap/helpers/api.phpsharedDataApplications())就是针对"同一组字段被多个端点复用"这一现实问题形成的既有模式,它和 Form Request 并不互斥——可以先把规则数组固化下来,再逐步包装成 Form Request;
  3. validated() 是无需妥协的底线。无论采用哪种组织形式,批量写入只接受 validated() 的数据,这条规则在 Coolify 现有代码中同样成立。

这套"文档定默认、代码定惯例、一致性压过理论最优"的三层结构,是这份验证规则对实际工程最有参考价值的部分:规则本身只占五小节的篇幅,但配合仓库中 Fortify 动作、API 控制器与共享规则 helper 的真实用法,足以支撑起一套可执行、可评审、可演进的 Laravel 验证层规范。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384