Dos plugins, una librería, un sitio roto: aislar dependencias en compilación
- Publicado el
- • 4 min de lectura•--- vistas
Cada plugin de WordPress que usa Composer trae su propio directorio vendor/. En un sitio con veinte plugins, eso son veinte autocargadores registrados en un único proceso de PHP.
Para librerías distintas es solo un desperdicio. Para una librería de la que dependen dos plugins, es un error, y sus síntomas aparecen en el código de otro.
La mecánica
Los nombres de clase en PHP son globales. Cuando el autocargador del plugin A se registra primero y carga Vendor\Library\Client desde su propia copia, el autocargador del plugin B ya no se ejecuta para esa clase: la clase existe, así que B recibe en silencio la versión de A.
Si A trae la v2 y B espera la v3, B se rompe. El fallo es un error fatal sobre un método inexistente, en el plugin B, en una línea que llevaba dos años siendo correcta. Ninguno de los dos autores puede reproducirlo, porque ninguno tiene instalado el otro plugin.
Las librerías de captcha caen en esto constantemente. También los clientes HTTP, las implementaciones PSR y todo lo de las familias Symfony e Illuminate, precisamente los paquetes que un plugin arrastra sin pensarlo.
Los arreglos que no funcionan
«Basta con exigir la misma versión». No puedes. No controlas el otro plugin, su calendario de versiones, ni si sigue mantenido.
«Comprueba antes si la clase existe». class_exists() te dice que una clase está cargada, no que sea la versión que necesitas. Proteger tu propia definición solo te convierte en el plugin que usa en silencio el código equivocado.
«Carguemos nuestro vendor primero». Ahora ganas tú la carrera y se rompe el otro plugin. Eso no es un arreglo, es elegir quién sufre.
Renombrar la dependencia en compilación
La única respuesta duradera es dejar de compartir el nombre. PHP-Scoper reescribe durante la compilación los espacios de nombres de todo lo que hay en vendor/, de modo que tu copia pasa a ser TuPrefijo\Vendor\Library\Client. Dos plugins pueden entonces traer dos versiones de la misma librería sin que ninguno vea la del otro.
La configuración trata sobre todo de lo que no hay que renombrar:
$wp_classes = json_decode(file_get_contents(__DIR__ .
'/vendor/sniccowp/php-scoper-wordpress-excludes/generated/exclude-wordpress-classes.json'), true);
$wp_functions = json_decode(file_get_contents(__DIR__ .
'/vendor/sniccowp/php-scoper-wordpress-excludes/generated/exclude-wordpress-functions.json'), true);
$wp_constants = json_decode(file_get_contents(__DIR__ .
'/vendor/sniccowp/php-scoper-wordpress-excludes/generated/exclude-wordpress-constants.json'), true);
Los símbolos propios de WordPress deben quedar intactos: poner prefijo a add_action produce un plugin que carga y no hace nada. El paquete sniccowp/php-scoper-wordpress-excludes mantiene esas listas por ti.
Las interfaces y contratos normalmente también deben excluirse:
$excluded = [
'/^Psr\\\\/',
'/^Symfony\\\\Contracts\\\\/',
'/^Illuminate\\\\Contracts\\\\/',
];
El razonamiento: una interfaz PSR es un vocabulario compartido. Si pones prefijo a Psr\Log\LoggerInterface, tu logger deja de satisfacer los type hints de los demás, y la interoperabilidad, el propósito entero del estándar, desaparece. Prefija implementaciones; deja los contratos en paz.
El coste, con honestidad
Aislar no es gratis.
La compilación gana un paso. El desarrollo va sin aislar y producción va aislada, y no son idénticas byte a byte. Un fallo que solo se reproduce en la compilación aislada es una tarde genuinamente desagradable.
Los nombres de clase dinámicos se rompen. new $className y call_user_func('Vendor\Thing::method') son cadenas, así que el aislador no las ve y hay que parchearlas o excluirlas. Las trazas también se vuelven más feas, porque el prefijo aparece en cada marco.
Ponlo en la balanza frente a la alternativa: un fallo que no puedes reproducir, reportado por un usuario que culpará a tu plugin de haber roto otro.
La regla general
Si tu código va a ejecutarse en un proceso que no controlas, no des por hecho que un nombre es solo tuyo. Los plugins de WordPress son el caso evidente, pero el mismo razonamiento cubre las extensiones de navegador que comparten una página, las librerías cargadas en una aplicación anfitriona y cualquier cosa inyectada en un entorno donde alguien más también inyecta.
Los nombres globales son un recurso compartido. Empaquetar una dependencia significa reclamar parte de ese espacio, y lo educado coincide aquí con lo duradero: reclamar una parte que nadie más vaya a querer.
Relacionado
Proyectos

CactusPlay - Tienda de juegos con una red de colaboradores verificados
Antes: los compradores de la CEI comparaban a mano los precios de PlayStation y Xbox entre vendedores dispersos, y cada pedido lo cumplían los propios fundadores. Después: una tienda con catálogo en vivo y un CRM al que podía sumarse cualquier colaborador verificado con datos de pago turcos para tomar pedidos y ganar con ellos. Resultado: más de 20 000 posiciones por hora, cambios de precio que llegan a la tienda y a Yandex.Market sin intervención humana, y un cumplimiento que ya no depende de un solo par de manos.
Artículos
Los contadores de analítica no pertenecen a los archivos del tema
Un snippet de seguimiento pegado en una plantilla desaparece con la siguiente actualización del tema, compite con la página por el navegador y duplica las cifras cuando alguien lo pega dos veces. Los tres problemas tienen el mismo arreglo.
Que la entrega sea consecuencia del pago, no una tarea que alguien ejecuta
La entrega manual de productos digitales está limitada por el horario laboral y protegida solo por la atención de alguien. Mover la entrega a los eventos del pedido cambia lo que el sistema garantiza, y la API es lo que impide que un segundo canal se convierta en una segunda implementación.
Poner precio a un catálogo contra una tienda que no controlas
Revender significa que tus precios dependen de que otro cambie los suyos. Consultar al cargar la página tumba tu tienda con la suya; una exportación nocturna se queda obsoleta. El camino intermedio es una caché con una estrategia de precio detrás.
Disponible para colaboración por contrato
Estoy disponible para colaborar por contrato. Si tiene una idea de proyecto interesante, reserve una llamada por Calendly.
Agenda una llamada de 30 min