response()->acmeFailure(...) 这样的生成 API。
本页参考 Laravel 官方文档的默认分支 13.x,以及 2026 年 11 月 8 日确认的 Framework 最新版本 v13.40.0 的官方实现。在 响应 和 Macroable trait 的基础上,讲解将其作为分发扩展包的公开 API 维护时需要注意的边界。
注册目标是 Factory 而不是响应
Response 这个名称既指请求生成响应的门面,也指生成后的 HTTP 响应类。官方文档中的 Response::macro() 扩展的是门面解析到的 ResponseFactory。
不带参数的
response() 返回 Factory。而 response('正文') 或 response(null) 返回由 make() 生成的响应,因此调用目标并不相同。
公开错误 JSON 的生成 API
在 Service Provider 中注册
在src/HttpServiceProvider.php 的 boot() 中注册带扩展包前缀的 Macro。关于服务提供者的加载,请参阅 扩展包自动发现。
本例的公开契约是包含 error.code、error.message 的 JSON,以及 400 到 599 的 HTTP 状态码。这个状态码限制并非 Laravel Macro 机制的约束,而是扩展包作为错误专用 API 所规定的。
runningInConsole() 的否定条件里。HTTP 功能测试同样从 CLI 运行。
只负责这项注册的服务提供者,也不需要做成等待服务解析的延迟提供者。延迟注册的机制请参阅 延迟服务提供者。
从控制器调用
在业务逻辑判定出冲突后,传入事先确定的错误码和面向用户的消息。409,JSON 如下所示。与普通的 json() 一样,Content-Type 为 application/json。
Macro 只生成调用了该方法的响应。它不会自动把验证异常或认证错误转换为这种格式。也没有添加根据
Accept 头切换 HTML 与 JSON 的处理。Closure 的 $this 与调用路径
Factory 使用了 Macroable。调用实例上未定义的方法时,__call() 会查找已注册的 Macro,并把普通 Closure 重新绑定到被调用的实例上。
即使注册代码位于 Service Provider 的方法中,运行时的 $this 也是 Factory,而不是 Service Provider。因此示例中的 $this->json() 调用的是 Factory 的普通方法。
- 使用
$this的 Macro 要用普通的function (...) { ... }定义。static function没有实例的$this。 - 注册 invokable 对象时,不会像 Closure 那样重新绑定。所需的依赖应由该对象自己通过构造函数等方式接收。
- Macro 的参数是调用方传入的参数。即使声明了
Request等类型,Macroable 也不会从容器自动注入。
Response::acmeFailure() 看起来是静态调用,但内部会转发到 Factory 实例。而具体类的 Illuminate\Routing\ResponseFactory::acmeFailure() 是真正的静态调用,__callStatic() 会在没有实例的情况下绑定 Closure。像本例这样使用 $this->json() 的 Macro,不支持对具体类的静态调用。
将名称冲突视为兼容性问题
macro() 只是向以名称为键的静态数组赋值。注册已存在的名称也不会发出警告。
与其使用
failure 这样的通用名称,不如选择 acmeFailure 这样能识别扩展包的名称。升级 Laravel 时,也要用 method_exists(ResponseFactory::class, 'acmeFailure') 确认是否与实际方法冲突。
如果看到 hasMacro() 就无条件跳过注册,可能会把其他扩展包的实现当作自己的 API 来使用。只有在把”应用可以通过预先注册来覆盖”作为规范时,才公开其优先顺序以及所需的参数和返回值。上面的示例采用的方针是每次服务提供者启动时都注册扩展包自己的定义。
区分注册状态与响应的生命周期
Macro 注册表是 Factory 的静态属性。仅仅重新创建 Factory 或清除门面已解析的实例,并不会清除这张表。在同一个 PHP 进程中重新创建应用的测试里,注册状态也可能残留。 另一方面,示例中的 Macro 每次调用时都由json() 生成新的 JsonResponse。共享的是生成逻辑,而不是响应的正文或头本身。
在长驻 worker 中应遵守的边界如下。
- 注册时不要用
use (...)捕获当前的 Request、认证用户或租户。这些值应在调用时作为参数传入。 - 不要重用注册时生成的响应。每次都返回新的响应。
- 如果在注册时把配置作为值捕获,要在规范中说明该值会在进程内保留。
- 更新扩展包后,重启常驻进程以使新的注册代码生效。
用实际返回值测试 HTTP 契约
在能够通过 Composer 加载扩展包的 Laravel 13 验证用应用中,放置以下tests/Feature/ResponseMacroTest.php。关于 Tests\TestCase 和 HTTP 测试的基础,请参阅 HTTP 测试;关于扩展包单独的环境搭建,请参阅 扩展包测试。
Response::shouldReceive('acmeFailure') 这样模拟生成 API 本身,Macro 的注册、Closure 的绑定和 JSON 的生成都不会执行。请与调用次数的验证分开,像上面那样验证实际的 HTTP 响应。
此外,要把用户收到的正文、状态码和头视为兼容性的对象。
- 保持
error.code稳定以供机器判定,并将其与显示消息的变更区分开。 - 参数名会被用作命名参数,因此要像默认值和顺序一样确认变更的影响。
204这样没有正文的响应,不要作为错误 JSON 返回,而应使用noContent()等其他 API。- 即使允许通过自定义头修改
Content-Type的扩展,也要使其与正文格式一致。 - 如果目的是数据序列化或分页,不要只用 Macro 重新实现,也可以考虑 Eloquent API 资源。
长期维护检查清单
- 在 Laravel 13 支持的组合中,服务提供者能够启动,并且可以从辅助函数和门面两种方式调用。
- Macro 名称不与其他注册或 Factory 的实际方法冲突。
- 测试覆盖了 JSON 键、HTTP 状态码、头、默认值和命名参数的兼容性。
- 连续调用时不共享响应,也不在注册时保存 Request 或用户信息。
- 重置注册的测试与其他扩展包隔离。
- 更新流程中包含常驻进程的重启。