Saltar al contenido principal
A veces quieres que los usuarios de tu paquete puedan devolver el mismo formato de JSON de error desde muchos controladores. Si publicas una macro de respuesta, puedes añadir una API de construcción como response()->acmeFailure(...) sin exigir que los controladores hereden de una clase base. Esta página se basa en la documentación oficial de Laravel en la rama predeterminada 13.x y en la implementación oficial de la última versión del Framework, v13.40.0, revisadas el 8 de noviembre de 2026. Partiendo de los fundamentos de Respuestas y del trait Macroable, trata los límites que debes tener en cuenta al mantener una macro como API pública de un paquete distribuido.

Se registra en la Factory, no en la respuesta

El nombre Response designa tanto a una facade que solicita construir una respuesta como a la clase de la respuesta HTTP generada. Lo que extiende Response::macro() en la documentación oficial es ResponseFactory, la clase a la que se resuelve la facade. response() sin argumentos devuelve la Factory. En cambio, response('cuerpo') y response(null) devuelven una respuesta creada con make(), así que no llaman al mismo destino.
Registrar en Illuminate\Http\Response::macro() no crea una macro para response()->acmeFailure(). No confundas en tus sentencias use la facade con la clase de respuesta del mismo nombre.

Publicar una API de construcción para JSON de error

Registrarla en un service provider

Registra una macro con prefijo del paquete en el método boot() de src/HttpServiceProvider.php. Para saber cómo se carga el provider, consulta Descubrimiento de paquetes. El contrato público de este ejemplo es un JSON con error.code y error.message, junto con un estado HTTP entre 400 y 599. Esta restricción de estado no es una limitación del mecanismo de macros de Laravel. La define el paquete como parte de una API exclusiva para errores.
Este provider debe arrancar antes de que se use la macro. No pongas el registro en un archivo que carga rutas HTTP ni dentro de una condición runningInConsole() negada. Las pruebas de funcionalidad HTTP también se ejecutan desde la CLI. Un provider cuya única tarea es este registro tampoco necesita ser un provider diferido que espera a que se resuelva un servicio. Para entender el registro diferido, consulta Service providers diferidos.

Llamarla desde un controlador

Después de que tu lógica de negocio detecte un conflicto, pasa el código de error elegido y un mensaje para el usuario.
El estado HTTP es 409 y el JSON tiene esta forma. Igual que con un json() normal, el Content-Type es application/json.
También puedes llamar a la misma macro de la Factory mediante la facade.
La macro solo construye las respuestas donde la llamas. No convierte automáticamente las excepciones de validación ni los errores de autenticación a este formato. Tampoco añade ningún cambio entre HTML y JSON según la cabecera Accept.

El $this de la Closure y la ruta de llamada

La Factory usa Macroable. Cuando llamas a un método no definido de una instancia, __call() busca la macro registrada y vuelve a vincular una Closure normal a la instancia sobre la que se llamó. Aunque el código de registro esté dentro de un método del service provider, $this en tiempo de ejecución es la Factory, no el service provider. Por eso $this->json() en el ejemplo llama al método normal de la Factory.
  • Define las macros que usan $this con una function (...) { ... } normal. Una static function no tiene $this de instancia.
  • Si registras un objeto invocable, no se revincula como una Closure. Haz que ese objeto reciba sus dependencias por sí mismo, por ejemplo mediante su constructor.
  • Los argumentos de la macro son los que pasa quien la llama. Aunque declares el tipo Request u otro similar, Macroable no lo inyecta automáticamente desde el contenedor.
La llamada a la facade Response::acmeFailure() parece estática, pero internamente se reenvía a la instancia de la Factory. En cambio, Illuminate\Routing\ResponseFactory::acmeFailure() sobre la clase concreta es una llamada estática real, y __callStatic() vincula la Closure sin instancia. Una macro como esta, que usa $this->json(), no admite llamadas estáticas sobre la clase concreta.

Tratar las colisiones de nombres como un problema de compatibilidad

