Cinco técnicas probadas en batalla que su desarrollador de API de WordPress no está usando

Publicado: 2022-03-11

Una de las mejores maneras de elevar su estatus como desarrollador de WordPress, al menos a los ojos de sus clientes, es adquirir la habilidad de consumir API. Aquí hay un escenario común para la implementación de la API de WordPress: su cliente le pide que agregue un widget a su sitio, digamos, por ejemplo, un widget de suscripción de correo electrónico. Tome un código de su servicio de correo electrónico de terceros, tal vez sea una etiqueta de secuencia de comandos o un iframe , péguelo en la página y responda a su cliente: "¡Entendido!"

Desafortunadamente, estás tratando con un cliente algo más exigente y nota las siguientes imperfecciones:

  • Aunque el widget, como el resto del sitio, presenta una fuente sans-serif, no es la correcta. El widget usa Helvetica en lugar de la fuente personalizada que ha instalado.
  • El formulario de suscripción del widget desencadena la carga de una nueva página, lo que puede ser perjudicial si se coloca a la mitad de un artículo.
  • El widget parece tardar un momento más en cargarse después del resto de la página, lo que se siente discordante y barato.
  • Al cliente le gustaría que los suscriptores fueran etiquetados con metadatos en función de la publicación a la que se suscribieron, y el widget no ofrece nada que se parezca ni remotamente a esta funcionalidad.
  • Al cliente le resulta molesto tener que administrar dos paneles (wp-admin y el área de administración para el servicio de correo electrónico).

En este punto, una de dos cosas podría suceder razonablemente. Podría declarar estos artículos como "buenos para tener" y tranquilizar a su cliente sobre los méritos de una solución 80/20, o podría cumplir con esas solicitudes. En mi experiencia personal, descubrí que cumplir con tales solicitudes, es decir, demostrar el dominio de los servicios de terceros, es una forma confiable de convencer al cliente de que usted es una especie de asistente de WordPress. Además, a menudo es divertido hacerlo.

Durante la última década, he usado WordPress como plataforma para el consumo de API contra probablemente 50 API diferentes. Algunas de las API más comunes han sido MailChimp, Google Analytics, Google Maps, CloudFlare y Bitbucket. Pero, ¿qué pasa si necesita hacer más, qué pasa si necesita una solución personalizada?

Cómo desarrollar un cliente API de WordPress

En este artículo, voy a desarrollar contra una API genérica de "servicio de correo electrónico", haciendo todo lo posible para mantener las cosas lo más agnósticas posible. Sin embargo, creo que es razonable suponer que estamos tratando con una API JSON REST. Aquí hay algunos temas de fondo que pueden ayudarlo a disfrutar de los puntos técnicos de este artículo:

  • La familia de funciones HTTP de WordPress
  • JSON
  • DESCANSO

Si se encuentra un poco familiarizado con estos temas y está interesado en profundizar, haga una pausa ahora mismo y descargue la excelente aplicación Postman. Le permite comunicarse con las API sin escribir código.

Captura de pantalla del tablero del cartero

Cartero. ¿Quizás mi herramienta de desarrollo favorita?

Sin embargo, si no está familiarizado con ellos, siga leyendo de todos modos. Una audiencia técnica con cierto grado de experiencia en WordPress sacará el máximo provecho de este artículo, pero me encargaré de explicar el valor de cada técnica de una manera menos técnica. Un lector no técnico dejará este artículo capaz de evaluar el ROI de cada punto antes de patrocinarlo y juzgar la calidad de la implementación una vez entregada.

Nota: En caso de que necesite un curso de actualización rápida, puede consultar nuestra guía API REST de WordPress.

Sin más preámbulos, permítanme compartir con ustedes un puñado de técnicas diferentes que aprecio en casi todas las API, proyectos y equipos con los que trabajo.

Transitorios: cuándo sostenerlos, cuándo plegarlos

En mi párrafo inicial, noté que al cliente le resultaba molesto estar a caballo entre dos áreas de administración: wp-admin y el tablero para su servicio de correo electrónico. Una buena manera de resolver eso sería proporcionarles un widget de tablero en wp-admin, para mostrar un resumen de su actividad reciente de suscriptores.

Captura de pantalla del widget del panel de wp-admin

Un ejemplo del tipo de interfaz de usuario del tablero que podríamos proporcionar dentro de WordPress, para ahorrarles a nuestros clientes un viaje a su proveedor de servicios de correo electrónico de terceros.

