> ## 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-macro's en HTTP-contracten in packages

> Bestudeer ResponseFactory en Macroable in Laravel 13 om package-eigen JSON-responses, naamconflicten, de levensduur van de registratiestatus en het testen en onderhouden van HTTP-contracten te begrijpen.

Soms wil je dat de gebruikers van je package vanuit veel controllers hetzelfde formaat fout-JSON kunnen teruggeven. Door een response-macro te publiceren kun je een opbouw-API zoals `response()->acmeFailure(...)` toevoegen zonder dat controllers van een basisklasse hoeven te erven.

Deze pagina is gebaseerd op de officiële Laravel-documentatie op de standaardbranch `13.x` en de officiële implementatie van de nieuwste Framework-release `v13.40.0`, gecontroleerd op 8 november 2026. Voortbouwend op de basis van [Responses](https://kawax.biz/nl/responses.md) en de [Macroable-trait](https://kawax.biz/nl/advanced/macroable.md) behandelt ze de grenzen waar je rekening mee moet houden wanneer je een macro onderhoudt als publieke API van een gedistribueerd package.

## Registreer op de Factory, niet op de response

De naam `Response` verwijst zowel naar een facade die vraagt om een response op te bouwen als naar de klasse van de gegenereerde HTTP-response. Wat `Response::macro()` in de officiële documentatie uitbreidt, is `ResponseFactory`, de klasse waarnaar de facade wordt opgelost.

| Doel | Rol | Gebruik op deze pagina |
| - | - | - |
| `Illuminate\Support\Facades\Response` | Facade die het `ResponseFactory`-contract oplost en aanroept | Registreren en aanroepen van de macro |
| `Illuminate\Routing\ResponseFactory` | Opbouw-API's zoals `json()`, `view()` en `noContent()` | Klasse die de macro bevat |
| `Illuminate\Http\Response` | Al gegenereerde HTTP-response | Macroregister los van de Factory |
| `Illuminate\Http\JsonResponse` | JSON-response | Retourwaarde van deze macro |

`response()` zonder argumenten geeft de Factory terug. `response('inhoud')` en `response(null)` geven daarentegen een met `make()` gemaakte response terug, dus ze roepen niet hetzelfde doel aan.

> [!WARNING]
>
> Registreren op `Illuminate\Http\Response::macro()` maakt geen macro voor `response()->acmeFailure()`. Verwar in je `use`-statements de facade niet met de gelijknamige responseklasse.

## Een opbouw-API voor fout-JSON publiceren

### Registreren in een service provider

Registreer in de methode `boot()` van `src/HttpServiceProvider.php` een macro met een packageprefix. Hoe de provider wordt geladen, lees je in [Package discovery](https://kawax.biz/nl/advanced/package-discovery.md).

Het publieke contract in dit voorbeeld is JSON met `error.code` en `error.message`, plus een HTTP-status tussen 400 en 599. Deze statusbeperking is geen beperking van het macromechanisme van Laravel. Het package legt haar vast voor een API die alleen voor fouten bedoeld is.

```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);
        });
    }
}
```

Deze provider moet opgestart zijn voordat de macro wordt gebruikt. Plaats de registratie niet in een bestand dat HTTP-routes laadt, en ook niet binnen een omgekeerde `runningInConsole()`-voorwaarde. Ook HTTP-featuretests draaien vanuit de CLI.

Een provider die alleen deze registratie verzorgt, hoeft ook geen uitgestelde provider te zijn die wacht tot een service wordt opgelost. Hoe uitgestelde registratie werkt, lees je in [Uitgestelde service providers](https://kawax.biz/nl/advanced/deferred-provider.md).

### Aanroepen vanuit een controller

Nadat je bedrijfslogica een conflict heeft vastgesteld, geef je de gekozen foutcode en een bericht voor de gebruiker door.

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

De HTTP-status is `409` en de JSON ziet er als volgt uit. Net als bij een gewone `json()` is de `Content-Type` `application/json`.

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

Je kunt dezelfde Factory-macro ook via de facade aanroepen.

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

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

> [!NOTE]
>
> De macro bouwt alleen de responses op waar je hem aanroept. Hij zet validatie-exceptions of authenticatiefouten niet automatisch om naar dit formaat. Hij voegt ook geen omschakeling tussen HTML en JSON toe op basis van de `Accept`-header.

## `$this` in de Closure en het aanroeppad

De Factory gebruikt `Macroable`. Wanneer je een niet-gedefinieerde methode op een instantie aanroept, zoekt `__call()` de geregistreerde macro op en bindt een gewone Closure opnieuw aan de instantie waarop hij werd aangeroepen.

```mermaid theme={null}
flowchart TD
    A["response()->acmeFailure(...)<br>of Response::acmeFailure(...)"] --> B["ResponseFactory-instantie"]
    B --> C["Macroable::__call()<br>$this van de Closure opnieuw binden"]
    C --> D["ResponseFactory::json()"]
    D --> E["Nieuwe JsonResponse"]
```

Ook al staat de registratiecode in een methode van de service provider, `$this` is tijdens het uitvoeren de Factory en niet de service provider. Daarom roept `$this->json()` in het voorbeeld de gewone methode van de Factory aan.

* Definieer macro's die `$this` gebruiken met een gewone `function (...) { ... }`. Een `static function` heeft geen instantie-`$this`.
* Als je een invokable object registreert, wordt het niet opnieuw gebonden zoals een Closure. Laat dat object zelf de benodigde afhankelijkheden ontvangen, bijvoorbeeld via de eigen constructor.
* De argumenten van de macro zijn de argumenten die de aanroeper doorgeeft. Ook als je `Request` of iets dergelijks als type opgeeft, injecteert Macroable het niet automatisch vanuit de container.

De facade-aanroep `Response::acmeFailure()` lijkt statisch, maar wordt intern doorgestuurd naar de Factory-instantie. `Illuminate\Routing\ResponseFactory::acmeFailure()` op de concrete klasse is daarentegen een echte statische aanroep, en `__callStatic()` bindt de Closure zonder instantie. Een macro zoals deze, die `$this->json()` gebruikt, ondersteunt geen statische aanroepen op de concrete klasse.

## Naamconflicten behandelen als compatibiliteitsprobleem

`macro()` wijst alleen een waarde toe in een statische array met de naam als sleutel. Het registreren van een bestaande naam geeft geen waarschuwing.

| Situatie | Gedrag van de onderzochte implementatie |
| - | - |
| Een macro met dezelfde naam opnieuw registreren | De latere registratie overschrijft de eerdere macro |
| Registreren onder de naam van een publieke methode die echt op de klasse bestaat, zoals `json()` | De gewone methode wordt aangeroepen en de macro wordt nooit bereikt |
| `hasMacro($name)` is `true` | Betekent dat de naam geregistreerd is. Geen garantie dat er geen conflict met een echte methode is |
| `mixin($object, replace: false)` | Voorkomt het vervangen van al geregistreerde macro's. Geen controle op conflicten met echte methoden |

Kies liever een naam die je package herkenbaar maakt, zoals `acmeFailure`, dan een algemene naam zoals `failure`. Controleer bij Laravel-updates ook op conflicten met echte methoden met `method_exists(ResponseFactory::class, 'acmeFailure')`.

Als je `hasMacro()` controleert en de registratie onvoorwaardelijk overslaat, gebruik je mogelijk de implementatie van een ander package alsof het je eigen API is. Alleen wanneer je "de applicatie kan hem overschrijven door eerder te registreren" onderdeel van de specificatie maakt, publiceer je die voorrang samen met de vereiste argumenten en retourwaarde. Het voorbeeld hierboven volgt het beleid om bij elke start van de provider de eigen definitie van het package te registreren.

## Registratiestatus en levensduur van de response scheiden

Het macroregister is een **statische eigenschap** van de Factory. De Factory opnieuw aanmaken of de opgeloste facade-instantie wissen maakt dit register niet leeg. Ook in tests die de applicatie binnen hetzelfde PHP-proces opnieuw aanmaken, kan de registratiestatus blijven bestaan.

De voorbeeldmacro laat `json()` daarentegen bij elke aanroep een nieuwe `JsonResponse` aanmaken. Wat gedeeld wordt, is de opbouwlogica, niet de inhoud of headers van de response zelf.

Dit zijn de grenzen die je in langlopende workers moet respecteren.

1. Leg bij de registratie niet de huidige Request, de geauthenticeerde gebruiker of de tenant vast met `use (...)`. Geef deze waarden bij de aanroep als argumenten door.
2. Hergebruik geen response die bij de registratie is aangemaakt. Geef elke keer een nieuwe response terug.
3. Als je configuratie bij de registratie als waarde vastlegt, neem dan in de specificatie op dat die waarde binnen het proces behouden blijft.
4. Herstart na een package-update langlopende processen zodat de nieuwe registratiecode wordt toegepast.

[De levensduur van packageservices ontwerpen](https://kawax.biz/nl/advanced/package-service-lifetimes.md) behandelt gedeelde containerstatus en status per request uitgebreider.

> [!WARNING]
>
> `Response::flushMacros()` verwijdert **alle macro's** die op de Factory geregistreerd zijn. Ook registraties van andere packages gaan verloren. Gebruik het dus niet in requestverwerking in productie of als ondoordachte opruimstap in een gedeelde testomgeving. Controleer conflicten en resets in een geïsoleerd testproces.

## Het HTTP-contract testen met echte retourwaarden

Plaats in een Laravel 13-testapplicatie die het package via Composer kan laden het volgende `tests/Feature/ResponseMacroTest.php`. Voor `Tests\TestCase` en de basis van HTTP-tests, zie [HTTP-tests](https://kawax.biz/nl/http-tests.md). Voor het opzetten van een zelfstandige packageomgeving, zie [Packages testen](https://kawax.biz/nl/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);
    }
}
```

Als je de opbouw-API zelf mockt, zoals met `Response::shouldReceive('acmeFailure')`, worden de macroregistratie, de Closure-binding en het genereren van JSON niet uitgevoerd. Houd controles op het aantal aanroepen gescheiden en controleer zoals hierboven de echte HTTP-response.

Behandel daarnaast de inhoud, status en headers die gebruikers ontvangen als onderdeel van de compatibiliteit.

* Houd `error.code` stabiel voor machinale controles en onderscheid het van wijzigingen in het weergegeven bericht.
* Argumentnamen worden gebruikt als benoemde argumenten, dus controleer de impact van een wijziging net als bij standaardwaarden of volgorde.
* Geef responses zonder inhoud, zoals `204`, terug met een andere API zoals `noContent()`, niet als fout-JSON.
* Als je extensies toestaat die de `Content-Type` via eigen headers wijzigen, houd die dan consistent met het formaat van de inhoud.
* Als je doel dataserialisatie of paginering is, overweeg dan ook [Eloquent API-resources](https://kawax.biz/nl/eloquent-resources.md) in plaats van ze alleen met macro's opnieuw te implementeren.

## Checklist voor langetermijnonderhoud

* [ ] Op de ondersteunde Laravel 13-combinaties start de provider op en is de macro aan te roepen via zowel de helper als de facade.
* [ ] De macronaam conflicteert niet met andere registraties of met echte methoden van de Factory.
* [ ] De compatibiliteit van JSON-sleutels, HTTP-status, headers, standaardwaarden en benoemde argumenten wordt getest.
* [ ] Opeenvolgende aanroepen delen geen responses, en bij de registratie wordt geen Request- of gebruikersinformatie bewaard.
* [ ] Tests die registraties resetten, zijn geïsoleerd van andere packages.
* [ ] Het herstarten van langlopende processen maakt deel uit van de updateprocedure.

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: Response macros en JSON responses (13.x)](https://github.com/laravel/docs/blob/13.x/responses.md#response-macros)
* [Officiële Laravel-documentatie: Package development (13.x)](https://github.com/laravel/docs/blob/13.x/packages.md)
* [De Response-facade en waarnaar die wordt opgelost (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Response.php)
* [Doorsturen van de Facade naar de instantie (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Facade.php)
* [De helper response() (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Foundation/helpers.php)
* [Opbouwlogica van ResponseFactory (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Routing/ResponseFactory.php)
* [Registratie, binding en reset in Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Macroable/Traits/Macroable.php)
* [Officiële tests van Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/tests/Support/SupportMacroableTest.php)


## Related topics

- [Macroable-trait](https://kawax.biz/nl/advanced/macroable.md)
- [Geavanceerde onderwerpen](https://kawax.biz/nl/advanced/index.md)
- [HTTP-clients en tests voor packages](https://kawax.biz/nl/advanced/package-http-client.md)
- [Eigen cachestores en contracten voor packages](https://kawax.biz/nl/advanced/package-cache-drivers.md)
- [Praktische technieken voor Laravel Telescope](https://kawax.biz/nl/blog/telescope-introduction.md)


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