在 PHP 项目中使用 Guzzle 作为 HTTP 客户端时,网络波动、第三方服务不可用或返回非预期状态码都会中断程序。为了让调用方拿到清晰且一致的错误数据,我们需要对不同异常分类处理并封装为结构化响应。

为什么需要结构化错误
默认情况下 Guzzle 抛出异常时只携带原始信息,直接返回会给前端解析造成困难。结构化错误通常包含错误码、消息和可选的上下文,方便日志追踪和用户体验优化。
Guzzle 常见异常类型
RequestException:请求相关异常的基类ConnectException:网络连接失败,如 DNS 或超时ClientException:响应状态码为 4xxServerException:响应状态码为 5xx
捕获并转换异常
下面示例展示如何捕获异常并返回统一数组结构。
<?php
require 'vendor/autoload.php';
use GuzzleHttpClient;
use GuzzleHttpExceptionConnectException;
use GuzzleHttpExceptionClientException;
use GuzzleHttpExceptionServerException;
use GuzzleHttpExceptionRequestException;
function callApi(string $url): array
{
$client = new Client(['timeout' => 3]);
try {
$response = $client->get($url);
return [
'success' => true,
'code' => $response->getStatusCode(),
'data' => json_decode((string)$response->getBody(), true)
];
} catch (ConnectException $e) {
return buildError('CONNECT_ERROR', '网络连接失败', $e->getMessage());
} catch (ClientException $e) {
$code = $e->getResponse()->getStatusCode();
return buildError('CLIENT_ERROR', '客户端请求错误', $e->getMessage(), $code);
} catch (ServerException $e) {
$code = $e->getResponse()->getStatusCode();
return buildError('SERVER_ERROR', '服务端异常', $e->getMessage(), $code);
} catch (RequestException $e) {
return buildError('REQUEST_ERROR', '请求异常', $e->getMessage());
}
}
function buildError(string $type, string $msg, string $detail, int $httpCode = 0): array
{
return [
'success' => false,
'error_type' => $type,
'message' => $msg,
'http_code' => $httpCode,
'detail' => $detail
];
}
// 示例调用
$result = callApi('https://ipipp.com/api/test');
header('Content-Type: application/json');
echo json_encode($result, JSON_UNESCAPED_UNICODE);
在框架中统一处理
如果你使用 Laravel 等框架,可以在异常处理类中集中转换 Guzzle 异常,避免在每个业务方法里重复编写捕获逻辑。
示例:Laravel 异常渲染
<?php
namespace AppExceptions;
use GuzzleHttpExceptionRequestException;
use IlluminateFoundationExceptionsHandler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
public function render($request, Throwable $e)
{
if ($e instanceof RequestException) {
return response()->json([
'success' => false,
'error_type' => 'GUZZLE_ERROR',
'message' => '外部接口调用失败',
'detail' => $e->getMessage()
], 502);
}
return parent::render($request, $e);
}
}
最佳实践建议
| 实践点 | 说明 |
|---|---|
| 分层捕获 | 先捕获具体异常,再捕获基类异常 |
| 隐藏敏感信息 | detail 字段仅记录日志,不直出给用户 |
| 统一格式 | 所有接口错误响应结构保持一致 |
通过上述方式,你可以优雅地捕获 Guzzle 异常,并将它们转换为可预测的结构化错误信息,提升系统的稳定性和可维护性。