Pero, de nuevo, esto podría requerir múltiples solicitudes HTTP a la API remota (la API proporcionada por el servicio de correo electrónico), lo que resulta en largas cargas de página. La solución a este problema de rendimiento es almacenar las llamadas a la API como transitorios. Este artículo del Codex brinda una excelente explicación que definitivamente debería leer, pero lo resumiré de la siguiente manera:

  1. Obtenga los datos de la API remota.
  2. Almacénelo usando set_transient() con un tiempo de caducidad de su elección basado en su propio juicio sobre el rendimiento, los límites de velocidad y el margen de error al mostrar datos obsoletos en esta aplicación en particular.
  3. Continúe con su lógica comercial: procesar los datos, devolver un valor, cualquiera que sea el caso.
  4. Cuando necesite los datos nuevamente, como en la carga de la siguiente página, verifíquelos en la memoria caché transitoria usando get_transient() antes de concluir que necesita obtenerlos de la API.

Considero que esto es una base útil y viable, pero puede dar un paso más si piensa por un momento en los verbos REST. De los cinco métodos más comunes (GET, POST, PATCH, PUT, DELETE), solo uno de ellos pertenece a su caché transitorio. ¿Puedes adivinar cuál? es OBTENER. En mis complementos, casi siempre tengo una clase de PHP dedicada a abstraer llamadas a la API remota en cuestión, y un argumento al instanciar esa clase es el método HTTP. Si no es una llamada GET, entonces no voy a invocar ninguna capa de almacenamiento en caché.

Además, si no es una llamada GET, entonces es lógico que esté tomando alguna acción para alterar los datos remotos de alguna manera, tal vez agregando, editando o eliminando un suscriptor de correo electrónico. Este podría ser un buen momento para invalidar el caché existente para ese recurso, a través de delete_transient() .

Para volver a nuestro ejemplo de una API de suscripción de correo electrónico de WordPress, así es como funcionaría en la práctica:

  • Un widget de tablero para mostrar suscriptores recientes llamará al punto final de API para /subscribers a través de una solicitud GET. Debido a que es una solicitud GET, se almacena en mi caché transitoria.
  • Un widget de la barra lateral para suscribirse a la lista de correo electrónico llamará al punto final de la API para /subscribers a través de una solicitud POST. Debido a que es una solicitud POST, no solo evitará mi caché transitoria, sino que me provocará que elimine la parte relevante de mi caché transitoria, de modo que el widget del tablero refleje este nuevo suscriptor.
  • Cuando nombro transitorios, a menudo los organizo nombrándolos literalmente según la URL de la API remota a la que estoy llamando. Esta es una forma práctica de identificar el transitorio correcto para eliminar. Si es un punto final que toma argumentos, los concatenaré en una cadena y también los agregaré al nombre transitorio.

Como cliente u otra parte interesada menos técnica, debe solicitar específicamente el almacenamiento en caché transitorio, o al menos una discusión al respecto, cada vez que la aplicación extrae datos de un servicio remoto. Debe familiarizarse con el excelente complemento Query Monitor para obtener una vista de cómo funcionan los transitorios. Le brindará una interfaz para explorar qué datos se almacenan como transitorios, con qué frecuencia y durante cuánto tiempo.

A veces, los transitorios no son lo suficientemente buenos

Algunos servicios de alojamiento premium de WordPress en realidad no le permiten usar transitorios en producción. Tienen un código en ejecución, tal vez en forma de un complemento MU o algún otro script, que interceptará su intento de usar la API de transitorios y almacenará esa información a través del caché de objetos. WP-Engine, en su configuración más común, es un excelente ejemplo de esto.

Captura de pantalla de la vista de phpMyAdmin descrita en el título

Una vista alarmante en la interfaz de usuario de phpMyAdmin: ¿Un sitio de producción completamente desprovisto de transitorios? Esto probablemente significa que el almacenamiento en caché de objetos está funcionando.

Si simplemente está almacenando y recuperando datos, en realidad no tiene que preocuparse por esto y es posible que ni siquiera se dé cuenta de que está sucediendo. Toda la familia de *_transient() le proporcionará el mismo resultado final, solo que filtrado para usar la caché de objetos en lugar de la caché transitoria. Sin embargo, donde puede tener problemas es al intentar eliminar los transitorios. Este es el por qué.

