Probablemente tengas scripts PHP que realizan diversas tareas, como limpiar metadatos de entradas huérfanas o eliminar transitorios caducados. Con el tiempo, esa colección crece y se almacena en un archivo de tema, una carpeta de plugins o un directorio oculto. Acorn ayuda a controlar esta desorganización al incorporar la consola Artisan de Laravel a WordPress.
Esto significa que puedes crear comandos WP-CLI personalizados con archivos de clase estructurados que centralizan tu lógica de mantenimiento. Estos comandos se ejecutan de forma coherente en las fases de desarrollo, staging y producción utilizando indicadores de progreso, salida de tablas formateadas, gestión adecuada de errores y mucho más. A continuación, puedes activarlos a través de SSH, programarlos con cron jobs o ejecutarlos durante los despliegues.
Cómo instalar Acorn y ejecutar comandos
El primer paso es instalar las dependencias que necesitas. Acorn necesita PHP 8.2 o superior, Composer para gestionar las dependencias y WP-CLI ejecutándose en tu servidor. Kinsta incluye WP-CLI en todos los planes de alojamiento, así que puedes empezar a crear comandos directamente.
Instala Acorn a través de Composer utilizando composer require roots/acorn dentro de la raíz del proyecto. A continuación, añade el código de arranque al archivo functions.php de tu tema o al archivo principal de tu plugin:
<?php
use RootsAcornApplication;
if (! class_exists(RootsAcornApplication::class)) {
wp_die(
__('You need to install Acorn to use this site.', 'domain'),
'',
[
'link_url' => 'https://roots.io/acorn/docs/installation/',
'link_text' => __('Acorn Docs: Installation', 'domain'),
]
);
}
add_action('after_setup_theme', function () {
Application::configure()
->withProviders([
AppProvidersThemeServiceProvider::class,
])
->boot();
}, 0);
La configuración lista para usar (zero-config) almacena la caché de la aplicación y los registros en el directorio de caché de WordPress en [wp-content]/cache/acorn/, mientras que tus comandos residen en el directorio app/ del tema.
La estructura tradicional sigue las convenciones de Laravel, como directorios dedicados para app/, config/, storage/, y resources/ en la raíz de tu proyecto. Esto se configura con una sola línea:
wp acorn acorn:init storage && wp acorn vendor:publish --tag=acorn
Si ejecutas wp acorn list, esto verificará tu instalación mostrando todos los comandos Acorn disponibles. A partir de este momento, todos los comandos personalizados que crees se almacenarán en el directorio app/Console/Commands/. Acorn descubre automáticamente cualquier clase de comando en esta ubicación y la registra en WP-CLI.
Creando tu primer comando Artisan
El comando Artisan wp acorn make:command CleanupCommand genera tu archivo con la estructura que necesitas. Contiene tres elementos clave que todo comando Artisan necesita:
- Una propiedad
$signatureque define el nombre y las opciones de tu comando. - La propiedad
$descriptionpara el texto de ayuda. - Un método
handle()donde vive la lógica de tu comando.
En este caso, construye app/Console/Commands/CleanupCommand.php con una estructura básica de comandos:
<?php
namespace AppConsoleCommands;
use IlluminateConsoleCommand;
class CleanupCommand extends Command
{
protected $signature = 'app:cleanup';
protected $description = 'Command description';
public function handle()
{
//
}
}
La propiedad $signature utiliza una sintaxis específica:
- Un comando básico sólo necesita un nombre, como
cleanup:run. - Los argumentos obligatorios se añaden entre llaves (
cleanup:run {days}, por ejemplo). - Los argumentos opcionales llevan un signo de interrogación:
cleanup:run {days?}. - Las opciones utilizan guiones dobles:
cleanup:run {--force} {--limit=100}.
A continuación, incluye una descripción del comando y su lógica básica:
protected $signature = 'cleanup:test {--dry-run : Preview changes without executing}';
protected $description = 'Test cleanup command';
public function handle()
{
$dryRun = $this->option('dry-run');
if ($dryRun) {
$this->components->info('Running in dry-run mode');
}
$this->components->info('Cleanup command executed');
return 0;
}
Puedes probarlo utilizando wp acorn cleanup:test --dry-run. El comando emite mensajes formateados utilizando el sistema de componentes de Artisan. El método $this->components->info() muestra los mensajes de éxito en verde. También puedes utilizar $this->components->error() para los errores, $this->components->warn() para las advertencias y $this->line() para la salida de texto plano.
Cómo construir 3 comandos de mantenimiento prácticos
Aquí tienes algunos ejemplos que te ayudarán a abordar las tareas de mantenimiento de la base de datos que surgen en muchos sitios de WordPress.
Aunque cada comando incluye gestión de errores y sigue en gran medida los estándares de codificación de WordPress para mantener tus datos seguros, debes utilizarlos como base para tus propios proyectos en lugar de limitarte a copiarlos y pegarlos.
1. Limpieza de metadatos de publicaciones huérfanas
Los metadatos de las entradas permanecen después de eliminar las entradas. Esto ocurre cuando los plugins omiten los hooks de eliminación de WordPress y dejan entradas de metadatos que apuntan a entradas que ya no existen. Con el tiempo, esta sobrecarga ralentiza las consultas a la base de datos.
Una vez creado el comando con wp acorn make:command CleanupOrphanedMeta, puedes empezar con la estructura y la firma de la clase del comando:
<?php
namespace AppConsoleCommands;
use IlluminateConsoleCommand;
class CleanupOrphanedMeta extends Command
{
protected $signature = 'maintenance:cleanup-orphaned-meta
{--dry-run : Preview orphans without deleting}';
protected $description = 'Remove orphaned post metadata from wp_postmeta';
public function handle()
{
global $wpdb;
$dryRun = $this->option('dry-run');
$this->components->info('Scanning for orphaned post metadata...');
El comando utiliza una consulta LEFT JOIN para encontrar estos registros huérfanos. El patrón comprueba si hay valores NULL en la tabla posts. Si la unión devuelve NULL los metadatos pertenecen a un post eliminado:
// Find orphaned metadata
$orphans = $wpdb->get_results("
SELECT pm.meta_id, pm.post_id, pm.meta_key
FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID
WHERE p.ID IS NULL
LIMIT 1000
");
if (empty($orphans)) {
$this->components->info('No orphaned metadata found');
return 0;
}
Cuando el comando encuentra registros huérfanos, muestra un recuento y una muestra de algunos registros si estás en modo de prueba (dry-run). Debes verificar qué se va a eliminar antes de confirmar los cambios:
$count = count($orphans);
$this->components->warn("Found {$count} orphaned metadata records");
if ($dryRun) {
$this->newLine();
$this->line('Sample orphaned records:');
foreach (array_slice($orphans, 0, 5) as $orphan) {
$this->line(" → Post ID {$orphan->post_id}: {$orphan->meta_key}");
}
return 0;
}
El borrado real utiliza $wpdb->prepare() para evitar ataques de inyección SQL. El comando procesa 1.000 registros a la vez, lo que evita problemas de memoria en sitios con millones de entradas de metadatos:
// Delete orphaned records
$metaIds = array_map(function($orphan) {
return $orphan->meta_id;
}, $orphans);
$placeholders = implode(',', array_fill(0, count($metaIds), '%d'));
$deleted = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->postmeta} WHERE meta_id IN ({$placeholders})",
...$metaIds
)
);
if ($deleted === false) {
$this->components->error('Failed to delete orphaned metadata');
return 1;
}
$this->components->info("Deleted {$deleted} orphaned metadata records");
return 0;
}
}
Para ejecutar el comando, utiliza wp acorn maintenance:cleanup-orphaned-meta --dry-run.
2. Eliminar transitorios caducados
WordPress almacena datos transitorios en wp_options con fechas de caducidad. Aunque un cron job diario se encarga de limpiarlos, a veces es necesario realizar una limpieza manual durante las ventanas de mantenimiento o cuando la acumulación de datos transitorios se convierte en un problema.
Después de generar el comando con wp acorn make:command CleanupTransients, puedes configurar la estructura del comando:
<?php
namespace AppConsoleCommands;
use IlluminateConsoleCommand;
class CleanupTransients extends Command
{
protected $signature = 'maintenance:cleanup-transients';
protected $description = 'Delete expired transients from wp_options';
public function handle()
{
global $wpdb;
$this->components->info('Deleting expired transients...');
Esta consulta de eliminación utiliza la sintaxis multi-tabla DELETE para eliminar tanto el transitorio como su opción de tiempo de espera a la vez. La consulta encuentra los registros de tiempo de espera en los que ha pasado la fecha de caducidad:
// Delete expired regular transients
$deleted = $wpdb->query(
$wpdb->prepare(
"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12))
AND b.option_value < %d",
$wpdb->esc_like('_transient_') . '%',
$wpdb->esc_like('_transient_timeout_') . '%',
time()
)
);
También comprueba si hay errores y realiza un seguimiento del recuento de eliminaciones:
if ($deleted === false) {
$this->components->error('Failed to delete transients');
return 1;
}
$transientCount = $deleted;
En las instalaciones Multisitio, el comando ejecuta una segunda consulta para los sitios transitorios. Éstas utilizan prefijos de tabla diferentes, pero siguen el mismo patrón de borrado:
// Delete expired site transients (multisite)
if (is_multisite()) {
$siteDeleted = $wpdb->query(
$wpdb->prepare(
"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT('_site_transient_timeout_', SUBSTRING(a.option_name, 17))
AND b.option_value < %d",
$wpdb->esc_like('_site_transient_') . '%',
$wpdb->esc_like('_site_transient_timeout_') . '%',
time()
)
);
if ($siteDeleted !== false) {
$transientCount += $siteDeleted;
}
}
$this->components->info("Deleted {$transientCount} expired transients");
return 0;
}
}
Para ejecutar el comando, ejecuta wp acorn maintenance:cleanup-transients.
3. Auditoría de opciones cargadas automáticamente
Las opciones con carga automática (autoloaded options) se cargan en cada solicitud que procesa tu sitio WordPress. Puedes empezar a notar ralentizaciones y picos de consumo de memoria cuando estos datos superan 1 MB. Este comando encuentra las opciones con carga automática más pesadas para que puedas localizar qué plugins están causando el exceso de carga.
Primero, crea el comando de auditoría con wp acorn make:command AuditAutoload. A continuación, define la firma del comando con un umbral configurable:
<?php
namespace AppConsoleCommands;
use IlluminateConsoleCommand;
class AuditAutoload extends Command
{
protected $signature = 'maintenance:audit-autoload
{--threshold=1000000 : Size threshold in bytes}';
protected $description = 'Audit autoloaded options size';
public function handle()
{
global $wpdb;
$threshold = (int) $this->option('threshold');
$this->components->info('Calculating autoloaded options size...');
A partir de aquí, calcula el tamaño total de todas las opciones autocargadas:
// Get total autoload size
$result = $wpdb->get_row(
"SELECT
SUM(LENGTH(option_value)) as total_bytes,
COUNT(*) as total_count
FROM {$wpdb->options}
WHERE autoload = 'yes'"
);
$totalBytes = (int) $result->total_bytes;
$totalCount = (int) $result->total_count;
$totalMb = round($totalBytes / 1024 / 1024, 2);
$this->newLine();
$this->line("Total autoloaded: {$totalMb} MB ({$totalCount} options)");
El comando ejecuta una consulta para las opciones que superan tu umbral, las ordena por tamaño y limita los resultados a las 20 más grandes:
// Get largest autoloaded options
$largeOptions = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name, LENGTH(option_value) as size_bytes
FROM {$wpdb->options}
WHERE autoload = 'yes'
AND LENGTH(option_value) > %d
ORDER BY size_bytes DESC
LIMIT 20",
$threshold
)
);
if (empty($largeOptions)) {
$this->components->info('No options exceed the threshold');
return 0;
}
El método $this->table() de Artisan formatea estos resultados como una tabla ASCII: leer datos tabulares en tu terminal es mejor que analizar la salida sin procesar de la consulta:
$this->newLine();
$this->components->warn('Large autoloaded options:');
$this->newLine();
$tableData = [];
foreach ($largeOptions as $option) {
$sizeKb = round($option->size_bytes / 1024, 2);
$tableData[] = [
$option->option_name,
$sizeKb . ' KB'
];
}
$this->table(
['Option Name', 'Size'],
$tableData
);
El comando lanza una advertencia cuando la carga automática total supera los 3 MB, lo que indica un problema de rendimiento que debes solucionar:
if ($totalBytes > 3000000) {
$this->newLine();
$this->components->error('Warning: Total autoload exceeds 3MB');
}
return 0;
}
}
Para ejecutar la auditoría, utiliza wp acorn maintenance:audit-autoload --threshold=500000.
Cómo acceder a los datos de WordPress dentro de los comandos
Las funciones de WordPress funcionan dentro de tus métodos de comando porque Acorn se inicia dentro del ciclo de vida de WordPress. Esto significa que puedes llamar a algunas funciones como get_posts() o get_option() sin ninguna configuración especial:
public function handle()
{
$posts = get_posts([
'post_type' => 'post',
'post_status' => 'publish',
'numberposts' => 10,
]);
foreach ($posts as $post) {
$this->line($post->post_title);
}
return 0;
}
Para consultas directas a la base de datos, declara la variable global $wpdb al inicio de tu método:
public function handle()
{
global $wpdb;
$count = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = 'publish'"
);
$this->line("Published posts: {$count}");
return 0;
}
$wpdb->prepare() es ideal siempre que tus consultas incluyan variables o entradas de usuario, ya que ayuda a evitar ataques de inyección SQL:
$status = 'publish';
$postType = 'post';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts}
WHERE post_status = %s AND post_type = %s",
$status,
$postType
)
);
Si realizas una comprobación de false después de cualquier operación con la base de datos, esto te permitirá detectar errores:
$updated = $wpdb->update(
$wpdb->posts,
['post_status' => 'draft'],
['ID' => 123],
['%s'],
['%d']
);
if ($updated === false) {
$this->components->error('Database update failed');
return 1;
}
Los tipos de entradas personalizadas y las taxonomías funcionan a través de las funciones estándar de WordPress:
$terms = get_terms([
'taxonomy' => 'category',
'hide_empty' => false,
]);
foreach ($terms as $term) {
wp_update_term($term->term_id, 'category', [
'description' => 'Updated via command',
]);
}
Crear comandos personalizados de WP-CLI es sencillo con Acorn y Artisan Console
Acorn te permite acceder a las clases de comandos estructurados de Laravel, componentes de salida formateados, gestión adecuada de errores y mucho más, a la vez que te da acceso completo a las funciones y datos de WordPress.
Puedes integrar comandos a través del acceso SSH y la programación cron dentro de Kinsta. También puedes añadir comandos a tus scripts de despliegue para automatizar el mantenimiento, por ejemplo, utilizando los flujos de trabajo de GitHub Actions.
Si estás listo para centralizar tus tareas de mantenimiento de WordPress con comandos WP-CLI personalizados, el alojamiento para WordPress administrado de Kinsta incluye acceso SSH y WP-CLI en todos los planes.