Skip to content

Conversation

@cosmastech
Copy link
Contributor

@cosmastech cosmastech commented Dec 11, 2025

While we can add middleware to the Guzzle stack, it doesn't actually build the Response object, which I would like to inspect and mutate.

  1. I want to record GraphQL query costs only for Shopify requests. I could add a listener for this, but then it's an extra function call for requests which are not for Shopify.
  2. I want to be able to leverage macros and all of the other niceties offered by the Illuminate Client\Request object, rather than mucking with the Guzzle handler.

An Example

class ShopifyResponse extends Response
{
    public function __construct($response)
    {
        $this->response = $response;
        $this->decoded = json_decode($this->body(), true, flags: JSON_THROW_ON_ERROR);
    }

    public function getQueryCost(): array {
        // ...
    }
}

Http::macro('shopifyRequest', function (ShopCredentials $shopCreds) {
    return Http::acceptJson()
        ->withHeader('X-Shopify-Access-Token', $shopCreds->token)
        ->baseUrl("https://{$shopCreds->shop_domain}.myshopify.com/admin/api/2025-10/")
        ->afterResponse(
            // Report any deprecation notices that were in the header
            function (Response $response) use ($shopCreds) {
                $header = $response->header('X-Shopify-API-Deprecated-Reason');
                if ($header) {
                    event(new ShopifyDeprecationNotice($shopCreds->shop, $header);
                }
         })
        ->afterResponse(
            // Map the response into our own custom response class
            fn (Response $response) => new ShopifyResponse($response->toPsrResponse())
        )
        ->afterResponse(
            // Report the cost of the query
            static fn (ShopifyResponse $response) => QueryCostResponse::report(
                $response->getQueryCost(),
                $shopCreds->shop
            )
        )
});

$r = Http::shopifyRequest($someShopCredentials)->post('graphql.json', ['query' => 'some graphql query']);

While obviously all of this can be done currently, it requires every developer to remember to run that pipeline on their response. We could make a macro that actually posts the response and maps it, however, that doesn't allow for mutating on a per request basis before sending (such as adding an additional header or setting throws() or a timeout).

@cosmastech cosmastech marked this pull request as draft December 11, 2025 13:19
@cosmastech cosmastech marked this pull request as ready for review December 13, 2025 13:23
@cosmastech cosmastech changed the title [12.x] Add ability to run callbacks after a response [12.x] Add ability to run callbacks after building an Http response Dec 13, 2025
Comment on lines 1032 to +1033
$this->dispatchResponseReceivedEvent($response);
$response = $this->runAfterResponseCallbacks($response);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not positive which of these should run first, to be honest.

@taylorotwell
Copy link
Member

@cosmastech could this method just be named then? Is that a bad choice? Does it conflict with the promise stuff we recently did?

@cosmastech
Copy link
Contributor Author

cosmastech commented Dec 17, 2025

@cosmastech could this method just be named then? Is that a bad choice? Does it conflict with the promise stuff we recently did?

I think then() is confusing between I would have something like Http::then(fn ($response) => new ShopifyResponse($response))->post('graphql.json', []) where then() precedes the action.

I don't think it creates a code conflict with promises, though it may be confusing. One instance of then() takes a callable with a param of PromiseInterface, while the non-async version executes a callback on the Response itself.

You're the guy with the good names, so I'll leave it up to you 😆

@taylorotwell taylorotwell merged commit 1222ed8 into laravel:12.x Dec 17, 2025
70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants