문서로 건너뛰기
PHP

오류

모든 실패는 예외를 던집니다. 거부에는 하나의 클래스, 응답 없음에는 또 하나의 클래스가 있으며, 모든 API 오류에는 요청 id가 있습니다.

오류 잡기

catch_errors.php
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;}

발송에서 나는 permission_error는 대개 워크스페이스가 아니라 키 자체의 발송 범위, 즉 키에 부여되지 않은 도메인이나 주소 때문입니다. 예제가 이 키로 어떤 주소에서 보낼 수 있는지를 addresses->listAll()로 확인해 로그에 남기는 이유가 여기에 있습니다.

거부의 종류마다 별도의 하위 클래스가 있으므로, catch는 처리할 것만 클래스로 골라내고 나머지는 위로 전달되게 할 수 있습니다.

catch_by_class.php
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;}

클래스

모든 클래스는 OpenEmail\Exception에 있습니다.

클래스발생 조건
OpenEmailException패키지가 던지는 모든 예외가 구현하는 인터페이스이므로, catch (OpenEmailException $error)로 InvalidArgumentException을 포함해 전부 잡을 수 있습니다.
ApiExceptionAPI가 응답했지만 성공이 아닌 경우입니다. status, type, errorCode, param, docUrl, requestId, retryAfterSeconds, fields, body를 담습니다. 서버 장애처럼 type이 api_error이면 이 클래스 그대로 던져지고, 그 밖에는 해당 type의 하위 클래스로 던져집니다. RuntimeException을 상속합니다.
InvalidRequestException, AuthenticationException, PermissionException, NotFoundException, ConflictException, ValidationException, RateLimitExceptiontype마다 하나씩 있는 ApiException의 하위 클래스입니다: invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, validation_error, rate_limit_error.
NetworkException응답이 전혀 오지 않았습니다. DNS, TLS, 거부되거나 끊긴 연결, 또는 타임아웃 때문입니다. getPrevious()는 그 밑에 있는 예외를 담고 있으며, 타임아웃이 원인이면 isTimeout()이 true입니다. RuntimeException을 상속합니다.
WebhookSignatureExceptionOpenEmail::verifyWebhookSignature()가 전달을 거부했습니다. UnexpectedValueException을 상속합니다.
InvalidArgumentException무엇이든 보내기 전에 던져집니다: 없거나 형식이 잘못된 키, 사용할 수 없는 baseUrl:, 빈 id 등입니다. 호출 자체가 잘못되었다는 뜻이므로 PHP 자체의 InvalidArgumentException을 상속합니다.

ApiException이 담고 있는 것

