首页
/ Coolify 中的 Laravel Actions:`WithAttributes` 特性完全解析——属性化输入、校验与 handle 集成

Coolify 中的 Laravel Actions:`WithAttributes` 特性完全解析——属性化输入、校验与 handle 集成

2026-09-06 13:04:41作者:冯梦姬Eddie

本文基于 Coolify 仓库内的技能参考文档 with-attributes.md,系统讲解 lorisleiva/laravel-actions 包中 WithAttributes 特性的属性生命周期 API(读写、合并、请求填充)、与控制器校验管道复用的授权/校验钩子,以及从 fillvalidateAttributes() 再到 handle(...) 的端到端用法。读完后,你将能够为 Coolify 这类基于 Action 架构的 PaaS 后端编写“属性驱动”的 Action:输入以属性集存储而非方法参数传递,天然获得与 Invokable Controller 一致的授权与验证能力。

一、定位:什么时候需要 WithAttributes

参考文档给出的适用范围(Scope)是:当 Action 需要通过“内部属性(internal attributes)”存储并校验输入,而不是通过 handle(...) 的方法参数接收输入时,使用 WithAttributes 特性。

这与 Coolify 仓库中的主流写法形成鲜明对比。以 SaveProxyConfiguration 为例,Coolify 现有 Action 普遍采用“参数式”风格:

class SaveProxyConfiguration
{
    use AsAction;

    public function handle(Server $server, string $configuration): void
    {
        // ...
    }
}

这种风格适合“参数个数固定、类型明确”的场景。而当输入是一个动态键值集合(例如来自 HTTP 请求的表单字段)、且需要在执行前统一做授权与校验时,WithAttributes 提供的属性集模型更合适:输入先落到 Action 的属性袋中,再走校验管道,最后把“校验通过的数据”交给 handle(array $attributes)

包版本前提:Coolify 在 composer.json 中声明依赖 "lorisleiva/laravel-actions": "^2.10.2",即本文所有 API 均基于该包 2.x 版本(v2.10.2 已在 composer.lock 中锁定)。

二、属性生命周期 API:13 个方法全览

WithAttributes 特性提供的 API 可分四类:批量写入、请求填充、读取/写入单键、魔术属性访问,外加一个校验入口。

2.1 setRawAttributes —— 全量替换

用给定的 payload 替换全部属性(注意:是替换,不是合并):

$action->setRawAttributes([
    'key' => 'value',
]);

2.2 fill —— 增量合并

把给定属性合并进现有属性(已有同名键会被覆盖):

$action->fill([
    'key' => 'value',
]);

setRawAttributesfill 的区别是本文档强调的第一个要点:前者清空重来,后者叠加更新。

2.3 fillFromRequest —— 从请求填充(含键冲突规则)

把 HTTP 请求的输入与路由参数(route parameters)合并进属性。参考文档特别澄清了键冲突时的优先级:

当键冲突时,请求输入(request input)优先于路由参数。

$action->fillFromRequest($request);

也就是说,如果路由中有 /{title} 而请求体中也提交了 title 字段,最终属性袋中是请求体的值,而不是路由参数——这是文档“Common pitfalls”里专门点名的一个易错点(详见第六节)。

2.4 读取类方法

方法 行为 示例
all 返回全部属性 $action->all();
only 返回匹配指定键的属性 $action->only('title', 'body');
except 返回排除指定键后的属性 $action->except('body');
has 判断某个键是否存在 $action->has('title');
get 按键取值,支持可选默认值 $action->get('title'); / $action->get('title', 'Untitled');
set 按键设置单个属性值 $action->set('title', 'My blog post');

这套 API 与 Laravel 集合(Collection)的 all/only/except/has/get 语义高度一致,对熟悉 Eloquent 的开发者没有学习成本。

2.5 魔术属性访问

WithAttributes 同时通过 __get / __set / __isset 让属性可以像对象属性一样直接读写:

$action->title;                      // __get:读取属性
$action->title = 'My blog post';     // __set:更新属性
isset($action->title);              // __isset:判断属性是否存在

这使 handle(array $attributes) 内部处理结构化数据、调用方却可以用 $action->title 这类直观写法,两种风格可共存。

2.6 validateAttributes —— 校验入口

执行授权(authorization)与验证(validation),并返回校验后的数据(validated data)

$validatedData = $action->validateAttributes();

它是属性流(attribute-based flow)与参数流的分水岭:在 handle(...) 产生任何副作用之前调用它,才能确保进入业务逻辑的数据已经过授权与规则校验。

三、复用的校验/授权钩子:AttributeValidatorAsController 同构

