Erreurs
Chaque échec lève une exception. Une classe pour un refus, une pour l'absence de réponse, et un request id sur chaque erreur d'API.
En attraper une
use OpenEmail\Exception\ApiException;use OpenEmail\Exception\NetworkException; $message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; try { $client->emails->send($message);} catch (ApiException $error) { if ($error->isValidation()) { error_log($error->errorCode . ' ' . $error->param . ' ' . $error->getMessage()); } if ($error->isPermission()) { $book = $client->addresses->listAll(); error_log('this key may send as ' . implode(', ', array_column($book->addresses, 'address'))); } if ($error->isRateLimited()) { error_log('try again in ' . $error->retryAfterSeconds . ' seconds'); } error_log($error->status . ' ' . $error->requestId); throw $error;} catch (NetworkException $error) { if ($error->isTimeout()) { error_log('no answer in time'); } throw $error;}Un permission_error sur un envoi vient généralement de la portée d'envoi de la CLÉ, un domaine ou une adresse qui ne lui a pas été accordé, plutôt que de l'espace de travail, et c'est pourquoi l'exemple journalise les adresses depuis lesquelles addresses->listAll() indique que cette clé peut envoyer.
Chaque type de refus a sa propre sous-classe : un catch peut donc choisir par classe ceux qu'il traite et laisser remonter les autres.
use OpenEmail\Exception\AuthenticationException;use OpenEmail\Exception\OpenEmailException;use OpenEmail\Exception\PermissionException;use OpenEmail\Exception\ValidationException; $message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; try { $client->emails->send($message);} catch (ValidationException $error) { error_log($error->param . ': ' . $error->getMessage());} catch (AuthenticationException|PermissionException $error) { error_log('the key cannot do this: ' . $error->errorCode); throw $error;} catch (OpenEmailException $error) { error_log($error::class . ': ' . $error->getMessage()); throw $error;}Les classes
Toutes les classes se trouvent dans OpenEmail\Exception.
| Classe | Quand |
|---|---|
| OpenEmailException | L'interface qu'implémente chaque exception que lève le package : catch (OpenEmailException $error) les intercepte donc toutes, InvalidArgumentException comprise. |
| ApiException | L'API a répondu, et pas par un succès. Porte status, type, errorCode, param, docUrl, requestId, retryAfterSeconds, fields et body. Levée telle quelle quand type vaut api_error, comme pour une panne du serveur, et sous la sous-classe de son type sinon. Elle étend RuntimeException. |
| InvalidRequestException, AuthenticationException, PermissionException, NotFoundException, ConflictException, ValidationException et RateLimitException | Sous-classes d'ApiException, une pour chaque type : invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, validation_error et rate_limit_error. |
| NetworkException | Aucune réponse n'est arrivée : DNS, TLS, une connexion refusée ou coupée, ou le délai dépassé. getPrevious() contient l'exception sous-jacente, et isTimeout() vaut true quand le délai était la raison. Elle étend RuntimeException. |
| WebhookSignatureException | OpenEmail::verifyWebhookSignature() a refusé une livraison. Elle étend UnexpectedValueException. |
| InvalidArgumentException | Levée avant tout envoi : une clé manquante ou mal formée, une baseUrl: inutilisable, un id vide. Elle étend l'InvalidArgumentException propre à PHP, car elle signifie que l'appel lui-même est erroné. |
Ce que porte une ApiException
getMessage()string- La phrase de l'API elle-même, écrite pour un humain, qui nomme la valeur fautive quand il y en a une. Ce n'est pas un identifiant stable : branchez-vous donc sur `errorCode`.
statusint or null- Le statut HTTP de la réponse, que `getCode()` renvoie aussi. null seulement quand un succès est revenu sous une forme que le client n'a pas pu lire.
typestring- L'une des huit valeurs de `OpenEmail\Constants\ErrorTypes`, un ensemble fixe qui ne s'agrandira pas. Quand le corps n'en nomme aucune, elle est déduite du statut.
errorCodestring- L'échec précis, comme `from_address_forbidden` ou `invalid_email_address`. Il s'appelle `errorCode` parce que PHP réserve `code` au nombre que renvoie `getCode()`. Ouvert et additif : traitez donc un code que vous ne reconnaissez pas comme son `type`. Il vaut `unrecognised_response` quand le corps n'était pas l'enveloppe d'erreur de l'API.
paramstring or null- Le champ refusé, sous forme de chemin pointé comme `to.0`, quand l'échec en nomme un.
docUrlstring or null- Une page sur cet échec, quand l'API en nomme une.
requestIdstring or null- L'id sous lequel le serveur a journalisé la requête, tiré du corps ou de l'en-tête `x-request-id`.
retryAfterSecondsint, float or null- L'attente demandée par le serveur dans `Retry-After`, en secondes, qu'il ait envoyé un nombre ou une date. null quand il n'en a envoyé aucune.
fieldsarray or null- Un tableau par problème, chacun avec `key` et `error`, comme `['key' => 'email', 'error' => 'email']` quand `forms->subscribe` a refusé les réponses avec un 422 `invalid_form_submission`. null quand l'erreur n'en liste aucun.
bodymixed- La réponse d'erreur entière, décodée. null quand elle était vide ou n'était pas du JSON.
| Méthode | Vrai quand |
|---|---|
| isAuth() | type vaut authentication_error, un 401 : aucune clé, un type d'identifiant erroné, ou une clé que nous n'avons pas émise. |
| isPermission() | permission_error, un 403 : une vraie clé à laquelle manque la portée ou l'adresse From dont elle a besoin. |
| isScopeMissing() | errorCode vaut insufficient_scope, le 403 qui nomme une portée manquante. |
| isInvalidRequest() | invalid_request_error, un 400 : une requête qui n'a pas pu être comprise. Un message dépassant le plafond de taille revient en 422 message_too_large : c'est donc isValidation() qui l'attrape. |
| isValidation() | validation_error, un 422 : le schéma l'a refusée, et param nomme le champ. |
| isNotFound() | not_found_error, un 404 : la ressource n'existe pas. |
| isConflict() | conflict_error, un 409 : la ressource a dépassé le stade où cette opération pouvait encore lui être appliquée. |
| isRateLimited() | rate_limit_error, un 429. retryAfterSeconds contient le délai d'attente quand le serveur en a indiqué un. |
| isServerError() | status vaut 500 ou plus. Citez requestId si vous contactez le support. |
| isRetryable() | status vaut 408, 429, 500, 502, 503 ou 504. |
| isStepUpRequired() | errorCode vaut step_up_required, le 403 qu'un jeton d'accès OAuth reçoit avant un changement sensible tant que la personne n'a pas vérifié de code. |
La plupart de ces méthodes lisent type, la moitié fixe de l'enveloppe, et chaque sous-classe correspond à un type. errorCode reste une chaîne, car l'API garantit qu'il est ouvert et additif : traitez donc un code que vous ne reconnaissez pas comme son type. Une liste fermée ferait d'une mise à jour du package le prix à payer pour lire un nouveau mode d'échec.
Un corps qui n'est pas l'enveloppe d'erreur de l'API lève malgré tout une ApiException, avec type déduit du statut et errorCode fixé à unrecognised_response. Une réponse en succès dont le corps n'est pas du JSON en lève une également.
isRetryable() décrit le statut, pas votre appel. Un appel qui peut être répété sans risque a déjà été réessayé au moment où il lève l'exception, et un 429 comme send_quota_exceeded ou ai_quota_exceeded échoue de la même façon tant que son quota n'est pas réinitialisé : montrez-le donc à une personne plutôt que de boucler dessus.
Une exception levée par votre client HTTP devient une NetworkException une fois épuisés les réessais que permet l'appel, avec l'originale comme getPrevious(). Une LogicException et toute Error, comme une TypeError, signalent un bug dans ce client : elles sont donc relevées telles quelles et jamais réessayées. Psr18HttpClient ne garde que le message d'une exception venant du client qu'il enveloppe, car cette exception contient la requête et son en-tête Authorization.
requestId
Chaque ApiException porte l'id de requête envoyé par le serveur, issu du corps de l'erreur ou de l'en-tête x-request-id, et c'est la seule chose qui relie votre échec à une ligne du journal du serveur. Une réponse en succès renvoie le seul corps décodé : il n'y a donc pas d'id de requête à y lire.