Vous avez probablement des scripts PHP qui effectuent diverses tâches, telles que le nettoyage des métadonnées de messages orphelins ou la suppression des messages transitoires expirés. Au fil du temps, cette collection s’agrandit et vit dans un fichier de thème, un dossier d’extension ou un répertoire caché. Acorn vous aide à maîtriser cette désorganisation en apportant la console Artisan de Laravel à WordPress.
Cela signifie que vous pouvez créer des commandes WP-CLI personnalisées avec des fichiers de classe structurés qui centralisent votre logique de maintenance. Ces commandes s’exécutent de manière cohérente dans le développement, le staging et la production en utilisant des indicateurs de progression, des tableaux formatés, une gestion appropriée des erreurs, et bien plus encore. Vous pouvez ensuite les déclencher via SSH, les planifier avec des tâches cron, ou les exécuter pendant les déploiements.
Comment installer Acorn et exécuter les commandes
La première étape consiste à installer les dépendances dont vous avez besoin. Acorn a besoin de PHP 8.2 ou plus, Composer pour gérer les dépendances, et WP-CLI sur votre serveur. Kinsta inclut WP-CLI dans tous les plans d’hébergement, vous pouvez donc commencer à créer des commandes immédiatement.
Vous installez Acorn via Composer en utilisant composer require roots/acorn dans la racine du projet. Ensuite, ajoutez le code de démarrage au fichier functions.php de votre thème ou au fichier principal de votre extension :
<?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 configuration zéro stocke le cache de l’application et les journaux dans le répertoire de cache de WordPress à [wp-content]/cache/acorn/, avec vos commandes dans le répertoire app/ du thème.
La structure traditionnelle suit les conventions de Laravel, comme les répertoires dédiés pour app/, config/, storage/, et resources/ à la racine de votre projet. Vous mettez cela en place avec une ligne :
wp acorn acorn:init storage && wp acorn vendor:publish --tag=acorn
Si vous exécutez wp acorn list, vous vérifiez votre installation en affichant toutes les commandes Acorn disponibles. À partir de là, toutes les commandes personnalisées que vous créez sont stockées dans le répertoire app/Console/Commands/. Acorn découvre automatiquement toutes les classes de commandes dans ce répertoire et les enregistre avec WP-CLI.
Création de votre première commande Artisan
La commande Artisan wp acorn make:command CleanupCommand génère votre fichier avec la structure dont vous avez besoin. Il contient trois éléments clés dont chaque commande Artisan a besoin :
- Une propriété
$signaturequi définit le nom et les options de votre commande. - La propriété
$descriptionpour le texte d’aide. - Une méthode
handle()où se trouve la logique de votre commande.
Dans ce cas, elle construit app/Console/Commands/CleanupCommand.php avec une structure de commande de base :
<?php
namespace AppConsoleCommands;
use IlluminateConsoleCommand;
class CleanupCommand extends Command
{
protected $signature = 'app:cleanup';
protected $description = 'Command description';
public function handle()
{
//
}
}
La propriété $signature utilise une syntaxe spécifique :
- Une commande de base n’a besoin que d’un nom, comme
cleanup:run. - Vous ajoutez les arguments obligatoires en les plaçant entre accolades (
cleanup:run {days}, par exemple). - Les arguments facultatifs sont accompagnés d’un point d’interrogation :
cleanup:run {days?}. - Les options utilisent des doubles tirets :
cleanup:run {--force} {--limit=100}.
Ensuite, incluez une description de la commande et votre logique de base :
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;
}
Vous pouvez tester ceci en utilisant wp acorn cleanup:test --dry-run. La commande produit des messages formatés à l’aide du système de composants Artisan. La méthode $this->components->info() affiche les messages de réussite en vert. Vous pouvez également utiliser $this->components->error() pour les erreurs, $this->components->warn() pour les avertissements et $this->line() pour le texte brut.
Comment créer 3 commandes de maintenance pratiques
Voici quelques exemples qui vous aideront à faire face aux tâches de maintenance de la base de données qui se présentent pour de nombreux sites WordPress.
Bien que chaque commande inclue la gestion des erreurs et suive largement les standards de codage de WordPress pour garder vos données en sécurité, vous devriez toujours les utiliser comme un squelette pour vos propres projets plutôt que de simplement les copier-coller.
1. Nettoyer les métadonnées des articles orphelins
Les métadonnées des articles restent présentes après que vous ayez supprimé des articles. Cela se produit lorsque les extensions contournent les crochets de suppression de WordPress et laissent des entrées de métadonnées pointant vers des articles qui n’existent plus. Au fil du temps, ces métadonnées ralentissent les requêtes de votre base de données.
Une fois que vous avez créé la commande avec wp acorn make:command CleanupOrphanedMeta, vous pouvez commencer avec la structure de la classe de commande et la signature :
<?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...');
La commande utilise une requête LEFT JOIN pour trouver ces enregistrements orphelins. Le modèle vérifie les valeurs NULL dans la table posts. Si la jointure renvoie NULL, les métadonnées appartiennent à un article supprimé :
// 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;
}
Lorsque la commande trouve des orphelins, elle vous montre un décompte et échantillonne quelques enregistrements si vous êtes en mode de fonctionnement à vide. Vous devez vérifier ce qui est supprimé avant de valider :
$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;
}
La suppression proprement dite utilise $wpdb->prepare() pour éviter les attaques par injection SQL. La commande traite 1000 enregistrements à la fois, ce qui évite les problèmes de mémoire sur les sites comportant des millions d’entrées de métadonnées :
// 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;
}
}
Pour exécuter la commande, utilisez wp acorn maintenance:cleanup-orphaned-meta --dry-run.
2. Suppression des transitoires expirés
WordPress stocke les transitoires dans wp_options avec des dates d’expiration. Bien qu’une tâche cron quotidienne les nettoie, vous devez parfois lancer un nettoyage manuel pendant les fenêtres de maintenance ou lorsque le gonflement transitoire devient un problème.
Après avoir généré la commande avec wp acorn make:command CleanupTransients, vous pouvez configurer la structure de la commande :
<?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...');
Cette requête de suppression utilise la syntaxe multi-table DELETE pour supprimer à la fois le transitoire et son option de délai en une seule fois. La requête recherche les enregistrements de délai d’attente dont l’heure d’expiration est dépassée :
// 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()
)
);
Vérifiez également s’il y a des erreurs et suivez le nombre de suppressions :
if ($deleted === false) {
$this->components->error('Failed to delete transients');
return 1;
}
$transientCount = $deleted;
Sur les installations multisites, la commande exécute une deuxième requête pour les sites transitoires. Ces derniers utilisent des préfixes de table différents mais suivent le même modèle de suppression :
// 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;
}
}
Pour exécuter la commande, lancez wp acorn maintenance:cleanup-transients.
3. Auditer les options chargées automatiquement
Les options chargées automatiquement se chargent à chaque requête traitée par votre site WordPress. Vous pouvez commencer à observer des ralentissements et des pics de consommation de mémoire lorsque ces données dépassent 1 Mo. Cette commande recherche les options les plus chargées afin que vous puissiez identifier les extensions responsables de ce gonflement.
Tout d’abord, créez la commande d’audit avec wp acorn make:command AuditAutoload. Ensuite, définissez la signature de la commande avec un seuil 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 là, calculez la taille totale de toutes les options chargées automatiquement :
// 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)");
La commande recherche les options supérieures à votre seuil, les trie par taille et limite les résultats aux 20 plus 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;
}
La méthode Artisan $this->table() formate ces résultats sous la forme d’un tableau ASCII : la lecture de données tabulaires dans votre terminal est préférable à l’analyse de la sortie brute de la requête :
$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
);
La commande émet un avertissement lorsque le chargement automatique total dépasse 3 Mo, ce qui indique un problème de performance que vous devez résoudre :
if ($totalBytes > 3000000) {
$this->newLine();
$this->components->error('Warning: Total autoload exceeds 3MB');
}
return 0;
}
}
Pour lancer l’audit, utilisez wp acorn maintenance:audit-autoload --threshold=500000.
Comment accéder aux données de WordPress dans les commandes
Les fonctions WordPress fonctionnent à l’intérieur de vos méthodes de commande parce qu’Acorn s’intègre dans le cycle de vie de WordPress. Cela signifie que vous pouvez appeler certaines fonctions telles que get_posts() ou get_option() sans aucune configuration particulière :
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;
}
Pour les requêtes directes à la base de données, déclarez le global $wpdb au début de votre méthode :
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() est idéal lorsque vos requêtes comprennent des variables ou des entrées utilisateur, car il permet de prévenir les attaques par injection 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 vous testez false après toute opération sur la base de données, vous devriez pouvoir détecter les erreurs :
$updated = $wpdb->update(
$wpdb->posts,
['post_status' => 'draft'],
['ID' => 123],
['%s'],
['%d']
);
if ($updated === false) {
$this->components->error('Database update failed');
return 1;
}
Les types de publication personnalisés et les taxonomies fonctionnent avec les fonctions standard 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',
]);
}
Les commandes WP-CLI personnalisées sont simples avec Acorn et Artisan Console
Acorn vous permet d’accéder aux classes de commandes structurées de Laravel, aux composants de sortie formatés, à la gestion des erreurs, et plus encore, tout en vous donnant un accès complet aux fonctions et aux données de WordPress.
Vous pouvez intégrer des commandes via l’accès SSH et la planification cron dans Kinsta. Vous pouvez également ajouter des commandes à vos scripts de déploiement pour automatiser la maintenance, par exemple en utilisant les flux de travail GitHub Actions.
Si vous êtes prêt à centraliser vos tâches de maintenance WordPress avec des commandes WP-CLI personnalisées, l‘hébergement WordPress infogéré de Kinsta inclut l’accès SSH et WP-CLI sur tous les plans.