参考文档明确列出:WithAttributes 内部使用的 AttributeValidatorAsController 复用同一套授权/验证钩子,共 12 个:

  • prepareForValidation —— 在授权与校验解析之前被调用,可向请求数据中 merge 额外字段;
  • authorize —— 授权逻辑,可返回 boolResponse(deny/allow);
  • rules —— 返回验证规则数组;
  • withValidator / afterValidator —— 在验证器上追加自定义后置检查;
  • getValidator —— 提供完全自定义的 validator 实例;
  • getValidationData —— 定义参与验证的数据(默认 $request->all());
  • getValidationMessages —— 自定义错误消息;
  • getValidationAttributes —— 为字段提供人类可读的名称;
  • getValidationRedirect —— 自定义校验失败后的重定向地址;
  • getValidationErrorBag —— 自定义错误 bag 名称(默认 default);
  • getValidationFailure / getAuthorizationFailure —— 覆写校验失败、授权失败的默认行为(例如抛出自定义异常)。

这一设计意味着:同一个 Action 既能作为对象/Job/Command 运行,也能作为 Invokable Controller 运行,且无论走哪条入口,校验语义都来自同一组钩子。Coolify 仓库的姊妹参考文档 controller.md 对其中每个钩子都给出了带注释的示例(如 prepareForValidation$request->merge([...])authorize 返回 Response::deny('...') 等),两者对照阅读可以覆盖属性流与控制器流的全部扩展点。

四、端到端示例:从 fillvalidateAttributes() 再到 handle(...)

继承参考文档的完整示例,并补充关键调用链注释:

class CreateArticle
{
    use AsAction;
    use WithAttributes;

    // 属性流的规则定义:键必须与属性袋中的键一一对应
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'min:8'],
            'body' => ['required', 'string'],
        ];
    }

    // 只接收“已校验”的属性数组,不直接信任原始输入
    public function handle(array $attributes): Article
    {
        return Article::create($attributes);
    }
}

// 1. 实例化并填充属性(等价于 setRawAttributes 后再 fill 的效果,此处为一次填充)
$action = CreateArticle::make()->fill([
    'title' => 'My first post',
    'body' => 'Hello world',
]);

// 2. 先走授权 + 校验;失败时按 getValidationFailure/getAuthorizationFailure 行为中断
$validated = $action->validateAttributes();

// 3. 再把校验通过的数据交给 handle,产生副作用
$article = $action->handle($validated);

调用链总结为三步:

  1. 填充make() 创建实例,fill() / fillFromRequest() / set() 把输入写入属性袋;
  2. 校验validateAttributes() 依次执行 authorize()rules() 管道,成功则返回 validated data,失败则走失败钩子;
  3. 执行handle(array $attributes) 只消费步骤 2 的产物,业务逻辑与输入解析彻底分离。

五、验证清单(Checklist)

参考文档给出的收尾自检清单,可作为代码评审依据:

  • 属性键(attribute keys)是显式且稳定的——不要在多个入口使用含义漂移的同名键;
  • 验证规则(rules())与实际期望的属性形状(shape)一一对应
  • 需要时,validateAttributes() 必须在任何副作用之前被调用;
  • 校验/授权钩子应在聚焦的单元测试中单独覆盖(与 SKILL.md 推荐的两层测试策略一致:一层测 handle(...) 业务正确性,一层测入口接线)。

六、常见陷阱(Common pitfalls)

参考文档明确列出三类高频错误,结合仓库实践补充说明:

  1. 同一 Action 中混用属性流与参数流:例如一部分入口用 fill(...) + validateAttributes(),另一部分入口直接把标量参数传给 handle(...),导致校验只在部分路径生效。应保持一个 Action 只采用一种输入契约;
  2. 误以为路由参数会覆盖请求输入:在 fillFromRequest 中,键冲突时请求输入获胜,路由参数只填充请求中不存在的键。依赖“路由参数兜底”或“路由参数优先”的逻辑都会与实际行为相悖;
  3. 使用外部输入时跳过 validateAttributes():这等于让未校验数据直接进入 handle(...) 的副作用逻辑,是典型的校验旁路。

七、在 Coolify 仓库中的落点与延伸阅读

  • 包依赖:composer.json 声明 lorisleiva/laravel-actions ^2.10.2,是本节全部 API 的版本前提;
  • Action 总入口约定(命名、handle(...) 优先、适配方法分工、fakes 测试矩阵):SKILL.md
  • 控制器入口与 12 个校验钩子的逐一示例:controller.md
  • 同目录下的 object.mdjob.mdlistener.mdcommand.mdtesting-fakes.mdtroubleshooting.md 分别覆盖其余入口与排障主题;
  • 仓库中现存的 Action 目录(app/Actions/,含 Server/Database/Proxy/ 等子域)展示了参数式写法的实际规模;从源码结构看,这些 Action 目前以“方法参数 + 类型提示”为主,WithAttributes 属于库提供、供 HTTP 绑定类 Action 按需启用的能力,两者并不互斥。

适用前提与限制:本文所有 API 行为描述均以 lorisleiva/laravel-actions 2.x(仓库锁定 v2.10.2)为准;fillFromRequest 的优先级规则、钩子列表来自参考文档对 AttributeValidator 的说明,实际开发时若升级大版本,应以对应版本的包源码为准复核。

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