response()->acmeFailure(...) 같은 생성 API를 추가할 수 있습니다.
이 페이지는 Laravel 공식 문서의 기본 브랜치 13.x와, 2026년 11월 8일에 확인한 Framework 최신 릴리스 v13.40.0의 공식 구현을 참조합니다. 응답과 Macroable 트레이트의 기본을 바탕으로, 배포 패키지의 공개 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는 Service Provider가 아니라 Factory입니다. 그래서 예의 $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를 생성합니다. 공유하는 것은 생성 처리이며, 응답의 본문이나 헤더 자체가 아닙니다.
장수명 워커에서 지켜야 할 경계는 다음과 같습니다.
- 등록 시에 현재 Request·인증 사용자·테넌트를
use (...)로 캡처하지 않는다. 이 값들은 호출 시 인수로 전달한다. - 등록 시에 생성한 응답을 재사용하지 않는다. 매번 새 응답을 반환한다.
- 설정을 등록 시에 값으로 캡처하는 경우, 그 값이 프로세스 안에서 유지된다는 것을 사양에 포함한다.
- 패키지 업데이트 후에는 상주 프로세스를 재시작해 새 등록 코드를 반영한다.
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나 사용자 정보를 등록 시에 보유하지 않는다.
- 등록을 리셋하는 테스트를 다른 패키지와 분리하고 있다.
- 상주 프로세스 재시작을 업데이트 절차에 포함하고 있다.