Abstracción de WordPress: mejores prácticas y complementos de abstracción de WordPress
WordPress es un CMS antiguo, pero también el más utilizado. Gracias a su historial de compatibilidad con versiones de PHP obsoletas y código heredado, todavía le falta implementar prácticas de codificación modernas; la abstracción de WordPress es un ejemplo.
Por ejemplo, será mucho mejor dividir el código base de WordPress en paquetes administrados por Composer. O quizás, para autocargar clases de WordPress desde rutas de archivo.
Este artículo le enseñará cómo abstraer el código de WordPress manualmente y utilizar las capacidades de los complementos abstractos de WordPress.
Problemas con la integración de herramientas PHP y WordPress
Debido a su arquitectura antigua, ocasionalmente encontramos problemas al integrar WordPress con herramientas para bases de código PHP, como el analizador estático PHPStan, la biblioteca de pruebas unitarias PHPUnit y la biblioteca de ámbito de nombres PHP-Scoper. Por ejemplo, considere los siguientes casos:
El código de WordPress dentro de nuestros proyectos será solo una fracción del total; el proyecto también contendrá el código comercial independiente del CMS subyacente. Sin embargo, con solo tener algo de código de WordPress, es posible que el proyecto no se integre correctamente con las herramientas.
Debido a esto, podría tener sentido dividir el proyecto en paquetes, algunos de ellos contienen código de WordPress y otros solo tienen código de negocios usando PHP “vanilla” y ningún código de WordPress. De esta manera, estos últimos paquetes no se verán afectados por los problemas descritos anteriormente, pero pueden integrarse perfectamente con las herramientas.
¿Qué es la abstracción de código?
La abstracción de código elimina las dependencias fijas del código, produciendo paquetes que interactúan entre sí a través de contratos. Estos paquetes se pueden agregar a diferentes aplicaciones con diferentes pilas, maximizando su usabilidad. El resultado de la abstracción de código es una base de código claramente desacoplada basada en los siguientes pilares:
- Codifique contra interfaces, no implementaciones.
- Cree paquetes y distribúyalos a través de Composer.
- Pegue todas las partes mediante la inyección de dependencia.
Codificación contra interfaces, no implementaciones
Codificar contra interfaces es la práctica de usar contratos para que los fragmentos de código interactúen entre sí. Un contrato es simplemente una interfaz PHP (o cualquier lenguaje diferente) que define qué funciones están disponibles y sus firmas, es decir, qué entradas reciben y su salida.
Una interfaz declara la intención de la funcionalidad sin explicar cómo se implementará la funcionalidad. Al acceder a la funcionalidad a través de interfaces, nuestra aplicación puede confiar en piezas de código autónomas que logran un objetivo específico sin saber ni preocuparse por cómo lo hacen. De esta manera, no es necesario adaptar la aplicación para cambiar a otro código que logre el mismo objetivo, por ejemplo, de un proveedor diferente.
Ejemplo de contratos
El siguiente código usa el contrato de Symfony CacheInterface y el contrato de recomendación estándar de PHP (PSR) CacheItemInterface para implementar la funcionalidad de almacenamiento en caché:
use PsrCacheCacheItemInterface;
use SymfonyContractsCacheCacheInterface;
$value = $cache->get('my_cache_key', function (CacheItemInterface $item) {
$item->expiresAfter(3600);
return 'foobar';
});
$cache implementos CacheInterface, que define el método get para recuperar un objeto de la caché. Al acceder a esta funcionalidad a través del contrato, la aplicación puede ignorar dónde está el caché. Ya sea en memoria, disco, base de datos, red o en cualquier otro lugar. Aún así, tiene que realizar la función. CacheItemInterface define el método expiresAfter para declarar cuánto tiempo debe mantenerse el elemento en el caché. La aplicación puede invocar este método sin importar el objeto en caché; solo le importa cuánto tiempo se debe almacenar en caché.
Codificación contra interfaces en WordPress
Debido a que estamos abstrayendo el código de WordPress, el resultado será que la aplicación no hará referencia al código de WordPress directamente, sino siempre a través de una interfaz. Por ejemplo, la función de WordPress get_posts tiene esta firma:
/**
* @param array $args
* @return WP_Post[]|int[] Array of post objects or post IDs.
*/
function get_posts( $args = null )
En lugar de invocar este método directamente, podemos acceder a él a través del contrato. OwnerMyAppContractsPostsAPIInterface:
namespace OwnerMyAppContracts;
interface PostAPIInterface
{
public function get_posts(array $args = null): PostInterface[]|int[];
}
Tenga en cuenta que la función de WordPress get_posts puede devolver objetos de la clase WP_Post, que es específico de WordPress. Al abstraer el código, necesitamos eliminar este tipo de dependencia fija. El método get_posts en el contrato devuelve objetos del tipo PostInterface, lo que le permite hacer referencia a la clase WP_Post sin ser explícito al respecto. La clase PostInterface tendrá que proporcionar acceso a todos los métodos y atributos de WP_Post:
namespace OwnerMyAppContracts;
interface PostInterface
{
public function get_ID(): int;
public function get_post_author(): string;
public function get_post_date(): string;
// ...
}
La ejecución de esta estrategia puede cambiar nuestra comprensión de dónde encaja WordPress en nuestra pila. En lugar de pensar en WordPress como la aplicación en sí (sobre la cual instalamos temas y complementos), podemos pensar en ella simplemente como otra dependencia dentro de la aplicación, reemplazable como cualquier otro componente. (Aunque no reemplazaremos WordPress en la práctica, es reemplazable desde un punto de vista conceptual.)
Creación y distribución de paquetes
Composer es un administrador de paquetes para PHP. Permite a las aplicaciones PHP recuperar paquetes (es decir, fragmentos de código) de un repositorio e instalarlos como dependencias. Para desacoplar la aplicación de WordPress, debemos distribuir su código en paquetes de dos tipos diferentes: los que contienen código de WordPress y los otros que contienen lógica empresarial (es decir, sin código de WordPress).
Finalmente, agregamos todos los paquetes como dependencias en la aplicación y los instalamos a través de Composer. Dado que las herramientas se aplicarán a los paquetes de códigos comerciales, estos deben contener la mayor parte del código de la aplicación; cuanto mayor sea el porcentaje, mejor. Hacer que administren alrededor del 90% del código general es un buen objetivo.
Extraer código de WordPress en paquetes
Siguiendo el ejemplo anterior, los contratos PostAPIInterface y PostInterface se agregará al paquete que contiene el código comercial, y otro paquete incluirá la implementación de WordPress de estos contratos. Satisfacer PostInterface, creamos un PostWrapper clase que recuperará todos los atributos de una WP_Post objeto:
namespace OwnerMyAppForWPContractImplementations;
use OwnerMyAppContractsPostInterface;
use WP_Post;
class PostWrapper implements PostInterface
{
private WP_Post $post;
public function __construct(WP_Post $post)
{
$this->post = $post;
}
public function get_ID(): int
{
return $this->post->ID;
}
public function get_post_author(): string
{
return $this->post->post_author;
}
public function get_post_date(): string
{
return $this->post->post_date;
}
// ...
}
Al implementar PostAPI, desde el método get_posts devoluciones PostInterface[], debemos convertir objetos de WP_Post para PostWrapper:
namespace OwnerMyAppForWPContractImplementations;
use OwnerMyAppContractsPostAPIInterface;
use WP_Post;
class PostAPI implements PostAPIInterface
{
public function get_posts(array $args = null): PostInterface[]|int[]
{
// This var will contain WP_Post[] or int[]
$wpPosts = get_posts($args);
// Convert WP_Post[] to PostWrapper[]
return array_map(
function (WP_Post|int $post) {
if ($post instanceof WP_Post) {
return new PostWrapper($post);
}
return $post
},
$wpPosts
);
}
}
Uso de la inyección de dependencia
La inyección de dependencia es un patrón de diseño que le permite pegar todas las partes de la aplicación juntas de una manera suelta. Con la inyección de dependencia, la aplicación accede a los servicios a través de sus contratos y las implementaciones del contrato se «inyectan» en la aplicación a través de la configuración.
Simplemente cambiando la configuración, podemos cambiar fácilmente de un proveedor por contrato a otro. Hay varias bibliotecas de inyección de dependencias entre las que podemos elegir. Recomendamos seleccionar uno que se adhiera a las Recomendaciones estándar de PHP (a menudo denominado “PSR”), para que podamos reemplazar fácilmente la biblioteca por otra si surge la necesidad. Con respecto a la inyección de dependencia, la biblioteca debe cumplir con PSR-11, que proporciona la especificación para una «interfaz de contenedor». Entre otras, las siguientes bibliotecas cumplen con PSR-11:
Acceder a los servicios a través del contenedor de servicios
La biblioteca de inyección de dependencias pondrá a disposición un «contenedor de servicios», que resuelve un contrato en su clase de implementación correspondiente. La aplicación debe depender del contenedor de servicios para acceder a todas las funciones. Por ejemplo, aunque normalmente invocaríamos las funciones de WordPress directamente:
$posts = get_posts();
… Con el contenedor de servicios, primero debemos obtener el servicio que satisfaga PostAPIInterface y ejecutar la funcionalidad a través de él:
use OwnerMyAppContractsPostAPIInterface;
// Obtain the service container, as specified by the library we use
$serviceContainer = ContainerBuilderFactory::getInstance();
// The obtained service will be of class OwnerMyAppForWPContractImplementationsPostAPI
$postAPI = $serviceContainer->get(PostAPIInterface::class);
// Now we can invoke the WordPress functionality
$posts = $postAPI->get_posts();
Usando DependencyInjection de Symfony
El componente DependencyInjection de Symfony es actualmente la biblioteca de inyección de dependencias más popular. Le permite configurar el contenedor de servicios a través de código PHP, YAML o XML. Por ejemplo, para definir ese contrato PostAPIInterface está satisfecho a través de la clase PostAPI está configurado en YAML así:
services:
OwnerMyAppContractsPostAPIInterface:
class: OwnerMyAppForWPContractImplementationsPostAPI
DependencyInjection de Symfony también permite que las instancias de un servicio se inyecten automáticamente (o se “conecten automáticamente”) en cualquier otro servicio que dependa de él. Además, facilita la definición de que una clase es una implementación de su propio servicio. Por ejemplo, considere la siguiente configuración de YAML:
services:
_defaults:
public: true
autowire: true
GraphQLAPIGraphQLAPIRegistriesUserAuthorizationSchemeRegistryInterface:
class: 'GraphQLAPIGraphQLAPIRegistriesUserAuthorizationSchemeRegistry'
GraphQLAPIGraphQLAPISecurityUserAuthorizationInterface:
class: 'GraphQLAPIGraphQLAPISecurityUserAuthorization'
GraphQLAPIGraphQLAPISecurityUserAuthorizationSchemes:
resource: '../src/Security/UserAuthorizationSchemes/*'
Esta configuración define lo siguiente:
- Contrato
UserAuthorizationSchemeRegistryInterfaceestá satisfecho a través de la claseUserAuthorizationSchemeRegistry - Contrato
UserAuthorizationInterfaceestá satisfecho a través de la claseUserAuthorization - Todas las clases en la carpeta
UserAuthorizationSchemes/son una implementación de sí mismos - Los servicios deben inyectarse automáticamente entre sí (
autowire: true)
Veamos cómo funciona el cableado automático. La clase UserAuthorization depende del servicio con contrato UserAuthorizationSchemeRegistryInterface:
class UserAuthorization implements UserAuthorizationInterface
{
public function __construct(
protected UserAuthorizationSchemeRegistryInterface $userAuthorizationSchemeRegistry
) {
}
// ...
}
Gracias a autowire: true, el componente DependencyInjection tendrá automáticamente el servicio UserAuthorization recibir su dependencia requerida, que es una instancia de UserAuthorizationSchemeRegistry.
Cuando Abstracto
La abstracción de código podría consumir un tiempo y un esfuerzo considerables, por lo que solo deberíamos realizarla cuando sus beneficios superen sus costos. Las siguientes son sugerencias sobre cuándo puede valer la pena abstraer el código. Puede hacer esto utilizando fragmentos de código en este artículo o los complementos abstractos de WordPress sugeridos a continuación.
Obtener acceso a herramientas
Como se mencionó anteriormente, ejecutar PHP-Scoper en WordPress es difícil. Al desacoplar el código de WordPress en paquetes distintivos, es factible establecer el alcance de un complemento de WordPress directamente.
Reducción del tiempo y el costo de las herramientas
Ejecutar un conjunto de pruebas PHPUnit lleva más tiempo cuando necesita inicializarse y ejecutar WordPress que cuando no lo hace. Menos tiempo también puede traducirse en menos dinero gastado en ejecutar las pruebas; por ejemplo, las acciones de GitHub cobran por los corredores alojados en GitHub en función del tiempo dedicado a su uso.
No se necesita una refactorización intensa
Un proyecto existente puede requerir una gran refactorización para introducir la arquitectura requerida (inyección de dependencia, división de código en paquetes, etc.), lo que dificulta su extracción. La abstracción de código al crear un proyecto desde cero lo hace mucho más manejable.
Producción de código para múltiples plataformas
Al extraer el 90% del código en un paquete independiente de CMS, podemos producir una versión de biblioteca que funcione para un CMS o marco diferente reemplazando solo el 10% de la base de código general.
Migrar a una plataforma diferente
Si necesitamos migrar un proyecto de Drupal a WordPress, de WordPress a Laravel o cualquier otra combinación, entonces solo se debe reescribir el 10% del código, un ahorro significativo.
Mejores prácticas
Al diseñar los contratos para abstraer nuestro código, hay varias mejoras que podemos aplicar al código base.
Adherirse a PSR-12
Al definir la interfaz para acceder a los métodos de WordPress, debemos adherirnos a PSR-12. Esta reciente especificación tiene como objetivo reducir la fricción cognitiva al escanear código de diferentes autores. Adherirse a PSR-12 implica cambiar el nombre de las funciones de WordPress.
Funciones de nombres de WordPress usando caso_serpiente, mientras que el PSR-12 usa el caso de Carmel. Por lo tanto, función get_posts se convertirá getPosts:
interface PostAPIInterface
{
public function getPosts(array $args = null): PostInterface[]|int[];
}
…y:
class PostAPI implements PostAPIInterface
{
public function getPosts(array $args = null): PostInterface[]|int[]
{
// This var will contain WP_Post[] or int[]
$wpPosts = get_posts($args);
// Rest of the code
// ...
}
}
Métodos divididos
Los métodos en la interfaz no necesitan ser una réplica de los de WordPress. Podemos transformarlos siempre que tenga sentido. Por ejemplo, la función de WordPress get_user_by($field, $value) sabe cómo recuperar al usuario de la base de datos mediante parámetro $field, que acepta valores "id", "ID", "slug", "email" o "login". Este diseño tiene algunos problemas:
- No fallará en el momento de la compilación si pasamos una cadena incorrecta
- Parámetro
$valuenecesita aceptar todos los tipos diferentes para todas las opciones, aunque al pasar"ID"espera unint, al pasar"email"solo puede recibir unstring
Podemos mejorar esta situación dividiendo la función en varias:
namespace OwnerMyAppContracts;
interface UserAPIInterface
{
public function getUserById(int $id): ?UserInterface;
public function getUserByEmail(string $email): ?UserInterface;
public function getUserBySlug(string $slug): ?UserInterface;
public function getUserByLogin(string $login): ?UserInterface;
}
El contrato se resuelve para WordPress así (suponiendo que hayamos creado UserWrapper y UserInterface, como se explicó anteriormente):
namespace OwnerMyAppForWPContractImplementations;
use OwnerMyAppContractsUserAPIInterface;
class UserAPI implements UserAPIInterface
{
public function getUserById(int $id): ?UserInterface
{
return $this->getUserByProp('id', $id);
}
public function getUserByEmail(string $email): ?UserInterface
{
return $this->getUserByProp('email', $email);
}
public function getUserBySlug(string $slug): ?UserInterface
{
return $this->getUserByProp('slug', $slug);
}
public function getUserByLogin(string $login): ?UserInterface
{
return $this->getUserByProp('login', $login);
}
private function getUserByProp(string $prop, int|string $value): ?UserInterface
{
if ($user = get_user_by($prop, $value)) {
return new UserWrapper($user);
}
return null;
}
}
Eliminar detalles de implementación de la firma de la función
Las funciones en WordPress pueden proporcionar información sobre cómo se implementan en su propia firma. Esta información se puede eliminar al evaluar la función desde una perspectiva abstracta. Por ejemplo, la obtención del apellido del usuario en WordPress se realiza llamando get_the_author_meta, lo que hace explícito que el apellido de un usuario se almacena como un valor «meta» (en la tabla wp_usermeta):
$userLastname = get_the_author_meta("user_lastname", $user_id);
No tiene que transmitir esta información al contrato. A las interfaces solo les importa el qué, no el cómo. Por lo tanto, el contrato puede tener un método getUserLastname, que no proporciona ninguna información sobre cómo se implementa:
interface UserAPIInterface
{
public function getUserLastname(UserWrapper $userWrapper): string;
...
}
Agregar tipos más estrictos
Algunas funciones de WordPress pueden recibir parámetros de diferentes formas, lo que genera ambigüedad. Por ejemplo, función add_query_arg puede recibir una única clave y valor:
$url = add_query_arg('id', 5, $url);
… o una serie de key => value:
$url = add_query_arg(['id' => 5], $url);
Nuestra interfaz puede definir una intención más comprensible al dividir dichas funciones en varias funciones separadas, cada una de las cuales acepta una combinación única de entradas:
public function addQueryArg(string $key, string $value, string $url);
public function addQueryArgs(array $keyValues, string $url);
Limpiar la deuda técnica
La función de WordPress get_posts devuelve no solo «publicaciones» sino también «páginas» o cualquier entidad del tipo «publicaciones personalizadas», y estas entidades no son intercambiables. Tanto las publicaciones como las páginas son publicaciones personalizadas, pero una página no es una publicación ni una página. Por lo tanto, ejecutando get_posts puede devolver páginas. Este comportamiento es una discrepancia conceptual.
Para hacerlo correcto get_posts en su lugar debería llamarse get_customposts, pero nunca se le cambió el nombre en el núcleo de WordPress. Es un problema común con la mayoría del software de larga duración y se denomina «deuda técnica»: código que tiene problemas, pero que nunca se soluciona porque introduce cambios importantes.
Sin embargo, al crear nuestros contratos, tenemos la oportunidad de evitar este tipo de deuda técnica. En este caso, podemos crear una nueva interfaz. ModelAPIInterface que puede tratar con entidades de diferentes tipos, y hacemos varios métodos, cada uno para tratar con un tipo diferente:
interface ModelAPIInterface
{
public function getPosts(array $args): array;
public function getPages(array $args): array;
public function getCustomPosts(array $args): array;
}
De esta manera, la discrepancia ya no ocurrirá y verá estos resultados:
getPostsdevuelve solo publicacionesgetPagesdevuelve solo páginasgetCustomPostsdevuelve publicaciones y páginas
Beneficios del código de resúmenes
Las principales ventajas de abstraer el código de una aplicación son:
- Las herramientas que se ejecutan en paquetes que solo contienen código comercial son más fáciles de configurar y tomarán menos tiempo (y menos dinero) para ejecutarlas.
- Podemos usar herramientas que no funcionan con WordPress, como el alcance de un complemento con PHP-Scoper.
- Los paquetes que producimos pueden ser autónomos para usarlos en otras aplicaciones fácilmente.
- La migración de una aplicación a otras plataformas se vuelve más sencilla.
- Podemos cambiar nuestra forma de pensar de WordPress para pensar en términos de nuestra lógica empresarial.
- Los contratos describen la intención de la aplicación, haciéndola más comprensible.
- La aplicación se organiza a través de paquetes, creando una aplicación ajustada que contiene lo mínimo y mejorándola progresivamente según sea necesario.
- Podemos liquidar la deuda técnica.
Problemas con el código de abstracción
Las desventajas de abstraer el código de una aplicación son:
- Inicialmente implica una cantidad considerable de trabajo.
- El código se vuelve más detallado; agregue capas adicionales de código para lograr el mismo resultado.
- Puede terminar produciendo docenas de paquetes que luego deben administrarse y mantenerse.
- Es posible que necesite un monorepo para administrar todos los paquetes juntos.
- La inyección de dependencia podría ser excesiva para aplicaciones simples (rendimientos decrecientes).
- La abstracción del código nunca se logrará por completo, ya que generalmente hay una preferencia general implícita en la arquitectura del CMS.
Opciones de complementos abstractos de WordPress
Aunque generalmente es más inteligente extraer su código a un entorno local antes de trabajar en él, algunos complementos de WordPress pueden ayudarlo a alcanzar sus objetivos de abstracción. Estas son nuestras mejores opciones.
1. WPide
Producido por WebFactory Ltd, el popular complemento WPide amplía drásticamente la funcionalidad del editor de código predeterminado de WordPress. Sirve como un complemento abstracto de WordPress al permitirle ver su código in situ para visualizar mejor lo que necesita atención.
El complemento WPide.
WPide también tiene una función de búsqueda y reemplazo para localizar rápidamente el código obsoleto o caducado y reemplazarlo con una versión refactorizada.
Además de esto, WPide ofrece muchas funciones adicionales, que incluyen:
- Resaltado de sintaxis y bloques
- Copias de seguridad automáticas
- Creación de archivos y carpetas
- Explorador de árbol de archivos completo
- Acceso a la API del sistema de archivos de WordPress
2. Ultimate DB Manager
El complemento Ultimate WP DB Manager de WPHobby le brinda una forma rápida de descargar sus bases de datos en su totalidad para extracción y refactorización.
El complemento Ultimate DB Manager.
Por supuesto, los complementos de este tipo no son necesarios para los usuarios de Kinsta, ya que Kinsta ofrece acceso directo a la base de datos a todos los clientes. Sin embargo, si no tiene suficiente acceso a la base de datos a través de su proveedor de alojamiento, Ultimate DB Manager podría ser útil como un complemento abstracto de WordPress.
3. Su propio complemento de WordPress abstracto personalizado
Al final, la mejor opción para la abstracción siempre será crear su complemento. Puede parecer una gran empresa, pero si tiene una capacidad limitada para administrar sus archivos centrales de WordPress directamente, esto ofrece una solución fácil de abstracción.
Hacerlo tiene claros beneficios:
- Abstrae sus funciones de los archivos de su tema
- Conserva su código a través de cambios de tema y actualizaciones de la base de datos
Puede aprender cómo crear su complemento abstracto de WordPress a través del Manual para desarrolladores de complementos de WordPress.
Resumen
¿Deberíamos abstraer el código en nuestras aplicaciones? Como ocurre con todo, no existe una «respuesta correcta» predefinida, ya que depende de cada proyecto. Aquellos proyectos que requieren una gran cantidad de tiempo para analizar con PHPUnit o PHPStan pueden beneficiarse al máximo, pero el esfuerzo necesario para llevarlo a cabo puede que no siempre valga la pena.
Ha aprendido todo lo que necesita saber para comenzar a abstraer el código de WordPress.
¿Planea implementar esta estrategia en su proyecto? Si es así, ¿utilizará un complemento abstracto de WordPress? ¡Infórmenos en la sección para comentarios!
Ahorre tiempo, costos y maximice el rendimiento del sitio con:
- Ayuda instantánea de expertos en alojamiento de WordPress, 24 horas al día, 7 días a la semana.
- Integración de Cloudflare Enterprise.
- Alcance de audiencia global con 28 centros de datos en todo el mundo.
- Optimización con nuestro monitoreo de rendimiento de aplicaciones integrado.
Todo eso y mucho más, en un plan sin contratos a largo plazo, migraciones asistidas y una garantía de devolución de dinero de 30 días. Consulte nuestros planes o hable con ventas para encontrar el plan adecuado para usted.
