> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Response-Macros und HTTP-Verträge in Paketen

> Untersuchen Sie ResponseFactory und Macroable in Laravel 13 und lernen Sie paketeigene JSON-Antworten, Namenskollisionen, die Lebensdauer des Registrierungszustands sowie das Testen und Pflegen von HTTP-Verträgen kennen.

Manchmal möchten Sie, dass die Nutzer Ihres Pakets aus vielen Controllern dasselbe Fehler-JSON-Format zurückgeben können. Wenn Sie ein Response-Macro veröffentlichen, können Sie eine Erzeugungs-API wie `response()->acmeFailure(...)` hinzufügen, ohne dass Controller von einer Basisklasse erben müssen.

Diese Seite basiert auf der offiziellen Laravel-Dokumentation im Standard-Branch `13.x` und der offiziellen Implementierung des neuesten Framework-Releases `v13.40.0`, geprüft am 8. November 2026. Aufbauend auf den Grundlagen von [Responses](https://kawax.biz/de/responses.md) und dem [Macroable-Trait](https://kawax.biz/de/advanced/macroable.md) behandelt sie die Grenzen, die Sie beachten sollten, wenn Sie ein Macro als öffentliche API eines verteilten Pakets pflegen.

## Registrierung an der Factory, nicht an der Response

Der Name `Response` bezeichnet sowohl eine Facade, die die Erzeugung einer Antwort anfordert, als auch die Klasse der erzeugten HTTP-Antwort. `Response::macro()` aus der offiziellen Dokumentation erweitert `ResponseFactory`, also die Klasse, in die die Facade aufgelöst wird.

| Ziel | Rolle | Verwendung auf dieser Seite |
| - | - | - |
| `Illuminate\Support\Facades\Response` | Facade, die den `ResponseFactory`-Vertrag auflöst und aufruft | Registrieren und Aufrufen des Macros |
| `Illuminate\Routing\ResponseFactory` | Erzeugungs-APIs wie `json()`, `view()` und `noContent()` | Klasse, die das Macro hält |
| `Illuminate\Http\Response` | Bereits erzeugte HTTP-Antwort | Von der Factory getrenntes Macro-Register |
| `Illuminate\Http\JsonResponse` | JSON-Antwort | Rückgabewert dieses Macros |

`response()` ohne Argumente gibt die Factory zurück. `response('Inhalt')` und `response(null)` geben dagegen eine mit `make()` erzeugte Antwort zurück und rufen daher nicht dasselbe Ziel auf.

> [!WARNING]
>
> Eine Registrierung über `Illuminate\Http\Response::macro()` erzeugt kein Macro für `response()->acmeFailure()`. Verwechseln Sie in Ihren `use`-Anweisungen nicht die Facade mit der gleichnamigen Response-Klasse.

## Eine Erzeugungs-API für Fehler-JSON veröffentlichen

### Im Service Provider registrieren

Registrieren Sie in der Methode `boot()` von `src/HttpServiceProvider.php` ein Macro mit Paketpräfix. Wie der Provider geladen wird, erfahren Sie unter [Paket-Discovery](https://kawax.biz/de/advanced/package-discovery.md).

Der öffentliche Vertrag in diesem Beispiel besteht aus JSON mit `error.code` und `error.message` sowie einem HTTP-Status zwischen 400 und 599. Diese Statusbeschränkung ist keine Einschränkung des Macro-Mechanismus von Laravel. Das Paket legt sie für eine reine Fehler-API fest.

```php theme={null}
<?php

namespace Acme\Http;

use Illuminate\Http\JsonResponse;
use Illuminate\Routing\ResponseFactory;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;

final class HttpServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Response::macro('acmeFailure', function (
            string $code,
            string $message,
            int $status = 422,
            array $headers = [],
        ): JsonResponse {
            if ($status < 400 || $status > 599) {
                throw new InvalidArgumentException(
                    'acmeFailure requires a status between 400 and 599.'
                );
            }

            /** @var ResponseFactory $this */
            return $this->json([
                'error' => [
                    'code' => $code,
                    'message' => $message,
                ],
            ], $status, $headers);
        });
    }
}
```

Dieser Provider muss gestartet sein, bevor das Macro verwendet wird. Platzieren Sie die Registrierung nicht in einer Datei, die HTTP-Routen lädt, und nicht innerhalb einer negierten `runningInConsole()`-Bedingung. Auch HTTP-Feature-Tests laufen über die CLI.

Ein Provider, der nur diese Registrierung übernimmt, muss auch kein verzögerter Provider sein, der auf die Auflösung eines Dienstes wartet. Wie verzögerte Registrierung funktioniert, erfahren Sie unter [Verzögerte Service Provider](https://kawax.biz/de/advanced/deferred-provider.md).

### Aus einem Controller aufrufen

Nachdem Ihre Geschäftslogik einen Konflikt erkannt hat, übergeben Sie den festgelegten Fehlercode und eine Meldung für den Benutzer.

```php theme={null}
return response()->acmeFailure(
    code: 'order_conflict',
    message: '注文を確定できませんでした。',
    status: 409,
    headers: ['X-Acme-Error' => 'order_conflict'],
);
```

Der HTTP-Status ist `409`, und das JSON sieht wie folgt aus. Wie beim normalen `json()` ist der `Content-Type` `application/json`.

```json theme={null}
{
  "error": {
    "code": "order_conflict",
    "message": "注文を確定できませんでした。"
  }
}
```

Auch über die Facade können Sie dasselbe Factory-Macro aufrufen.

```php theme={null}
use Illuminate\Support\Facades\Response;

return Response::acmeFailure(
    'order_conflict', '注文を確定できませんでした。', 409
);
```

> [!NOTE]
>
> Das Macro erzeugt nur die Antworten, bei denen Sie es aufrufen. Es wandelt Validierungsausnahmen oder Authentifizierungsfehler nicht automatisch in dieses Format um. Es fügt auch keine Umschaltung zwischen HTML und JSON abhängig vom `Accept`-Header hinzu.

## `$this` in der Closure und der Aufrufpfad

Die Factory verwendet `Macroable`. Wenn Sie eine nicht definierte Methode einer Instanz aufrufen, sucht `__call()` das registrierte Macro und bindet eine normale Closure neu an die aufrufende Instanz.

```mermaid theme={null}
flowchart TD
    A["response()->acmeFailure(...)<br>oder Response::acmeFailure(...)"] --> B["ResponseFactory-Instanz"]
    B --> C["Macroable::__call()<br>$this der Closure neu binden"]
    C --> D["ResponseFactory::json()"]
    D --> E["Neue JsonResponse"]
```

Auch wenn der Registrierungscode in einer Methode des Service Providers steht, ist `$this` zur Laufzeit die Factory und nicht der Service Provider. Deshalb ruft `$this->json()` im Beispiel die normale Methode der Factory auf.

* Definieren Sie Macros, die `$this` verwenden, mit einer normalen `function (...) { ... }`. Eine `static function` hat kein Instanz-`$this`.
* Wenn Sie ein invokable Objekt registrieren, wird es nicht wie eine Closure neu gebunden. Benötigte Abhängigkeiten erhält dieses Objekt selbst, zum Beispiel über seinen Konstruktor.
* Die Argumente des Macros sind die Argumente, die der Aufrufer übergibt. Auch wenn Sie `Request` oder Ähnliches als Typ angeben, injiziert Macroable es nicht automatisch aus dem Container.

Der Facade-Aufruf `Response::acmeFailure()` sieht statisch aus, wird intern aber an die Factory-Instanz weitergeleitet. `Illuminate\Routing\ResponseFactory::acmeFailure()` auf der konkreten Klasse ist dagegen ein echter statischer Aufruf, und `__callStatic()` bindet die Closure ohne Instanz. Ein Macro wie dieses, das `$this->json()` verwendet, unterstützt keine statischen Aufrufe auf der konkreten Klasse.

## Namenskollisionen als Kompatibilitätsproblem behandeln

`macro()` weist lediglich einem statischen Array mit dem Namen als Schlüssel einen Wert zu. Die Registrierung eines vorhandenen Namens erzeugt keine Warnung.

| Situation | Verhalten der geprüften Implementierung |
| - | - |
| Erneute Registrierung eines Macros mit demselben Namen | Die spätere Registrierung überschreibt das frühere Macro |
| Registrierung unter dem Namen einer tatsächlich vorhandenen öffentlichen Methode wie `json()` | Die normale Methode wird aufgerufen, das Macro wird nie erreicht |
| `hasMacro($name)` ist `true` | Bedeutet, dass der Name registriert ist. Keine Garantie, dass keine Kollision mit einer echten Methode besteht |
| `mixin($object, replace: false)` | Vermeidet das Ersetzen bereits registrierter Macros. Keine Prüfung auf Kollisionen mit echten Methoden |

Wählen Sie statt eines allgemeinen Namens wie `failure` einen Namen, der Ihr Paket erkennen lässt, etwa `acmeFailure`. Prüfen Sie bei Laravel-Updates zusätzlich mit `method_exists(ResponseFactory::class, 'acmeFailure')` auf Kollisionen mit echten Methoden.

Wenn Sie `hasMacro()` prüfen und die Registrierung bedingungslos überspringen, verwenden Sie womöglich die Implementierung eines anderen Pakets, als wäre sie Ihre eigene API. Nur wenn Sie „die Anwendung kann es durch vorherige Registrierung überschreiben“ zur Spezifikation machen, sollten Sie diese Rangfolge zusammen mit den erforderlichen Argumenten und dem Rückgabewert veröffentlichen. Das obige Beispiel folgt dem Grundsatz, bei jedem Start des Providers die eigene Definition des Pakets zu registrieren.

## Registrierungszustand und Lebensdauer der Antwort trennen

Das Macro-Register ist eine **statische Eigenschaft** der Factory. Das Neuerzeugen der Factory oder das Löschen der aufgelösten Facade-Instanz leert dieses Register nicht. Auch in Tests, die die Anwendung innerhalb desselben PHP-Prozesses neu erzeugen, kann der Registrierungszustand bestehen bleiben.

Das Beispiel-Macro lässt dagegen bei jedem Aufruf `json()` eine neue `JsonResponse` erzeugen. Geteilt wird die Erzeugungslogik, nicht der Inhalt oder die Header der Antwort selbst.

Diese Grenzen sollten Sie in langlebigen Workern einhalten.

1. Erfassen Sie bei der Registrierung nicht den aktuellen Request, den authentifizierten Benutzer oder den Mandanten mit `use (...)`. Übergeben Sie diese Werte beim Aufruf als Argumente.
2. Verwenden Sie keine bei der Registrierung erzeugte Antwort wieder. Geben Sie jedes Mal eine neue Antwort zurück.
3. Wenn Sie Konfiguration bei der Registrierung als Wert erfassen, nehmen Sie in die Spezifikation auf, dass dieser Wert im Prozess erhalten bleibt.
4. Starten Sie nach einem Paket-Update langlebige Prozesse neu, damit der neue Registrierungscode wirksam wird.

[Lebensdauer von Paketdiensten entwerfen](https://kawax.biz/de/advanced/package-service-lifetimes.md) behandelt geteilten Container-Zustand und Zustand pro Request ausführlicher.

> [!WARNING]
>
> `Response::flushMacros()` löscht **alle Macros**, die an der Factory registriert sind. Auch Registrierungen anderer Pakete gehen verloren. Verwenden Sie es daher nicht in der Request-Verarbeitung in Produktion oder als unbedachten Aufräumschritt in einer gemeinsam genutzten Testumgebung. Prüfen Sie Kollisionen und Zurücksetzungen in einem isolierten Testprozess.

## Den HTTP-Vertrag mit echten Rückgabewerten testen

Legen Sie in einer Laravel-13-Testanwendung, die das Paket über Composer laden kann, die folgende Datei `tests/Feature/ResponseMacroTest.php` an. Zu `Tests\TestCase` und den Grundlagen von HTTP-Tests siehe [HTTP-Tests](https://kawax.biz/de/http-tests.md), zur Einrichtung einer eigenständigen Paketumgebung siehe [Pakete testen](https://kawax.biz/de/advanced/package-testing.md).

```php theme={null}
<?php

namespace Tests\Feature;

use Acme\Http\HttpServiceProvider;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;
use InvalidArgumentException;
use Tests\TestCase;

class ResponseMacroTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->app->register(HttpServiceProvider::class);
    }

    public function test_http_response_keeps_the_public_contract(): void
    {
        Route::get('/_acme/failure', function (): JsonResponse {
            return response()->acmeFailure(
                'order_conflict',
                '注文を確定できませんでした。',
                409,
                ['X-Acme-Error' => 'order_conflict'],
            );
        });

        $this->getJson('/_acme/failure')
            ->assertStatus(409)
            ->assertHeader('Content-Type', 'application/json')
            ->assertHeader('X-Acme-Error', 'order_conflict')
            ->assertExactJson([
                'error' => [
                    'code' => 'order_conflict',
                    'message' => '注文を確定できませんでした。',
                ],
            ]);
    }

    public function test_facade_returns_a_fresh_json_response(): void
    {
        $first = Response::acmeFailure('first', '最初のエラー');
        $second = response()->acmeFailure('second', '次のエラー', 503);

        $this->assertInstanceOf(JsonResponse::class, $first);
        $this->assertNotSame($first, $second);
        $this->assertSame(422, $first->getStatusCode());

        $first->setData(['changed' => true]);

        $this->assertSame(503, $second->getStatusCode());
        $this->assertSame('second', $second->getData(true)['error']['code']);
    }

    public function test_success_status_is_rejected(): void
    {
        $this->expectException(InvalidArgumentException::class);

        response()->acmeFailure('invalid_status', '設定を確認してください。', 200);
    }
}
```

Wenn Sie die Erzeugungs-API selbst mocken, etwa mit `Response::shouldReceive('acmeFailure')`, werden Macro-Registrierung, Closure-Bindung und JSON-Erzeugung nicht ausgeführt. Halten Sie Prüfungen der Aufrufanzahl getrennt und prüfen Sie wie oben die echte HTTP-Antwort.

Behandeln Sie außerdem Inhalt, Status und Header, die Nutzer erhalten, als Teil der Kompatibilität.

* Halten Sie `error.code` für die maschinelle Auswertung stabil und unterscheiden Sie es von Änderungen der Anzeigemeldung.
* Argumentnamen werden als benannte Argumente verwendet. Prüfen Sie die Auswirkungen ihrer Änderung daher genauso wie bei Standardwerten oder Reihenfolge.
* Geben Sie Antworten ohne Inhalt wie `204` nicht als Fehler-JSON zurück, sondern über eine andere API wie `noContent()`.
* Wenn Sie Erweiterungen zulassen, die den `Content-Type` über eigene Header ändern, halten Sie ihn mit dem Inhaltsformat konsistent.
* Wenn es um Datenserialisierung oder Paginierung geht, implementieren Sie diese nicht allein mit Macros neu, sondern ziehen Sie auch [Eloquent-API-Ressourcen](https://kawax.biz/de/eloquent-resources.md) in Betracht.

## Checkliste für die langfristige Pflege

* [ ] In den unterstützten Laravel-13-Kombinationen startet der Provider, und das Macro ist sowohl über den Helper als auch über die Facade aufrufbar.
* [ ] Der Macro-Name kollidiert weder mit anderen Registrierungen noch mit echten Methoden der Factory.
* [ ] Die Kompatibilität von JSON-Schlüsseln, HTTP-Status, Headern, Standardwerten und benannten Argumenten ist getestet.
* [ ] Aufeinanderfolgende Aufrufe teilen keine Antworten, und bei der Registrierung werden keine Request- oder Benutzerinformationen gespeichert.
* [ ] Tests, die Registrierungen zurücksetzen, sind von anderen Paketen isoliert.
* [ ] Der Neustart langlebiger Prozesse ist Teil des Update-Verfahrens.

## Verwendete Primärquellen

* [Offizielle Laravel-Dokumentation: Response macros und JSON responses (13.x)](https://github.com/laravel/docs/blob/13.x/responses.md#response-macros)
* [Offizielle Laravel-Dokumentation: Package development (13.x)](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Die Response-Facade und ihr Auflösungsziel (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Response.php)
* [Weiterleitung der Facade an die Instanz (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Facade.php)
* [Der Helper response() (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Foundation/helpers.php)
* [Erzeugungslogik der ResponseFactory (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Routing/ResponseFactory.php)
* [Registrierung, Bindung und Zurücksetzen in Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Macroable/Traits/Macroable.php)
* [Offizielle Tests für Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/tests/Support/SupportMacroableTest.php)


## Related topics

- [Macroable-Trait](https://kawax.biz/de/advanced/macroable.md)
- [Weiterführende Themen](https://kawax.biz/de/advanced/index.md)
- [HTTP-Client und Tests in Paketen](https://kawax.biz/de/advanced/package-http-client.md)
- [Eigene Cache-Stores und Verträge in Paketen](https://kawax.biz/de/advanced/package-cache-drivers.md)
- [Responses](https://kawax.biz/de/responses.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.