Si la integración de su API es lo suficientemente compleja como para merecer su propia página de configuración, es posible que desee incluir una interfaz de usuario para permitir que el usuario administrador borre todo el caché transitorio de su complemento . El uso más común de este botón sería para cuando el cliente cambia algún dato directamente en el servicio remoto, y quiere invalidar la caché que estamos guardando en WordPress. Este botón también puede ser útil si el cliente cambia las credenciales de la cuenta, las claves API o, en general, como un botón de "restablecimiento de fábrica" ​​para la depuración.

Captura de pantalla de los botones de opción

Un ejemplo de una interfaz de usuario para permitir que el cliente vacíe el caché local para sus datos de API.

Incluso si fuera lo suficientemente inteligente como para nombrar todas sus claves transitorias de modo que tenga alguna esperanza de identificar cada una de ellas para delete_transient() , el mejor de los casos probablemente todavía involucre SQL sin procesar, que siempre trato de evitar en WordPress:

 <?php // Purge all the transients associated with our plugin. function purge() { global $wpdb; $prefix = esc_sql( $this -> get_transient_prefix() ); $options = $wpdb -> options; $t = esc_sql( "_transient_timeout_$prefix%" ); $sql = $wpdb -> prepare ( " SELECT option_name FROM $options WHERE option_name LIKE '%s' ", $t ); $transients = $wpdb -> get_col( $sql ); // For each transient... foreach( $transients as $transient ) { // Strip away the WordPress prefix in order to arrive at the transient key. $key = str_replace( '_transient_timeout_', '', $transient ); // Now that we have the key, use WordPress core to the delete the transient. delete_transient( $key ); } } ?>

No es conveniente, no es eficiente. En cambio, esta situación requiere el almacenamiento en caché de objetos porque el almacenamiento en caché de objetos nos brinda una forma conveniente de agrupar los valores almacenados en caché . De esta manera, cuando necesite vaciar todos los valores almacenados en caché relacionados con su complemento, es una simple llamada de una sola línea a wp_cache_delete( $key, $group ) .

Resumiría todo esto diciendo: no puede ser un experto en el consumo de API si aún no es un experto en administrar el caché para esos datos.

Como cliente, lo más importante a tener en cuenta es el comportamiento anómalo de la memoria caché entre los entornos de ensayo y producción. En otras palabras, aunque probar un nuevo lote de trabajo en el ensayo siempre es una buena práctica, el almacenamiento en caché es algo que también debe probarse en producción con el mismo cuidado.

La API remota puede ayudar a informar su jerarquía de clases de PHP

Al diseñar las diversas clases de PHP para mi complemento, a menudo encuentro útil imitar la forma en que se definen los puntos finales de la API; por ejemplo, ¿qué parecen tener en común los siguientes puntos finales?

  • https://api.example-email-service.com/v1/subscribers.json
  • https://api.example-email-service.com/v1/lists.json
  • https://api.example-email-service.com/v1/campaigns.json

Todos devuelven colecciones , es decir, el resultado de una solicitud GET, que devuelve resultados de cero a muchos donde cada resultado es miembro de una matriz. Eso puede sonar bastante obvio, pero creo que es un aviso útil para la siguiente estructura de clases en mi código PHP:

  • class.collection.php , una clase abstracta
  • class.subscribers.php extiende la clase abstracta, Collection .
  • class.lists.php extiende la clase abstracta, Collection .
  • class.campaigns.php extiende la clase abstracta, Collection .

La clase abstracta tomaría como único argumento una serie de parámetros de consulta: cosas como paginación, columna de clasificación, orden de clasificación y filtros de búsqueda. Tendría métodos para tareas comunes como llamar a la API remota, manejar errores y quizás transformar los resultados en un menú HTML <select> o jQueryUI AutoSuggest. Las clases que instancian la clase abstracta pueden ser bastante cortas, tal vez haciendo poco más que especificar la cadena que se usará en la URL del punto final de la API *.json .

Captura de pantalla del área de juegos de la API de Mailchimp

Mailchimp publica un "patio de juegos" de API para llamadas API de sandboxing y demás. También actúa como una forma conveniente de navegar por toda la jerarquía de datos de su API, lo que nos brinda una idea útil de cómo podríamos estructurar nuestra propia jerarquía de clases.

