Prólogo
Esta arquitectura fue originalmente concebida, propuesta e implementada por el autor en el Sistema de Votaciones Online del Poder Judicial de Chile (PJUD), específicamente para el Área de Bienestar, con el objetivo de garantizar integridad, trazabilidad y seguridad en procesos electorales internos.
En dicho entorno —una intranet controlada, sin exposición a internet y con servidores dedicados— se descartó la comunicación vía APIs REST por considerarla innecesariamente compleja, con mayor superficie de ataque y dependiente de mecanismos de autenticación frágiles. En su lugar, se diseñó un canal de comunicación directo entre WordPress (frontend y autenticación) y Laravel (lógica de negocio), utilizando exclusivamente comandos CLI y el protocolo SSH como transporte cifrado.
El éxito operativo, la estabilidad y la facilidad de auditoría de dicha implementación validaron el enfoque. Hoy, este patrón se propone como solución genérica para cualquier integración entre WordPress y Laravel en entornos autohospedados donde el control total del sistema, la simplicidad y la seguridad son prioritarios.
Objetivo
Permitir la comunicación segura, eficiente y extensible entre WordPress (frontend, autenticación, UX) y Laravel (lógica de negocio, modelos, procesamiento) en entornos autohospedados, sin exponer APIs HTTP, usando exclusivamente comandos CLI (wp-cli + artisan) y canales IPC (stdin/stdout o SSH).
Alcance
- Soporta cualquier operación delegable: sincronización de carritos, creación de cursos, votaciones, notificaciones, etc.
- Funciona en:
- Mismo servidor (IPC local)
- Servidores distintos en red privada (SSH)
- No requiere:
- APIs REST
- Bases de datos compartidas
- Plugins de autenticación cruzada
Arquitectura
+----------------+ CLI Call +------------------+ | | ---------------------> | | WordPress | (wp-cli → artisan) | Laravel | | (Frontend) | <--------------------- | (Business Logic) | | | JSON via stdout | +----------------+ +------------------+
Flujo de ejecución
- WordPress dispara un hook (ej.
woocommerce_cart_updated
). - Plugin de WordPress ejecuta un comando wp-cli personalizado.
- wp-cli llama a
artisan bridge:dispatch
(local o vía SSH). - Laravel ejecuta la acción registrada, procesa y devuelve JSON por stdout.
- WordPress recibe la respuesta y actúa (mostrar mensaje, log, etc.).
Componentes
1. WordPress: Plugin wp-laravel-bridge
Comando CLI
wp laravel:call --user= --data=
Función helper (PHP)
function wp_laravel_call(string $action, array $data, int $user_id = null): array;
Internamente
- Usa
proc_open()
para ejecutar artisan. - Escapa parámetros con
escapeshellarg()
. - Captura stdout y parsea JSON.
- Maneja errores (timeout, comandos fallidos).
2. Laravel: Comando bridge:dispatch
Uso
php artisan bridge:dispatch --user= --payload=
Registro de acciones (routes/bridge.php
)
return [
'cart.sync' => \\App\\Actions\\SyncCart::class,
'vote.register' => \\App\\Actions\\RegisterVote::class,
'course.enroll' => \\App\\Actions\\EnrollInCourse::class,
// ...
];
Contrato de acción
interface BridgeAction { public function handle(array $data): array; }
Salida
- Éxito:
echo json_encode($result);
- Error:
fwrite(STDERR, json_encode($error)); exit(1);
Seguridad
Medida | Implementación |
---|---|
Autenticación | El user_id lo provee WordPress (sesión validada). Laravel confía en él. |
Validación | Cada acción valida entrada con Laravel Validator o clases propias. |
Ejecución | Solo comandos CLI permitidos. Nada de eval() ni ejecución dinámica. |
Red | Si es remoto, solo vía SSH con clave pública y ForceCommand . |
Permisos | Usuario del servidor (www-data ) con acceso mínimo a rutas de Laravel/WordPress. |
Ejemplo: Sincronización de carrito (WooCommerce)
WordPress (plugin)
add_action('woocommerce_cart_updated', function() {
if (!is_user_logged_in()) return;
$items = array_column(WC()->cart->get_cart(), 'product_id');
$result = wp_laravel_call('cart.sync', [
'items' => $items,
'total' => WC()->cart->get_total('numeric')
]);
if ($result['success'] ?? false) {
WC()->session->set('cart_tracked', true);
}
});
Laravel (SyncCart.php
)
class SyncCart implements BridgeAction {
public function handle(array $data): array {
CartTracker::updateOrCreate(
['user_id' => $data['user_id']],
['items' => $data['items'], 'total' => $data['total']]
);
return ['success' => true, 'tracked_at' => now()];
}
}
Comunicación bidireccional (opcional)
Para notificaciones Laravel → WordPress:
// En Laravel shell_exec("wp --path=/var/www/wordpress user meta set {$userId} last_action " . time());
→ Requiere que Laravel pueda ejecutar wp-cli.
Requisitos del sistema
- WordPress: con wp-cli instalado y accesible.
- Laravel: >= 8.x, con comandos Artisan.
- PHP:
proc_open()
habilitado. - Servidor: acceso SSH entre nodos (si es distribuido).
- Permisos: usuario común con acceso a ambas rutas.
Escalabilidad
- Para alta carga: reemplazar
proc_open()
por cola asíncrona (Redis, DB). - Para auditoría: loggear cada llamada en Laravel (
bridge.log
). - Para idempotencia: incluir
request_id
en el payload.
Anti-patrones a evitar
- No usar
shell_exec()
sin sanitización. - No pasar credenciales en la línea de comandos
- No pasar credenciales en la línea de comandos.
- No asumir que Laravel y WordPress comparten base de datos.
- No exponer el comando
bridge:dispatch
a HTTP.
Estructura de archivos sugerida
wordpress/ ├── wp-content/ │ └── plugins/ │ └── wp-laravel-bridge/ │ ├── wp-laravel-bridge.php │ ├── includes/ │ │ └── BridgeClient.php │ └── commands/ │ └── CallLaravel.php laravel/ ├── app/ │ └── Actions/ │ ├── SyncCart.php │ ├── RegisterVote.php │ └── EnrollInCourse.php ├── routes/ │ └── bridge.php ├── app/ │ └── Console/ │ └── Commands/ │ └── BridgeDispatch.php
Licencia
Este patrón puede ser implementado libremente en cualquier sistema autohospedado. No se recomienda su uso en entornos compartidos, multisite o con exposición pública sin adaptaciones adicionales.
Autor
Benjamin Sánchez
Desarrollador y Analista de Sistemas
github.com/benjasanchez
Comentarios
Publicar un comentario