macro() solo asigna un valor en un array estático indexado por nombre. Registrar un nombre existente no genera ninguna advertencia. Elige un nombre que identifique tu paquete, como acmeFailure, en lugar de uno genérico como failure. Al actualizar Laravel, comprueba también las colisiones con métodos reales mediante method_exists(ResponseFactory::class, 'acmeFailure'). Si compruebas hasMacro() y omites el registro sin condiciones, podrías usar la implementación de otro paquete como si fuera tu propia API. Solo cuando conviertas “la aplicación puede sobrescribirla registrándola antes” en parte de la especificación, publica esa prioridad junto con los argumentos y el valor de retorno necesarios. El ejemplo anterior sigue la política de registrar la definición propia del paquete cada vez que arranca el provider.

Separar el estado de registro de la vida útil de la respuesta

El registro de macros es una propiedad estática de la Factory. Volver a crear la Factory o borrar la instancia resuelta de la facade no vacía este registro. El estado de registro también puede persistir en pruebas que vuelven a crear la aplicación dentro del mismo proceso PHP. Por otro lado, la macro del ejemplo hace que json() cree una nueva JsonResponse en cada llamada. Lo que se comparte es la lógica de construcción, no el cuerpo ni las cabeceras de la respuesta. Estos son los límites que debes respetar en workers de larga duración.
  1. No captures la Request actual, el usuario autenticado ni el tenant con use (...) en el momento del registro. Pasa esos valores como argumentos al llamar.
  2. No reutilices una respuesta creada en el momento del registro. Devuelve una respuesta nueva cada vez.
  3. Si capturas configuración como valor en el momento del registro, incluye en la especificación que ese valor se conserva dentro del proceso.
  4. Después de actualizar el paquete, reinicia los procesos de larga duración para que se aplique el nuevo código de registro.
Diseño del ciclo de vida de los servicios de paquetes trata con más detalle el estado compartido del contenedor y el estado por petición.
Response::flushMacros() elimina todas las macros registradas en la Factory. También se pierden los registros de otros paquetes, así que no lo uses en el procesamiento de peticiones en producción ni como limpieza descuidada en un entorno de pruebas compartido. Verifica las colisiones y los reinicios en un proceso de pruebas aislado.

Probar el contrato HTTP con valores de retorno reales

En una aplicación de pruebas de Laravel 13 que pueda cargar el paquete mediante Composer, añade el siguiente tests/Feature/ResponseMacroTest.php. Para Tests\TestCase y los fundamentos de las pruebas HTTP, consulta Pruebas HTTP. Para preparar un entorno de paquete independiente, consulta Pruebas de paquetes.
Si simulas la propia API de construcción, como con Response::shouldReceive('acmeFailure'), no se ejecutan el registro de la macro, la vinculación de la Closure ni la generación del JSON. Separa la verificación del número de llamadas y comprueba la respuesta HTTP real como se muestra arriba. Además, trata el cuerpo, el estado y las cabeceras que reciben los usuarios como parte de la compatibilidad.
  • Mantén error.code estable para la evaluación automática y distínguelo de los cambios en el mensaje mostrado.
  • Los nombres de los argumentos se usan como argumentos con nombre, así que comprueba el impacto de cambiarlos igual que con los valores predeterminados o el orden.
  • Devuelve las respuestas sin cuerpo, como 204, con otra API como noContent(), no como JSON de error.
  • Si permites extensiones que cambian el Content-Type mediante cabeceras propias, mantenlo coherente con el formato del cuerpo.
  • Si tu objetivo es la serialización de datos o la paginación, considera también los recursos de API de Eloquent en lugar de reimplementarlos solo con macros.

Lista de comprobación para el mantenimiento a largo plazo

  • En las combinaciones de Laravel 13 soportadas, el provider arranca y la macro se puede llamar tanto desde el helper como desde la facade.
  • El nombre de la macro no colisiona con otros registros ni con métodos reales de la Factory.
  • Hay pruebas de compatibilidad para las claves JSON, el estado HTTP, las cabeceras, los valores predeterminados y los argumentos con nombre.
  • Las llamadas consecutivas no comparten respuestas y no se guarda información de la Request ni del usuario en el momento del registro.
  • Las pruebas que reinician los registros están aisladas de otros paquetes.
  • El reinicio de los procesos de larga duración forma parte del procedimiento de actualización.

Fuentes primarias consultadas

Última modificación el 8 de noviembre de 2026