Del mismo modo, ¿qué tienen en común los siguientes puntos finales?

  • https://api.example-email-service.com/v1/subscribers/104abyh4.json
  • https://api.example-email-service.com/v1/lists/837dy1h2.json
  • https://api.example-email-service.com/v1/campaigns/9i8udr43.json

Todos devuelven un elemento , es decir, exactamente un miembro único y específico de una colección: cosas como un suscriptor de correo electrónico en particular, una lista de correo electrónico o una campaña de correo electrónico. Por lo tanto, me gusta usar la siguiente estructura en mi código PHP:

  • class.item.php , una clase abstracta
  • class.subscriber.php extiende la clase abstracta, Item .
  • class.list.php extiende la clase abstracta, Item .
  • class.campaign.php extiende la clase abstracta, Item .

La clase abstracta tomaría como único argumento una cadena para identificar el elemento específico que se solicita. Una vez más, las clases que se instancian pueden ser bastante cortas, tal vez haciendo poco más que especificar la cadena que se usará en */duy736td.json .

Existen muchos enfoques para estructurar la herencia de clases, pero incluso si adopta un enfoque diferente al que describí anteriormente, apuesto a que existe una buena posibilidad de que la estructura de la API remota pueda ayudar a informar la estructura de su aplicación.

Como cliente, un síntoma común de una arquitectura deficiente es cuando tiene que solicitar el mismo cambio una y otra vez en una aplicación. Por ejemplo, si solicita que los informes arrojen 100 resultados por página en lugar de 10, y tiene que repetir esa solicitud para informes de suscriptores, informes de campañas, informes de cancelación de suscripción, etc., es posible que esté detectando una arquitectura de clases deficiente. En esta situación, vale la pena preguntarle a su equipo si se beneficiaría de un ciclo de refactorización: un cuerpo de trabajo donde el objetivo no es cambiar el comportamiento del producto sino mejorar el código subyacente para que sea más fácil cambiar el comportamiento. del producto en el futuro.

El caso de uso perfecto para WP_Error

Me avergüenza admitir que me tomó años más de lo que debería comenzar a usar correctamente la familia de funciones WP_Error en mi código. Solía ​​​​codificar mi camino, ya sea asumiendo que nunca habría errores por los que valiera la pena preocuparse mediante programación o manejándolos caso por caso. Trabajar con API remotas atraviesa esa mentalidad como un rayo láser, porque presenta un caso de uso extremadamente conveniente y poderoso para usar WP_Error .

Recuerde anteriormente que mencioné que a menudo tengo una clase de PHP cuyo propósito es realizar solicitudes HTTP a la API remota. Cuando retira todo el repetitivo, toda la manipulación de datos, todas las preocupaciones secundarias, esa clase realmente se reduce a llamar a wp_remote_request() para obtener un objeto de respuesta HTTP de la API. Convenientemente, wp_remote_request() devolverá un WP_Error si la llamada no se ejecuta por algún motivo, pero ¿qué sucede si la llamada logra devolver una respuesta HTTP de un tipo desfavorable?

Captura de pantalla de un formulario de suscripción

Técnicamente hablando, la llamada a la API funcionó , pero no está exactamente libre de advertencias. Estas advertencias deben capturarse e informarse de manera coherente en todo el código base.

