The question was:
I would like to handle errors from Guzzle when the server returns 4xx and 5xx status codes
While you can handle 4xx or 5xx status codes specifically, in practice it makes sense to catch all exceptions and handle the results accordingly.
The question is also, whether you just want to handle the errors or get the body? I think in most cases it would be sufficient to handle the errors and not get the message body or only get the body in the case of a non-error.
I would look at the documentation to check how your version of Guzzle handles it because this may change: https://docs.guzzlephp.org/en/stable/quickstart.html#exceptions
Also see this page in the official documentation on "Working with errors", which states:
Requests that receive a 4xx or 5xx response will throw a Guzzle\Http\Exception\BadResponseException
. More specifically, 4xx errors throw a Guzzle\Http\Exception\ClientErrorResponseException
, and 5xx errors throw a Guzzle\Http\Exception\ServerErrorResponseException
. You can catch the specific exceptions or just catch the BadResponseException
to deal with either type of error.
Guzzle 7 (from the docs):
. \RuntimeException
└── TransferException (implements GuzzleException)
└── RequestException
├── BadResponseException
│ ├── ServerException
│ └── ClientException
├── ConnectException
└── TooManyRedirectsException
So, your code might look like this:
use GuzzleHttp\Exception\TooManyRedirectsException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ConnectException;
// ...
try {
$response = $client->request('GET', $url);
if ($response->getStatusCode() >= 300) {
// is HTTP status code (for non-exceptions)
$statusCode = $response->getStatusCode();
// handle error
} else {
// is valid URL
}
} catch (TooManyRedirectsException $e) {
// handle too many redirects
} catch (ClientException | ServerException $e) {
// ClientException is thrown for 400 level errors if the http_errors request option is set to true.
// ServerException is thrown for 500 level errors if the http_errors request option is set to true.
if ($e->hasResponse()) {
// is HTTP status code, e.g. 500
$statusCode = $e->getResponse()->getStatusCode();
}
} catch (ConnectException $e) {
// ConnectException is thrown in the event of a networking error.
// This may be an error reported by lowlevel functionality
// (e.g. cURL error)
$handlerContext = $e->getHandlerContext();
if ($handlerContext['errno'] ?? 0) {
// this is the lowlevel error code, not the HTTP status code!!!
// for example 6 for "Couldn't resolve host" (for libcurl)
$errno = (int)($handlerContext['errno']);
}
// get a description of the error
$errorMessage = $handlerContext['error'] ?? $e->getMessage();
} catch (\Exception $e) {
// fallback, in case of other exception
}
If you really need the body, you can retrieve it as usual:
https://docs.guzzlephp.org/en/stable/quickstart.html#using-responses
$body = $response->getBody();
Under the hood, by default cURL is used or PHP stream wrapper, see Guzzle docs, so the error codes and messages may reflect that:
Guzzle no longer requires cURL in order to send HTTP requests. Guzzle will use the PHP stream wrapper to send HTTP requests if cURL is not installed. Alternatively, you can provide your own HTTP handler used to send requests. Keep in mind that cURL is still required for sending concurrent requests.