Mejorar la gestión de errores de la API
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeDirígete a src/Store/LemonSqueezyApi.php
. Probablemente recuerdes este métodocreateCheckoutUrl()
de antes. Este lanzamiento a string
solucionaba un error
// ... lines 1 - 11 | |
final readonly class LemonSqueezyApi | |
{ | |
// ... lines 14 - 23 | |
public function createCheckoutUrl(User $user): string | |
{ | |
// ... lines 26 - 35 | |
$attributes['checkout_data']['custom']['user_id'] = $user->getId(); | |
// ... lines 37 - 86 | |
} | |
// ... lines 88 - 125 | |
} |
Elimínalo temporalmente para que podamos recuperar ese error. De vuelta en tu navegador, haz clic en "Añadir a la cesta", luego en "Pagar con LemonSqueezy", y... veremos nuestro esperadoClientException
.
Anteriormente, utilizamos este truco de dd($response->getContent(false))
para ver los detalles detrás de ClientException
. Descomenta esta línea, actualiza la página y... ahora podemos ver el error real.
Hacer que los mensajes de error sean más informativos
Esto está bien, pero seguro que podríamos mejorarlo aún más. En lugar de utilizar dd()
para depurar, probemos a envolver el método client->request()
en otro método. En la parte inferior de esta clase, crea un private function
llamado request()
que devuelva un array
.
// ... lines 1 - 126 | |
private function request(string $method, string $url, array $options = []): array | |
{ | |
} | |
// ... lines 130 - 131 |
Su primer argumento será string $method
, seguido de string $url
y una matriz de opciones. Ahora viene la parte divertida: Abre un bloque try-catch
y, en el try
, escribe $response = $this->client->request()
y pasa todas las variables - $method
, $url
, y $options
. Crea una variable $data
que sea igual a $response->toArray()
. Nosotros catch
ClientException $e
.
// ... lines 1 - 127 | |
private function request(string $method, string $url, array $options = []): array | |
{ | |
try { | |
$response = $this->client->request($method, $url, $options); | |
$data = $response->toArray(); | |
} catch (ClientException $e) { | |
} | |
// ... lines 136 - 137 | |
} | |
// ... lines 139 - 140 |
En la parte inferior, return $data
, y de vuelta en el catch
, queremos el contenido en bruto de la respuesta, así que escribe $data = $e->getResponse()->toArray()
y pasa false
como primer argumento. También añadiremos aquí dd($data)
temporalmente para que podamos ver la respuesta de error de la API.
// ... lines 1 - 127 | |
private function request(string $method, string $url, array $options = []): array | |
{ | |
try { | |
// ... lines 131 - 132 | |
} catch (ClientException $e) { | |
$data = $e->getResponse()->toArray(false); | |
dd($data); | |
} | |
return $data; | |
} | |
// ... lines 140 - 141 |
A continuación, actualiza el método createCheckoutUrl()
. En lugar de$this->client->request()
, utiliza sólo $this->request()
, pasando todos los mismos argumentos. Si nos dirigimos e intentamos comprobarlo de nuevo... ¡boom! Esto es un volcado correcto de la petición real a la API en forma de matriz.
// ... lines 1 - 24 | |
public function createCheckoutUrl(User $user): string | |
{ | |
// ... lines 27 - 61 | |
$response = $this->request(Request::METHOD_POST, 'checkouts', [ | |
// ... lines 63 - 82 | |
]); | |
// ... lines 84 - 87 | |
} | |
// ... lines 89 - 141 |
Crear mensajes de error útiles
Bien, en lugar de "volcar y morir", vamos a elaborar algunos mensajes de error que sean más útiles. En nuestro código, busca el método request()
. Comenta esta sentencia dd()
y debajo, añade $mainErrorMessage = 'LS API Error:'
. Ahora, comprobemos si tenemos un error con $error = $data['errors'][0] ??
null. Si lo hay,error
, haz otra comprobación con if (isset($error['status'])
. Dentro, escribe $mainErrorMessage .=
' ' . $error['status']. Haz lo mismo con title
,detail
, y source.pointer
. Iré más rápido en esta parte. Por último, else
, y dentro, añade el contenido en bruto con$mainErrorMessage .= $e->getResponse()->getContent(false)
. Perfecto
Al final, throw new \Exception()
con $mainErrorMessage
, 0
como segundo argumento, y $e
como tercer argumento.
// ... lines 1 - 127 | |
private function request(string $method, string $url, array $options = []): array | |
{ | |
try { | |
// ... lines 131 - 132 | |
} catch (ClientException $e) { | |
// ... lines 134 - 136 | |
$mainErrorMessage = 'LS API Error:'; | |
$error = $data['errors'][0] ?? null; | |
if ($error) { | |
if (isset($error['status'])) { | |
$mainErrorMessage .= ' ' . $error['status']; | |
} | |
if (isset($error['title'])) { | |
$mainErrorMessage .= ' ' . $error['title']; | |
} | |
if (isset($error['detail'])) { | |
$mainErrorMessage .= ' "' . $error['detail'] . '"'; | |
} | |
if (isset($error['source']['pointer'])) { | |
$mainErrorMessage .= sprintf(' (at path "%s")', $error['source']['pointer']); | |
} | |
} else { | |
$mainErrorMessage .= $e->getResponse()->getContent(false); | |
} | |
throw new \Exception($mainErrorMessage, 0, $e); | |
} | |
// ... lines 159 - 160 | |
} | |
// ... lines 162 - 163 |
Esto establece la excepción original como la anterior, lo que ayuda aún más a la depuración ¡Eso es! Se trata de un patrón bastante común y útil para simplificar excepciones complejas, pero sin dejar de proporcionar una referencia a la original.
¡Vamos a probarlo!
En la página de pago, actualiza y... ¡voilá! El mensaje de error genérico es ahora un mensaje personalizado:
Error de API LS: 422 Entidad no procesable "El campo {0} debe ser una cadena" (en la ruta "data/attributes/checkout_data/custom/user_id").
Eso era mucho más fácil de entender.
Todo lo que tenemos que hacer ahora es devolver la tipificación string
en la línea user_id
. Ya no necesitamos esta línea $response->toArray()
, así que podemos eliminarla junto con la dd()
. Sustituye también la variable $response
por $lsCheckout
, puesto que ya tenemos aquí una matriz de datos de objetos de caja.
// ... lines 1 - 24 | |
public function createCheckoutUrl(User $user): string | |
{ | |
// ... lines 27 - 36 | |
$attributes['checkout_data']['custom']['user_id'] = (string) $user->getId(); | |
// ... lines 38 - 61 | |
$lsCheckout = $this->request(Request::METHOD_POST, 'checkouts', [ | |
// ... lines 63 - 82 | |
]); | |
// ... lines 84 - 85 | |
} | |
// ... lines 87 - 161 |
Vuelve a actualizar la página para ver si funciona, y... ¡ya está!
El último paso es sustituir todas las llamadas a $this->client->request()
restantes por$this->request()
. Haré esto rápidamente para retrieveStoreUrl()
,listOrders()
, y eliminaré las llamadas a $response->toArray()
mientras estoy en ello.
// ... lines 1 - 12 | |
final readonly class LemonSqueezyApi | |
{ | |
// ... lines 15 - 87 | |
public function retrieveStoreUrl(): string | |
{ | |
$lsStore = $this->request(Request::METHOD_GET, 'stores/' . $this->storeId); | |
return $lsStore['data']['attributes']['url']; | |
} | |
// ... line 94 | |
public function listOrders(User $user): array | |
{ | |
// ... lines 97 - 102 | |
return $this->request(Request::METHOD_GET, 'orders', [ | |
// ... lines 104 - 112 | |
]); | |
} | |
public function retrieveCustomer(string $customerId): array | |
{ | |
return $this->request(Request::METHOD_GET, 'customers/' . $customerId); | |
} | |
// ... lines 120 - 154 | |
} |
Si probamos nuestro sitio una vez más... ¡la página de cuenta sigue funcionando... y también la página de pago! Nuestro proceso de tratamiento de errores es ahora eficaz e informativo.
Siguiente paso: Mejoremos la experiencia de pago de nuestros clientes incrustando la página de pago de LemonSqueezy en nuestra aplicación.