Comprender estas diferencias resulta fundamental para desarrollar extensiones mantenibles, evitar conflictos y trabajar correctamente con el core de cada plataforma.
Arquitectura del core de WordPress
WordPress mantiene una arquitectura procedural en buena parte de su núcleo, aunque progresivamente ha incorporado clases, objetos y componentes más estructurados. Durante una petición, el sistema carga archivos principales como wp-load.php, wp-config.php, wp-settings.php y los componentes situados en wp-includes.
El proceso de arranque carga la configuración, inicializa plugins, registra temas, crea los objetos globales principales, analiza la URL solicitada, ejecuta la consulta principal y finalmente selecciona una plantilla.
Los hooks permiten intervenir en prácticamente todas estas fases sin editar el core. Son, por tanto, la base de la Plugin API de WordPress y el principal mecanismo de desacoplamiento entre el núcleo, los plugins y los temas.
WordPress diferencia dos tipos de hooks:
Actions, para ejecutar código en un momento concreto.
Filters, para recibir, modificar y devolver un valor.
Aunque ambos se registran internamente sobre la misma infraestructura, su intención semántica es distinta.
1. Overrides
En WordPress no existe un sistema de overrides equivalente al de PrestaShop para sustituir clases completas del core. Se pueden sobrescribir plantillas, por ejemplo, copiando templates de WooCommerce al tema hijo, pero modificar el core o reemplazar comportamiento mediante copias completas suele generar problemas de mantenimiento.
En PrestaShop sí existe históricamente el directorio /override, pero abusar de él es una mala práctica porque:
- Dos módulos pueden intentar sobrescribir la misma clase.
- Las actualizaciones del core pueden dejar el override desfasado.
- Resulta difícil saber qué código está ejecutándose realmente.
- Puede funcionar durante meses y romperse tras actualizar PrestaShop o instalar otro módulo.
Siempre que exista un hook adecuado, es preferible usar un módulo conectado al hook antes que un override. Cuando el hook no existe, primero conviene valorar servicios, decoradores, eventos Symfony o la creación de un punto de extensión propio.
2. Los hooks son síncronos
Los hooks de WordPress y los hooks clásicos de PrestaShop se ejecutan, por defecto, de forma síncrona y secuencial dentro de la misma petición.
do_action('mi_hook');WordPress no continúa hasta que terminan todos los callbacks registrados en ese hook. La prioridad decide el orden, pero no crea procesos paralelos.
Esto tiene consecuencias reales: si un callback tarda tres segundos porque llama a una API externa, la petición completa puede tardar al menos esos tres segundos. Para tareas pesadas hay que desacoplar el proceso mediante cron, colas, Action Scheduler, Symfony Messenger, trabajos programados o procesos externos.
3. Ejemplo real en producción
Cuando un hook deja de ser teoría y empieza a provocar problemas reales
Durante años he encontrado desarrollos que aparentemente funcionaban, pero cuya arquitectura hacía muy difícil mantenerlos. Uno de los casos más habituales es descubrir que una funcionalidad depende de una modificación directa del core, de un archivo sobrescrito o de un override creado mucho tiempo atrás.
Mientras nadie actualiza WordPress, WooCommerce, PrestaShop o alguno de sus módulos, el problema puede permanecer oculto. La dificultad aparece al actualizar: una función deja de ejecutarse, un botón desaparece, el proceso de compra falla o dos módulos comienzan a comportarse de forma incompatible.
En esos casos, mi primera pregunta no es únicamente “¿qué línea está fallando?”, sino “¿en qué punto del ciclo de ejecución debería intervenir este código?”.
Por ejemplo, en WordPress es frecuente encontrar código introducido directamente en una plantilla de WooCommerce para modificar el botón de compra. Puede funcionar, pero quizá WooCommerce ya expone una action o un filter específico para realizar esa modificación sin copiar toda la plantilla.
add_action(
'woocommerce_single_product_summary',
'i8_mostrar_informacion_adicional',
25
);
function i8_mostrar_informacion_adicional(): void
{
echo '<p class="i8-product-info">Entrega estimada entre 24 y 48 horas.</p>';
}
La prioridad 25 no es un número decorativo. Indica en qué momento se ejecutará el callback respecto a otros elementos de la ficha de producto. Si otro plugin utiliza el mismo hook con prioridad 20, su contenido aparecerá antes. Si utiliza prioridad 30, aparecerá después.
Este orden de ejecución es una de las primeras cosas que reviso cuando un desarrollo “funciona, pero no donde debería”.
También hay que recordar que los hooks son síncronos. Si dentro del callback realizamos una llamada lenta a un ERP, un CRM o una API de pagos, el usuario queda esperando mientras finaliza la operación.
add_action('woocommerce_order_status_completed', 'i8_sincronizar_pedido');
function i8_sincronizar_pedido(int $order_id): void
{
// Una llamada remota lenta bloquearía esta misma petición.
}
Para una operación rápida puede ser aceptable. Para una sincronización compleja, lo correcto sería guardar la tarea y procesarla después mediante Action Scheduler, una cola o un proceso programado.
En PrestaShop he encontrado el mismo problema con los overrides. Un override puede parecer la solución más rápida cuando el core no hace exactamente lo que necesitamos, pero también puede convertirse en una capa invisible difícil de depurar.
Dos módulos pueden sobrescribir el mismo método, una actualización puede modificar la firma original de la clase y el override puede continuar cargándose con una versión antigua del comportamiento. El resultado es un error que parece estar en el core, aunque realmente procede de una sobrescritura personalizada.
Por eso, en i8 la prioridad es utilizar hooks, módulos y servicios desacoplados:
public function hookActionValidateOrder(array $params): void
{
$order = $params['order'];
$this->orderSynchronizer->schedule((int) $order->id);
}
El método conectado al hook debe ser pequeño. Recibe el evento, valida el contexto y delega el trabajo a un servicio. Así podemos probar la lógica, sustituir la integración y localizar errores sin depender completamente del ciclo interno del CMS.
Los hooks no evitan por sí solos una mala arquitectura. También se puede escribir código acoplado dentro de un hook. La diferencia está en utilizarlos como puntos de entrada controlados y no como contenedores donde introducir toda la lógica de negocio.
Esta es, probablemente, una de las diferencias entre conocer add_action() y comprender realmente la arquitectura de WordPress o PrestaShop.
Actions en WordPress
Una acción se registra mediante add_action():
add_action('init', 'registrar_tipo_contenido', 10);
function registrar_tipo_contenido(): void
{
register_post_type('proyecto', [
'public' => true,
'label' => 'Proyectos',
]);
}
El hook init se ejecuta cuando WordPress ha terminado buena parte de su inicialización, pero todavía no ha generado las cabeceras de la respuesta.
La firma completa de add_action() es:
add_action(
string $hook_name,
callable $callback,
int $priority = 10,
int $accepted_args = 1
);
La prioridad determina el orden de ejecución. Los valores menores se ejecutan antes:
add_action('init', 'primera_funcion', 5);
add_action('init', 'segunda_funcion', 20);
Las acciones no deberían utilizarse para transformar valores y devolverlos al código que disparó el hook. Su finalidad es producir un efecto: registrar contenido, guardar datos, enviar una notificación, cargar recursos o modificar el estado de la aplicación.
También podemos crear acciones propias:
do_action('mi_plugin_pedido_procesado', $order_id, $customer_id);
Otros componentes pueden suscribirse a ellas:
add_action(
'mi_plugin_pedido_procesado',
'registrar_evento_pedido',
10,
2
);
function registrar_evento_pedido(
int $order_id,
int $customer_id
): void {
// Lógica adicional.
}
Crear hooks propios convierte un plugin cerrado en una arquitectura extensible.
Filters en WordPress
Los filtros se utilizan para transformar datos durante su recorrido por WordPress:
add_filter('the_title', 'personalizar_titulo', 10, 2);
function personalizar_titulo(string $title, int $post_id): string
{
if (get_post_type($post_id) === 'proyecto') {
return 'Proyecto: ' . $title;
}
return $title;
}
La diferencia crítica es que un filtro debe devolver siempre el valor recibido, modificado o no. Si una función conectada a un filtro no devuelve nada, el valor puede convertirse en null y provocar errores difíciles de localizar.
Un filtro personalizado se expone mediante apply_filters():
$price = apply_filters(
'mi_plugin_precio_final',
$price,
$product_id,
$customer_id
);
La primera variable es el valor que recorrerá la cadena de callbacks. Cada callback recibe el resultado del anterior, lo modifica y lo devuelve.
La clase interna WP_Hook
Desde WordPress 4.7, la gestión interna de hooks se centraliza principalmente en la clase WP_Hook, situada en:
wp-includes/class-wp-hook.php
Cuando utilizamos add_action() o add_filter(), WordPress almacena los callbacks en la variable global:
$GLOBALS['wp_filter']
Cada nombre de hook suele estar asociado a una instancia de WP_Hook. Esta clase organiza los callbacks por prioridad, mantiene el estado de las iteraciones activas y permite añadir o eliminar callbacks incluso durante la ejecución del propio hook.
Esto explica por qué actions y filters comparten casi toda su infraestructura interna. De hecho, add_action() delega en add_filter(). La diferencia principal no está en su almacenamiento, sino en cómo se utilizan y en si existe un valor que debe devolverse.
Para eliminar una función registrada se necesita el mismo nombre del hook, callback y prioridad:
remove_action('init', 'registrar_tipo_contenido', 10);
Con métodos de clase o closures anónimos, la eliminación puede complicarse porque debe proporcionarse exactamente la misma referencia al callback.
El papel de functions.php en los hooks de WordPress
En muchos proyectos WordPress, el primer contacto real con los hooks ocurre dentro de functions.php. Este archivo forma parte del tema activo y WordPress lo carga automáticamente durante el proceso de inicialización.
Desde ahí es habitual registrar acciones y filtros:
add_action('wp_enqueue_scripts', 'i8_cargar_recursos');
function i8_cargar_recursos(): void
{
wp_enqueue_style(
'i8-theme',
get_stylesheet_uri(),
[],
'1.0.0'
);
}
También puede utilizarse para modificar comportamientos existentes:
add_filter('excerpt_length', 'i8_longitud_extracto');
function i8_longitud_extracto(int $length): int
{
return 30;
}
En ambos casos, functions.php no ejecuta directamente toda la lógica en el momento de cargarse. Lo que hace es registrar callbacks en el sistema de hooks para que WordPress los ejecute más adelante, cuando alcance el punto concreto del ciclo de vida correspondiente.
Esta diferencia es importante. No es lo mismo ejecutar código inmediatamente:
actualizar_datos_externos();
que conectarlo a un hook:
add_action('init', 'actualizar_datos_externos');
En el segundo caso, la función se ejecutará cuando WordPress dispare init, no cuando PHP lea la línea de functions.php.
functions.php no es un plugin
Aunque técnicamente permite añadir funcionalidades, functions.php está ligado al tema. Si se cambia de tema, su código deja de cargarse.
Por eso conviene diferenciar entre lógica de presentación y lógica de negocio.
Tiene sentido colocar en functions.php:
Registro de menús.
Soporte para imágenes destacadas.
Carga de CSS y JavaScript.
Modificaciones visuales del tema.
Hooks relacionados con plantillas.
Personalizaciones pequeñas de WooCommerce ligadas al diseño.
Sin embargo, no es recomendable colocar ahí funcionalidades críticas como:
Integraciones con CRM o ERP.
Sincronizaciones de pedidos.
Nuevos tipos de contenido esenciales para el negocio.
Procesos de facturación.
Conexiones con APIs externas.
Reglas comerciales que deben seguir activas al cambiar de tema.
Ese tipo de funcionalidad debería vivir en un plugin propio.
El error de convertir functions.php en un contenedor infinito
En proyectos antiguos es frecuente encontrar archivos functions.php con miles de líneas. Se añaden snippets, filtros, consultas, llamadas AJAX, shortcodes y lógica de WooCommerce hasta que el archivo se convierte en una aplicación completa.
Esto dificulta saber:
Qué hook ejecuta cada función.
En qué orden se cargan los callbacks.
Qué código pertenece al tema.
Qué funcionalidad es crítica.
Qué se puede eliminar sin romper la web.
En i8, cuando reviso un proyecto de este tipo, una de las primeras tareas es identificar los hooks registrados y separar responsabilidades.
Un enfoque más mantenible consiste en utilizar functions.php únicamente como punto de arranque:
require_once get_stylesheet_directory() . '/inc/assets.php';
require_once get_stylesheet_directory() . '/inc/woocommerce.php';
require_once get_stylesheet_directory() . '/inc/setup.php';
Cada archivo puede registrar sus propios hooks:
add_action('after_setup_theme', 'i8_configurar_tema');
function i8_configurar_tema(): void
{
add_theme_support('post-thumbnails');
register_nav_menus([
'primary' => 'Menú principal',
]);
}
De este modo, functions.php mantiene una función arquitectónica clara: inicializar los componentes del tema.
Hooks demasiado tempranos en functions.php
Otro problema habitual es ejecutar funciones antes de que WordPress haya cargado el contexto necesario.
Por ejemplo, este código puede ejecutarse demasiado pronto:
$user = wp_get_current_user();
Dependiendo del punto de carga, ciertos objetos globales, consultas o datos de usuario todavía pueden no estar completamente disponibles.
La solución es esperar al hook adecuado:
add_action('init', 'i8_inicializar_funcionalidad');
function i8_inicializar_funcionalidad(): void
{
$user = wp_get_current_user();
}
Elegir el hook correcto es tan importante como escribir correctamente la función.
Algunos hooks habituales en functions.php son:
add_action('after_setup_theme', 'i8_setup');
add_action('init', 'i8_register_content');
add_action('wp_enqueue_scripts', 'i8_enqueue_assets');
add_action('admin_enqueue_scripts', 'i8_enqueue_admin_assets');
add_filter('body_class', 'i8_body_classes');
add_filter('the_content', 'i8_modify_content');
Cada uno representa una fase distinta de ejecución.
Tema padre y tema hijo
Cuando se trabaja con un tema hijo, su functions.php no sustituye al del tema padre. WordPress carga ambos archivos.
Esto puede provocar duplicidades o conflictos si el tema hijo vuelve a registrar funciones o hooks que ya existen en el tema padre.
Además, para eliminar un hook del tema padre es necesario hacerlo después de que ese hook haya sido registrado:
add_action('after_setup_theme', 'i8_modificar_hooks_padre', 20);
function i8_modificar_hooks_padre(): void
{
remove_action(
'wp_footer',
'funcion_del_tema_padre',
10
);
}
La prioridad y el momento de ejecución son fundamentales. Si intentamos eliminar el callback antes de que el tema padre lo registre, remove_action() no tendrá efecto.
Closures y dificultad para eliminar hooks
En functions.php también es frecuente encontrar hooks registrados mediante funciones anónimas:
add_action('init', function (): void {
// Código.
});
Es válido, pero tiene una limitación: resulta más difícil eliminar ese callback desde otro plugin o desde un tema hijo, porque no existe una referencia sencilla a la función.
En código extensible suele ser preferible utilizar funciones nombradas o métodos de clase:
add_action('init', 'i8_registrar_elementos');
function i8_registrar_elementos(): void
{
// Código.
}
functions.php como punto de entrada, no como arquitectura completa
La función más valiosa de functions.php no es almacenar toda la personalización de WordPress. Su verdadero papel es conectar el tema con el ciclo de ejecución mediante hooks.
Debe actuar como una capa de inicialización ligera:
add_action('after_setup_theme', 'i8_bootstrap_theme');
function i8_bootstrap_theme(): void
{
Theme\Setup::register();
Theme\Assets::register();
Theme\WooCommerce::register();
}
A partir de ahí, cada clase o componente puede registrar sus propios actions y filters.
Esta separación mejora la legibilidad, facilita las pruebas y permite saber rápidamente qué parte del sistema responde a cada hook.
En resumen, functions.php es relevante porque suele ser el primer punto desde el que un tema se conecta con el core de WordPress. Sin embargo, utilizarlo correctamente implica entender que no debería convertirse en un almacén de snippets, sino en un punto de entrada ordenado hacia una arquitectura basada en hooks, clases y responsabilidades bien separadas.
Hooks en PrestaShop
PrestaShop utiliza hooks para conectar módulos con eventos del sistema y con posiciones visuales del front office o del back office.
A diferencia de WordPress, donde la distinción principal es action frente a filter, en PrestaShop encontramos principalmente:
Hooks de tipo
display.Hooks de tipo
action.Hooks con prefijo
filter.Hooks dinámicos asociados a objetos, formularios o controladores.
Los hooks display suelen insertar contenido en una posición concreta:
public function hookDisplayHeader(array $params): void
{
$this->context->controller->registerStylesheet(
'module-mi-modulo',
'modules/' . $this->name . '/views/css/front.css'
);
}
Un módulo debe registrar el hook durante su instalación:
public function install(): bool
{
return parent::install()
&& $this->registerHook('displayHeader')
&& $this->registerHook('actionValidateOrder');
}
Los hooks de PrestaShop permiten insertar contenido en cabeceras, pies, fichas de producto, carrito o zonas administrativas, pero también reaccionar ante eventos como la creación de un pedido, la actualización de stock o la modificación de un producto.
Hooks action, display y filter en PrestaShop
Los hooks cuyo nombre comienza por action suelen representar eventos:
public function hookActionValidateOrder(array $params): void
{
$order = $params['order'];
// Ejecutar una integración, registrar un evento,
// sincronizar datos o lanzar una notificación.
}
Los hooks display devuelven habitualmente HTML:
public function hookDisplayProductAdditionalInfo(array $params): string
{
$this->context->smarty->assign([
'product' => $params['product'],
]);
return $this->display(
__FILE__,
'views/templates/hook/product-info.tpl'
);
}
Los hooks filter, por su parte, permiten alterar datos. En estos casos es imprescindible revisar la documentación del hook concreto, porque los parámetros pueden incluir referencias modificables:
public function hookFilterProductContent(array $params): array
{
$params['object']['custom_label'] = 'Producto destacado';
return $params;
}
PrestaShop ejecuta los hooks clásicos principalmente mediante:
Hook::exec('actionValidateOrder', $params);
La clase Hook localiza los módulos registrados, comprueba restricciones de tienda, dispositivo, grupos o excepciones y ejecuta el método correspondiente de cada módulo.
En las zonas modernas del back office, PrestaShop combina este sistema histórico con Symfony, servicios, controladores, comandos, consultas y un despachador de hooks. Esto produce una arquitectura híbrida en la que conviven componentes legacy y componentes modernos.
Relación entre los hooks de WordPress y PrestaShop
Los dos sistemas persiguen el mismo objetivo: aplicar el principio abierto/cerrado. El core permanece cerrado a modificaciones directas, pero abierto a extensiones mediante puntos de conexión controlados.
En WordPress:
do_action('evento', $data);
$value = apply_filters('valor', $value);
En PrestaShop:
Hook::exec('actionEvento', $params);
WordPress ofrece una separación técnica y semántica muy clara entre acciones y filtros. PrestaShop incorpora la intención del hook principalmente en su nombre y en el contrato de parámetros establecido por el core.
Otra diferencia importante es que WordPress registra callbacks arbitrarios. Una función, un método estático, un método de objeto o una closure pueden conectarse a un hook.
PrestaShop, en cambio, suele relacionar el nombre del hook con un método concreto de una clase Module. Por ejemplo:
displayHeader
se transforma conceptualmente en:
hookDisplayHeader()
WordPress funciona como un dispatcher global de callbacks. PrestaShop funciona principalmente como un dispatcher de módulos registrados en hooks.
Buenas prácticas avanzadas
En ambos CMS conviene evitar lógica pesada dentro de hooks ejecutados con mucha frecuencia. Consultas repetidas, llamadas HTTP, cálculos complejos o cargas masivas pueden degradar notablemente el rendimiento.
También es recomendable:
Usar prioridades explícitas cuando exista dependencia del orden.
Comprobar el contexto antes de ejecutar lógica.
Evitar modificar archivos del core.
Documentar los parámetros esperados.
Crear hooks propios en plugins y módulos reutilizables.
Mantener callbacks pequeños y delegar la lógica en servicios.
Evitar efectos secundarios dentro de filtros.
Revisar las diferencias de hooks entre versiones.
En una arquitectura profesional, el método conectado al hook debería actuar como adaptador:
public function hookActionValidateOrder(array $params): void
{
$this->orderSynchronizer->synchronize(
(int) $params['order']->id
);
}
De este modo, la lógica de negocio no queda acoplada al CMS y puede probarse mediante tests unitarios.
Conclusión
Los hooks son mucho más que funciones para insertar código. Constituyen el sistema de eventos y extensibilidad sobre el que se construyen plugins, módulos y temas.
WordPress dispone de una API especialmente flexible basada en actions, filters y la clase interna WP_Hook. PrestaShop utiliza un sistema orientado a módulos, posiciones visuales y eventos de comercio electrónico, actualmente integrado con una arquitectura híbrida basada también en Symfony.
Dominar estos mecanismos implica conocer no solo nombres como add_action(), add_filter() o Hook::exec(), sino entender cuándo se ejecutan, qué datos reciben, en qué orden intervienen y qué parte del ciclo de vida del core están modificando. Esa comprensión es la que permite desarrollar extensiones realmente mantenibles, compatibles y preparadas para evolucionar junto con el CMS.