getMessage()string
API가 사람이 읽도록 쓴 문장으로, 문제가 된 값이 있으면 그것을 짚어 줍니다. 안정적인 식별자가 아니므로, 분기는 `errorCode`로 하세요.
statusint or null
응답의 HTTP 상태이며, `getCode()`도 이 값을 반환합니다. 성공 응답이 클라이언트가 읽을 수 없는 형태로 돌아온 경우에만 null입니다.
typestring
`OpenEmail\Constants\ErrorTypes`의 여덟 값 중 하나로, 이 집합은 고정되어 늘어나지 않습니다. 본문에 없으면 상태 코드에서 추론합니다.
errorCodestring
`from_address_forbidden`이나 `invalid_email_address` 같은 구체적인 실패입니다. PHP가 `code`를 `getCode()`가 반환하는 숫자에 쓰기 때문에 `errorCode`라는 이름을 씁니다. 개방적이고 추가만 되므로, 알아보지 못하는 값은 그 `type`으로 취급하세요. 본문이 API의 오류 봉투가 아니었다면 `unrecognised_response`입니다.
paramstring or null
실패가 특정 필드를 가리키는 경우, 거부된 그 필드를 `to.0` 같은 점 구분 경로로 나타냅니다.
docUrlstring or null
API가 알려 주는 경우, 이 실패에 관한 페이지입니다.
requestIdstring or null
서버가 요청을 기록할 때 쓴 id로, 본문이나 `x-request-id` 헤더에서 가져옵니다.
retryAfterSecondsint, float or null
서버가 `Retry-After`로 요청한 대기 시간을 초 단위로 나타내며, 숫자로 보냈든 날짜로 보냈든 마찬가지입니다. 보내지 않았으면 null입니다.
fieldsarray or null
문제마다 하나의 배열이며, 각각 `key`와 `error`를 가집니다. 예를 들어 `forms->subscribe`가 422 `invalid_form_submission`으로 응답을 거부했다면 `['key' => 'email', 'error' => 'email']`과 같습니다. 오류가 아무것도 나열하지 않으면 null입니다.
bodymixed
디코딩된 오류 응답 전체입니다. 비어 있거나 JSON이 아니었다면 null입니다.
메서드true가 되는 조건
isAuth()type이 authentication_error인 401입니다. 키가 없거나, 자격 증명의 종류가 잘못되었거나, OpenEmail이 발급하지 않은 키입니다.
isPermission()permission_error, 즉 403입니다. 유효한 키이지만 필요한 스코프나 From 주소를 갖고 있지 않습니다.
isScopeMissing()errorCode가 insufficient_scope인 경우로, 빠진 스코프를 명시하는 403입니다.
isInvalidRequest()invalid_request_error, 즉 400입니다. 해석할 수 없는 요청입니다. 크기 상한을 넘은 메시지는 422 message_too_large로 돌아오므로, 그 경우를 잡는 메서드는 isValidation()입니다.
isValidation()validation_error, 즉 422입니다. 스키마가 요청을 거부했으며 param이 해당 필드를 가리킵니다.
isNotFound()not_found_error, 즉 404입니다. 그런 리소스가 없습니다.
isConflict()conflict_error, 즉 409입니다. 리소스가 이미 이 작업을 할 수 있는 시점을 지났습니다.
isRateLimited()rate_limit_error, 즉 429입니다. 서버가 대기 시간을 지정한 경우 retryAfterSeconds에 그 값이 담깁니다.
isServerError()status가 500 이상입니다. 지원팀에 문의할 때는 requestId를 함께 알려 주십시오.
isRetryable()status가 408, 429, 500, 502, 503, 504 중 하나입니다.
isStepUpRequired()errorCode가 step_up_required인 경우로, 사용자가 코드를 인증하기 전까지 OAuth 액세스 토큰이 민감한 변경 전에 받는 403입니다.

이들 대부분은 오류 봉투에서 고정된 쪽인 type을 읽으며, 각 하위 클래스는 하나의 type을 나타냅니다. errorCode는 API가 개방적이고 추가만 된다고 보장하는 값이므로 문자열로 남아 있으며, 알아보지 못하는 값은 그 type으로 취급하세요. 닫힌 목록으로 만들면 새로운 실패 모드를 읽는 대가로 패키지를 업그레이드해야 합니다.

API의 오류 봉투 형식이 아닌 본문도 여전히 ApiException을 던지며, type은 상태 코드에서 추론하고 errorCode는 unrecognised_response로 설정됩니다. 본문이 JSON이 아닌 성공 응답도 마찬가지로 이 예외를 던집니다.

isRetryable()은 호출이 아니라 상태를 설명합니다. 반복해도 안전한 호출은 예외를 던질 때쯤 이미 재시도된 상태이며, send_quota_exceeded나 ai_quota_exceeded 같은 429는 허용량이 초기화될 때까지 똑같이 실패하므로, 반복해서 시도하지 말고 사람에게 보여 주세요.

HTTP 클라이언트가 던진 예외는 호출이 허용하는 재시도를 모두 소진하면 NetworkException이 되며, 원래 예외는 getPrevious()로 얻을 수 있습니다. LogicException과 TypeError 같은 모든 Error는 HTTP 클라이언트의 버그를 뜻하므로 그대로 던져지며 재시도되지 않습니다. Psr18HttpClient는 감싼 클라이언트의 예외에서 메시지만 남깁니다. 그 예외가 요청과 그 Authorization 헤더를 담고 있기 때문입니다.

requestId

모든 ApiException은 서버가 보낸 요청 id를 오류 본문이나 x-request-id 헤더에서 가져와 담고 있으며, 이 값만이 여러분의 실패를 서버 로그의 한 줄과 이어 줍니다. 성공하면 디코딩된 본문만 반환되므로 거기서 읽을 요청 id는 없습니다.