Skip to content

Support Guzzle 8 while keeping Guzzle 7 compatibility - #880

Open
mokuzon wants to merge 4 commits into
masterfrom
users/mokuson/NO-ISSUE/fix-guzzle8-ci
Open

Support Guzzle 8 while keeping Guzzle 7 compatibility#880
mokuzon wants to merge 4 commits into
masterfrom
users/mokuson/NO-ISSUE/fix-guzzle8-ci

Conversation

@mokuzon

@mokuzon mokuzon commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Supersedes #876 (guzzlehttp/guzzle to v8).

Its CI fails because laravel/framework (via dev dependency orchestra/testbench) still requires Guzzle ^7.8.2, so pinning the root requirement to ^8.0 makes composer install unresolvable.

Changes

@mokuzon mokuzon changed the title NO-ISSUE: Support Guzzle 8 while keeping Guzzle 7 compatibility Support Guzzle 8 while keeping Guzzle 7 compatibility Jul 28, 2026
@mokuzon
mokuzon force-pushed the users/mokuson/NO-ISSUE/fix-guzzle8-ci branch from 0f6da5c to 74c6695 Compare July 28, 2026 01:08
@mokuzon mokuzon self-assigned this Jul 28, 2026
@mokuzon
mokuzon force-pushed the users/mokuson/NO-ISSUE/fix-guzzle8-ci branch 3 times, most recently from bd6ecb4 to a9ae9ca Compare July 28, 2026 01:56
mokuzon added 3 commits July 28, 2026 14:01
Update the api.pebble template for two Guzzle 8 breaking changes:

- Replace GuzzleHttp\Utils::jsonEncode(), removed in Guzzle 8, with native
  json_encode() using JSON_THROW_ON_ERROR.
- Stop calling RequestException::getResponse(), which was moved to
  ResponseException in Guzzle 8. Catch BadResponseException for error
  responses instead, which carries the response in both Guzzle 7 and 8,
  and TransferException for failures without a response such as connection
  errors (both in sync and async paths).

Also widen the guzzlehttp/guzzle constraint to ^7.3 || ^8.0.
orchestra/testbench (via laravel/framework, which still requires guzzle
^7.8.2) cannot be installed together with guzzle ^8.0 only, which made
composer install unresolvable and broke CI.
Generated by python3 generate-code.py from the updated api.pebble
template. No manual edits.
The generate-code workflow regenerates against the latest line-openapi
(git submodule update --remote), so this PR fails that check while the
pinned submodule is behind. Pull in line/line-openapi@de8bd9e (Fix
access_token description in revokeChannelToken, line/line-openapi#130),
which replaces the closed auto-generated update PR #879. The resulting
diff is doc comments only.
@mokuzon
mokuzon force-pushed the users/mokuson/NO-ISSUE/fix-guzzle8-ci branch from a9ae9ca to 55acb61 Compare July 28, 2026 05:02
$options = $this->createHttpClientOption();
try {
$response = $this->client->send($request, $options);
} catch (RequestException $e) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why this catch structure changed: Guzzle 8 removed RequestException::getResponse(), which the previous code relied on. The response-carrying exceptions were moved to a new ResponseException subclass:

  Guzzle 7                                Guzzle 8
  ────────────────────────────────        ────────────────────────────────
  TransferException                       TransferException
  ├── RequestException                    ├── RequestException
  │    │  getResponse(): ?Response        │    │  (getResponse() removed!)
  │    └── BadResponseException           │    └── ResponseException  (new)
  │          4xx/5xx, response            │          │  getResponse(): Response
  │          guaranteed                   │          └── BadResponseException
  └── ConnectException                    └── NetworkException  (new)
                                               └── ConnectException

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If you want the equivalent exception, BadResponseException is not the right one. You should still catch RequestException and check if it's an instance of ResponseException to see if you can get the response headers or body.

Comment on lines +465 to +472
if (!$exception instanceof BadResponseException) {
throw new ApiException(
"[{$exception->getCode()}] {$exception->getMessage()}",
(int) $exception->getCode(),
null,
null
);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The original callback called $exception->getResponse() unconditionally, but rejections without a response (e.g. ConnectException, which has no getResponse()) made this crash with a fatal Error even on Guzzle 7 as a bug.

This branch converts such failures to a response-less ApiException, exactly as the sync path's catch (ConnectException) has always done.

@mokuzon
mokuzon marked this pull request as ready for review July 28, 2026 05:46
@mokuzon
mokuzon requested a review from a team July 28, 2026 05:46
$options = $this->createHttpClientOption();
try {
$response = $this->client->send($request, $options);
} catch (RequestException $e) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If you want the equivalent exception, BadResponseException is not the right one. You should still catch RequestException and check if it's an instance of ResponseException to see if you can get the response headers or body.

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