Como ejemplo, tal vez hicimos una llamada al punto final /lists.json , pero esta cuenta en particular aún no tiene ninguna lista configurada. Esto devolvería una respuesta HTTP válida, pero con un código de estado de 400. Si bien eso no es exactamente un error fatal per se, desde la perspectiva de algún código front-end que quiere convertir esta llamada API en un menú desplegable, un 400 podría también ser un WSOD! Por lo tanto, me parece útil hacer un análisis adicional del resultado de wp_remote_request() , lo que podría devolver un WP_Error después de todo:

 <?php function call() { $response = wp_remote_request( $this -> url, $this -> args ); $code = wp_remote_retrieve_response_code( $response ); $first_digit = $code[0]; $good_responses = array( 2, 3 ); if( ! in_array( $first_digit, $good_responses ) { $body = wp_remote_retrieve_body( $response ); $out = new WP_Error( $code, $body ); } else { $out = $response; } return $out; } ?>

Este patrón puede ayudar a simplificar el código que invoca nuestra clase de llamada, porque sabemos que podemos confiar en is_wp_error() antes de continuar con nuestra salida.

Como cliente, de vez en cuando debe desempeñar el papel de un usuario malicioso, un usuario confundido y un usuario impaciente. Utilice la aplicación de formas en las que no está destinada a ser utilizada. Haz las cosas que tus desarrolladores no parecen querer que hagas. Toma nota de lo que sucede. ¿Recibe mensajes de error útiles? ¿Recibes algún mensaje de error? De lo contrario, puede valer la pena patrocinar un cuerpo de trabajo sobre un mejor manejo de errores.

El hermoso poder de depuración de ob_get_clean()

La web programable moderna, donde casi todos los sitios consumen las API de otros sitios y se consumen a través de su propia API, se ha convertido en un escenario increíblemente poderoso para el código. Pero es precisamente esta cualidad la que también puede hacerlo bastante lento.

Es común que las solicitudes HTTP remotas sean las partes que consumen más tiempo en la carga de una página determinada. Por esta razón, muchos componentes controlados por API se ejecutan a través de Ajax o cron. Por ejemplo, una sugerencia automática para buscar a través de una lista de suscriptores de correo electrónico probablemente debería hacer ping a la fuente de datos remota a pedido, con cada pulsación de tecla, en lugar de cargar los 100,000 suscriptores dentro del DOM a medida que se carga la página. Si esa no es una opción, tal vez una consulta grande podría sincronizarse en una tarea cron nocturna, de modo que los resultados se puedan extraer de un espejo local en lugar de la API remota.

El problema con este enfoque es que puede ser difícil de depurar. En lugar de simplemente activar WP_DEBUG y dejar que los mensajes de error aparezcan en la ventana de su navegador, está atascado mirando en la consola de red del navegador o siguiendo un archivo de registro mientras se ejecuta una tarea cron (¿con suerte?). Encuentro esto incómodo.

Una forma de mejorar esta situación es realizar llamadas cuidadosas y estratégicas a error_log() . Pero, de nuevo, un problema común con el registro es que con aplicaciones grandes o ocupadas, los registros de errores pueden crecer demasiado, o crecer demasiado rápido, para ser útiles para monitorear o analizar. Por lo tanto, tenemos que ser selectivos con lo que registramos, pensando tanto en eso como lo hacemos con nuestra lógica de aplicación real . Es una lástima haberse tomado el tiempo de registrar algún error de caso extremo exótico que solo parece ocurrir de manera intermitente en alguna tarea cron poco frecuente solo para darse cuenta de que la verdadera naturaleza del error lo ha evadido una vez más porque no pudo registrar algún miembro de la matriz en particular , digamos, del valor ofensivo.

Por lo tanto, mi filosofía se ha convertido en, no siempre registro, pero cuando lo hago, registro todo . En otras palabras, después de identificar una función particularmente preocupante, la registraré con una red lo más amplia posible:

 <?php function debug( $bug ) { ob_start(); var_dump( $bug ); $out = ob_get_clean(); error_log( $out ); } ?>

Esto equivale a var_dump() 'ing el valor de error completo en una sola entrada en el archivo de registro de errores.

Captura de pantalla del archivo de registro de errores

Un registro de errores que ha crecido demasiado para ser ergonómico para la depuración.

Como cliente, vale la pena verificar periódicamente el uso total de la memoria de archivos para su aplicación. Si nota que de repente se está topando con los límites de almacenamiento en su cuenta de alojamiento, es muy probable que la culpa sea de un registro de errores que se ha vuelto loco. Sus desarrolladores se beneficiarán de un ciclo de trabajo centrado en un mejor registro, ¡y sus clientes también!

No es exactamente un clickbait, pero servirá

Perdone la estructura de listas de este artículo. No podría forzar estos puntos en un tema de artículo más unificador porque estos patrones son muy genéricos: se aplican a cualquier punto final JSON REST y cualquier salida de WordPress .

Son los patrones que veo que ocurren una y otra vez, independientemente de cuál sea la API remota o para qué la estemos usando dentro de WordPress. He ido tan lejos como para recopilar todo este tipo de principios en un modelo de complemento que acelera enormemente mi trabajo. ¿Tienes puntos similares que retienes para cada proyecto? ¡Por favor compártalos para que pueda robarlos y agregarlos a mi repetitivo!

Relacionado: Cómo abordar el desarrollo moderno de WordPress (Parte 1)