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

# Macros de réponse et contrats HTTP dans les packages

> Étudiez ResponseFactory et Macroable dans Laravel 13 pour comprendre les réponses JSON propres à un package, les collisions de noms, la durée de vie de l'état d'enregistrement, ainsi que le test et la maintenance des contrats HTTP.

Il arrive que vous souhaitiez permettre aux utilisateurs de votre package de renvoyer le même format de JSON d'erreur depuis de nombreux contrôleurs. En publiant une macro de réponse, vous pouvez ajouter une API de construction comme `response()->acmeFailure(...)` sans imposer aux contrôleurs d'hériter d'une classe de base.

Cette page s'appuie sur la documentation officielle de Laravel sur la branche par défaut `13.x` et sur l'implémentation officielle de la dernière version du Framework, `v13.40.0`, vérifiées le 8 novembre 2026. En partant des bases des [Réponses](https://kawax.biz/fr/responses.md) et du [trait Macroable](https://kawax.biz/fr/advanced/macroable.md), elle traite des limites à respecter lorsque vous maintenez une macro comme API publique d'un package distribué.

## L'enregistrement se fait sur la Factory, pas sur la réponse

Le nom `Response` désigne à la fois une facade qui demande la construction d'une réponse et la classe de la réponse HTTP générée. Ce que `Response::macro()` étend dans la documentation officielle, c'est `ResponseFactory`, la classe vers laquelle la facade est résolue.

| Cible | Rôle | Usage dans cette page |
| - | - | - |
| `Illuminate\Support\Facades\Response` | Facade qui résout et appelle le contrat `ResponseFactory` | Enregistrement et appel de la macro |
| `Illuminate\Routing\ResponseFactory` | API de construction comme `json()`, `view()` et `noContent()` | Classe qui contient la macro |
| `Illuminate\Http\Response` | Réponse HTTP déjà générée | Registre de macros distinct de la Factory |
| `Illuminate\Http\JsonResponse` | Réponse JSON | Valeur de retour de cette macro |

`response()` sans argument renvoie la Factory. En revanche, `response('corps')` et `response(null)` renvoient une réponse créée par `make()`, ils n'appellent donc pas la même cible.

> [!WARNING]
>
> Un enregistrement via `Illuminate\Http\Response::macro()` ne crée pas de macro `response()->acmeFailure()`. Ne confondez pas, dans vos instructions `use`, la facade et la classe de réponse du même nom.

## Publier une API de construction pour le JSON d'erreur

### L'enregistrer dans un service provider

Enregistrez une macro préfixée par le nom du package dans la méthode `boot()` de `src/HttpServiceProvider.php`. Pour savoir comment le provider est chargé, consultez [Découverte de packages](https://kawax.biz/fr/advanced/package-discovery.md).

Le contrat public de cet exemple est un JSON comportant `error.code` et `error.message`, accompagné d'un statut HTTP compris entre 400 et 599. Cette restriction de statut n'est pas une limite du mécanisme de macros de Laravel. C'est le package qui la définit pour une API dédiée aux erreurs.

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

Ce provider doit avoir démarré avant que la macro soit utilisée. Ne placez pas l'enregistrement dans un fichier qui charge les routes HTTP, ni dans une condition `runningInConsole()` inversée. Les tests fonctionnels HTTP s'exécutent eux aussi depuis la CLI.

Un provider dont le seul rôle est cet enregistrement n'a pas non plus besoin d'être un provider différé qui attend la résolution d'un service. Pour le fonctionnement de l'enregistrement différé, consultez [Service providers différés](https://kawax.biz/fr/advanced/deferred-provider.md).

### L'appeler depuis un contrôleur

Une fois qu'un conflit a été détecté par votre logique métier, transmettez le code d'erreur choisi et un message destiné à l'utilisateur.

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

Le statut HTTP est `409` et le JSON a la forme suivante. Comme avec un `json()` classique, le `Content-Type` est `application/json`.

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

Vous pouvez aussi appeler la même macro de la Factory via la facade.

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

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

> [!NOTE]
>
> La macro ne construit que les réponses là où vous l'appelez. Elle ne convertit pas automatiquement les exceptions de validation ou les erreurs d'authentification dans ce format. Elle n'ajoute pas non plus de bascule entre HTML et JSON selon l'en-tête `Accept`.

## Le `$this` de la Closure et le chemin d'appel

La Factory utilise `Macroable`. Lorsque vous appelez une méthode non définie sur une instance, `__call()` recherche la macro enregistrée et relie à nouveau une Closure ordinaire à l'instance sur laquelle elle a été appelée.

```mermaid theme={null}
flowchart TD
    A["response()->acmeFailure(...)<br>ou Response::acmeFailure(...)"] --> B["Instance de ResponseFactory"]
    B --> C["Macroable::__call()<br>Relie à nouveau le $this de la Closure"]
    C --> D["ResponseFactory::json()"]
    D --> E["Nouvelle JsonResponse"]
```

Même si le code d'enregistrement se trouve dans une méthode du service provider, `$this` à l'exécution est la Factory et non le service provider. C'est pourquoi `$this->json()` dans l'exemple appelle la méthode ordinaire de la Factory.

* Définissez les macros qui utilisent `$this` avec une `function (...) { ... }` ordinaire. Une `static function` n'a pas de `$this` d'instance.
* Si vous enregistrez un objet invocable, il n'est pas relié à nouveau comme une Closure. Faites recevoir à cet objet les dépendances dont il a besoin, par exemple via son propre constructeur.
* Les arguments de la macro sont ceux que passe l'appelant. Même si vous déclarez le type `Request` ou un type similaire, Macroable ne l'injecte pas automatiquement depuis le conteneur.

L'appel via la facade `Response::acmeFailure()` semble statique, mais il est transmis en interne à l'instance de la Factory. En revanche, `Illuminate\Routing\ResponseFactory::acmeFailure()` sur la classe concrète est un véritable appel statique, et `__callStatic()` lie la Closure sans instance. Une macro comme celle-ci, qui utilise `$this->json()`, ne prend pas en charge les appels statiques sur la classe concrète.

## Traiter les collisions de noms comme un problème de compatibilité

`macro()` se contente d'affecter une valeur dans un tableau statique indexé par nom. Enregistrer un nom existant ne produit aucun avertissement.

| Situation | Comportement de l'implémentation vérifiée |
| - | - |
| Réenregistrer une macro portant le même nom | L'enregistrement le plus récent écrase la macro précédente |
| Enregistrer un nom identique à une méthode publique réellement présente dans la classe, comme `json()` | La méthode ordinaire est appelée et la macro n'est jamais atteinte |
| `hasMacro($name)` vaut `true` | Signifie que le nom est enregistré. Ne garantit pas l'absence de collision avec une méthode réelle |
| `mixin($object, replace: false)` | Évite de remplacer les macros déjà enregistrées. Ne vérifie pas les collisions avec les méthodes réelles |

Préférez un nom qui identifie votre package, comme `acmeFailure`, à un nom générique comme `failure`. Lors des mises à jour de Laravel, vérifiez aussi les collisions avec les méthodes réelles à l'aide de `method_exists(ResponseFactory::class, 'acmeFailure')`.

Si vous vérifiez `hasMacro()` et ignorez l'enregistrement sans condition, vous risquez d'utiliser l'implémentation d'un autre package comme s'il s'agissait de votre propre API. Ce n'est que si vous faites de « l'application peut la remplacer en l'enregistrant au préalable » une partie de la spécification que vous devez publier cette priorité avec les arguments et la valeur de retour requis. L'exemple ci-dessus suit le principe d'enregistrer la définition propre au package à chaque démarrage du provider.

## Séparer l'état d'enregistrement de la durée de vie des réponses

Le registre des macros est une **propriété statique** de la Factory. Recréer la Factory ou effacer l'instance résolue de la facade ne vide pas ce registre. L'état d'enregistrement peut aussi persister dans les tests qui recréent l'application au sein du même processus PHP.

En revanche, la macro de l'exemple fait créer une nouvelle `JsonResponse` par `json()` à chaque appel. Ce qui est partagé, c'est la logique de construction, et non le corps ou les en-têtes de la réponse eux-mêmes.

Voici les limites à respecter dans les workers de longue durée.

1. Ne capturez pas la Request courante, l'utilisateur authentifié ou le tenant avec `use (...)` au moment de l'enregistrement. Passez ces valeurs en arguments au moment de l'appel.
2. Ne réutilisez pas une réponse créée au moment de l'enregistrement. Renvoyez une nouvelle réponse à chaque fois.
3. Si vous capturez une configuration sous forme de valeur au moment de l'enregistrement, indiquez dans la spécification que cette valeur est conservée dans le processus.
4. Après une mise à jour du package, redémarrez les processus de longue durée pour que le nouveau code d'enregistrement soit pris en compte.

[Concevoir la durée de vie des services d'un package](https://kawax.biz/fr/advanced/package-service-lifetimes.md) traite plus en détail l'état partagé du conteneur et l'état propre à chaque requête.

> [!WARNING]
>
> `Response::flushMacros()` supprime **toutes les macros** enregistrées sur la Factory. Les enregistrements des autres packages sont également perdus. Ne l'utilisez donc pas dans le traitement des requêtes en production, ni comme nettoyage hâtif dans un environnement de test partagé. Vérifiez les collisions et les réinitialisations dans un processus de test isolé.

## Tester le contrat HTTP avec de vraies valeurs de retour

Dans une application de test Laravel 13 capable de charger le package via Composer, ajoutez le fichier `tests/Feature/ResponseMacroTest.php` suivant. Pour `Tests\TestCase` et les bases des tests HTTP, consultez [Tests HTTP](https://kawax.biz/fr/http-tests.md). Pour préparer un environnement de package autonome, consultez [Tester les packages](https://kawax.biz/fr/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);
    }
}
```

Si vous simulez l'API de construction elle-même, comme avec `Response::shouldReceive('acmeFailure')`, ni l'enregistrement de la macro, ni la liaison de la Closure, ni la génération du JSON ne sont exécutés. Séparez la vérification du nombre d'appels et contrôlez la vraie réponse HTTP comme ci-dessus.

En outre, considérez le corps, le statut et les en-têtes reçus par les utilisateurs comme relevant de la compatibilité.

* Gardez `error.code` stable pour les traitements automatiques, et distinguez-le des modifications du message affiché.
* Les noms d'arguments sont utilisés comme arguments nommés. Vérifiez donc l'impact de leur modification au même titre que les valeurs par défaut ou l'ordre.
* Renvoyez les réponses sans corps, comme `204`, avec une autre API comme `noContent()`, et non sous forme de JSON d'erreur.
* Si vous autorisez des extensions qui modifient le `Content-Type` via des en-têtes personnalisés, gardez-le cohérent avec le format du corps.
* Si votre objectif est la sérialisation des données ou la pagination, envisagez aussi les [ressources d'API Eloquent](https://kawax.biz/fr/eloquent-resources.md) plutôt que de les réimplémenter avec des macros seules.

## Liste de contrôle pour la maintenance à long terme

* [ ] Sur les combinaisons Laravel 13 prises en charge, le provider démarre et la macro peut être appelée depuis le helper comme depuis la facade.
* [ ] Le nom de la macro n'entre en collision ni avec d'autres enregistrements ni avec des méthodes réelles de la Factory.
* [ ] La compatibilité des clés JSON, du statut HTTP, des en-têtes, des valeurs par défaut et des arguments nommés est testée.
* [ ] Les appels successifs ne partagent pas de réponses, et aucune information de Request ou d'utilisateur n'est conservée au moment de l'enregistrement.
* [ ] Les tests qui réinitialisent les enregistrements sont isolés des autres packages.
* [ ] Le redémarrage des processus de longue durée fait partie de la procédure de mise à jour.

## Sources primaires consultées

* [Documentation officielle de Laravel : Response macros et JSON responses (13.x)](https://github.com/laravel/docs/blob/13.x/responses.md#response-macros)
* [Documentation officielle de Laravel : Package development (13.x)](https://github.com/laravel/docs/blob/13.x/packages.md)
* [La facade Response et sa cible de résolution (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Response.php)
* [Transmission de la Facade vers l'instance (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Facade.php)
* [Le helper response() (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Foundation/helpers.php)
* [Logique de construction de ResponseFactory (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Routing/ResponseFactory.php)
* [Enregistrement, liaison et réinitialisation dans Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Macroable/Traits/Macroable.php)
* [Tests officiels de Macroable (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/tests/Support/SupportMacroableTest.php)


## Related topics

- [Trait Macroable](https://kawax.biz/fr/advanced/macroable.md)
- [Sujets avancés](https://kawax.biz/fr/advanced/index.md)
- [Stores de cache personnalisés et contrats pour les packages](https://kawax.biz/fr/advanced/package-cache-drivers.md)
- [Client HTTP et tests dans un package](https://kawax.biz/fr/advanced/package-http-client.md)
- [Contrats](https://kawax.biz/fr/contracts.md)


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