Introduction
Le système de hooks (crochets) de WordPress est l’épine dorsale de son architecture extensible. C’est ce qui permet à des milliers de plugins et thèmes de coexister et d’étendre WordPress sans modifier son code source. Comprendre les hooks est essentiel pour tout développeur WordPress intermédiaire.
Dans ce guide approfondi, nous explorerons la différence entre actions et filtres, leur fonctionnement interne, et comment les utiliser efficacement dans vos projets professionnels.
Table des matières
- Architecture du système de hooks
- Actions vs Filtres : comprendre la différence
- Utiliser les actions existantes
- Utiliser les filtres existants
- Créer vos propres hooks personnalisés
- Priorités et ordre d’exécution
- Hooks conditionnels et contextuels
- Performance et optimisation
- Debugging des hooks
- Cas d’usage avancés et patterns
1. Architecture du système de hooks
Le pattern Observer
WordPress implémente le pattern de conception Observer (aussi appelé Publish-Subscribe) :
┌─────────────┐ Fire Event ┌──────────────┐
│ WordPress │ ──────────────────────> │ Hook System │
│ Core │ │ (wp-hooks) │
└─────────────┘ └──────────────┘
│
│ Notify
▼
┌────────────────────────────────┐
│ Registered Callbacks │
│ (Plugins, Themes, Functions) │
└────────────────────────────────┘
Fonctionnement interne
<?php
/**
* Simplifié : structure interne du système de hooks
*/
// Tableau global stockant tous les hooks
global $wp_filter;
/*
Structure de $wp_filter:
$wp_filter = array(
'hook_name' => WP_Hook Object {
callbacks = array(
10 => array( // Priorité
'unique_id_1' => array(
'function' => 'callback_function',
'accepted_args' => 1
),
'unique_id_2' => array(
'function' => array($object, 'method'),
'accepted_args' => 2
)
),
20 => array( ... )
)
}
)
*/
Cycle de vie d’un hook
1. add_action() ou add_filter()
↓
2. Stockage dans $wp_filter global
↓
3. do_action() ou apply_filters()
↓
4. Tri par priorité
↓
5. Exécution séquentielle des callbacks
↓
6. Retour du résultat (filtres) ou fin (actions)
2. Actions vs Filtres : comprendre la différence
Actions : Faire quelque chose à un moment précis
Les actions permettent d’exécuter du code à des moments spécifiques sans modifier de données.
<?php
/**
* Exemple conceptuel d'une action
*/
// WordPress fait ceci internalement:
function do_action_example() {
// Code WordPress...
do_action('mon_action_personnalisee', $arg1, $arg2);
// Suite du code WordPress...
}
// Vous vous "accrochez" avec:
add_action('mon_action_personnalisee', 'ma_fonction_callback', 10, 2);
function ma_fonction_callback($arg1, $arg2) {
// Votre code s'exécute ici
// Pas besoin de retourner quoi que ce soit
echo "Action déclenchée !";
}
Filtres : Modifier des données
Les filtres permettent de modifier des données avant qu’elles soient utilisées ou affichées.
<?php
/**
* Exemple conceptuel d'un filtre
*/
// WordPress fait ceci internalement:
function apply_filters_example() {
$valeur = "Texte original";
// Permet aux plugins de modifier $valeur
$valeur = apply_filters('mon_filtre_personnalise', $valeur, $arg2);
// Utilise $valeur (potentiellement modifiée)
echo $valeur;
}
// Vous vous "accrochez" avec:
add_filter('mon_filtre_personnalise', 'mon_callback_filtre', 10, 2);
function mon_callback_filtre($valeur, $arg2) {
// Modifier la valeur
$valeur = strtoupper($valeur);
// TOUJOURS retourner la valeur
return $valeur;
}
Tableau comparatif
| Caractéristique | Actions | Filtres |
|---|---|---|
| But | Exécuter du code | Modifier des données |
| Valeur de retour | Aucune (void) | Obligatoire |
| Fonction d’ajout | add_action() |
add_filter() |
| Fonction de déclenchement | do_action() |
apply_filters() |
| Exemple d’usage | Envoyer un email, enregistrer un log | Modifier un titre, ajouter du CSS |
3. Utiliser les actions existantes
Actions du cycle de vie WordPress
<?php
/**
* Hook au démarrage de WordPress
*/
add_action('init', 'yawc_initialization');
function yawc_initialization() {
// Enregistrer des post types personnalisés
register_post_type('livre', array(
'labels' => array(
'name' => 'Livres',
'singular_name' => 'Livre'
),
'public' => true,
'has_archive' => true,
'supports' => array('title', 'editor', 'thumbnail'),
'rewrite' => array('slug' => 'livres'),
));
// Enregistrer des taxonomies
register_taxonomy('genre', 'livre', array(
'labels' => array(
'name' => 'Genres',
'singular_name' => 'Genre'
),
'hierarchical' => true,
'rewrite' => array('slug' => 'genre'),
));
}
/**
* Hook lors de l'activation d'un thème
*/
add_action('after_setup_theme', 'yawc_theme_setup');
function yawc_theme_setup() {
// Support des fonctionnalités du thème
add_theme_support('post-thumbnails');
add_theme_support('custom-logo');
add_theme_support('html5', array(
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
));
// Enregistrer les menus
register_nav_menus(array(
'primary' => __('Menu Principal', 'yawc'),
'footer' => __('Menu Pied de page', 'yawc'),
));
// Tailles d'images personnalisées
add_image_size('portrait', 400, 600, true);
add_image_size('paysage', 800, 400, true);
}
/**
* Hook lors du chargement des scripts
*/
add_action('wp_enqueue_scripts', 'yawc_enqueue_assets');
function yawc_enqueue_assets() {
// Charger les styles
wp_enqueue_style(
'yawc-main-style',
get_template_directory_uri() . '/assets/css/main.css',
array(),
'1.0.0'
);
// Charger les scripts
wp_enqueue_script(
'yawc-main-script',
get_template_directory_uri() . '/assets/js/main.js',
array('jquery'),
'1.0.0',
true
);
// Passer des données PHP au JavaScript
wp_localize_script('yawc-main-script', 'yawcData', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('yawc-nonce'),
'siteUrl' => home_url(),
));
}
Actions de contenu et d’affichage
<?php
/**
* Ajouter du contenu avant et après les articles
*/
add_action('the_content', 'yawc_add_content_before', 1);
add_action('the_content', 'yawc_add_content_after', 999);
function yawc_add_content_before($content) {
if (is_single() && !is_admin()) {
$before = '<div class="article-meta">';
$before .= '<span class="reading-time">' . yawc_get_reading_time() . ' min de lecture</span>';
$before .= '<span class="publish-date">Publié le ' . get_the_date() . '</span>';
$before .= '</div>';
return $before . $content;
}
return $content;
}
function yawc_add_content_after($content) {
if (is_single() && !is_admin()) {
$after = '<div class="article-share">';
$after .= '<h4>Partager cet article</h4>';
$after .= yawc_get_share_buttons();
$after .= '</div>';
return $content . $after;
}
return $content;
}
function yawc_get_reading_time() {
$content = get_post_field('post_content', get_the_ID());
$word_count = str_word_count(strip_tags($content));
$reading_time = ceil($word_count / 200); // 200 mots par minute
return $reading_time;
}
/**
* Modifier le footer
*/
add_action('wp_footer', 'yawc_custom_footer_content');
function yawc_custom_footer_content() {
?>
<div class="custom-footer-banner">
<p>Abonnez-vous à notre newsletter pour recevoir nos derniers articles !</p>
<form action="<?php echo admin_url('admin-post.php'); ?>" method="post">
<?php wp_nonce_field('newsletter_subscribe', 'newsletter_nonce'); ?>
<input type="hidden" name="action" value="newsletter_subscribe">
<input type="email" name="email" placeholder="votre@email.com" required>
<button type="submit">S'abonner</button>
</form>
</div>
<?php
}
Actions d’administration
<?php
/**
* Ajouter une page d'administration personnalisée
*/
add_action('admin_menu', 'yawc_add_admin_menu');
function yawc_add_admin_menu() {
add_menu_page(
'Configuration YAWC', // Titre de la page
'YAWC Settings', // Titre du menu
'manage_options', // Capacité requise
'yawc-settings', // Slug
'yawc_settings_page_callback', // Callback
'dashicons-admin-generic', // Icône
30 // Position
);
add_submenu_page(
'yawc-settings',
'Statistiques',
'Stats',
'manage_options',
'yawc-stats',
'yawc_stats_page_callback'
);
}
function yawc_settings_page_callback() {
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form method="post" action="options.php">
<?php
settings_fields('yawc_settings_group');
do_settings_sections('yawc-settings');
submit_button();
?>
</form>
</div>
<?php
}
/**
* Enregistrer les paramètres
*/
add_action('admin_init', 'yawc_register_settings');
function yawc_register_settings() {
register_setting('yawc_settings_group', 'yawc_api_key', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
));
register_setting('yawc_settings_group', 'yawc_enable_cache', array(
'type' => 'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
'default' => false,
));
add_settings_section(
'yawc_main_section',
'Paramètres principaux',
'yawc_section_callback',
'yawc-settings'
);
add_settings_field(
'yawc_api_key',
'Clé API',
'yawc_api_key_callback',
'yawc-settings',
'yawc_main_section'
);
add_settings_field(
'yawc_enable_cache',
'Activer le cache',
'yawc_enable_cache_callback',
'yawc-settings',
'yawc_main_section'
);
}
function yawc_section_callback() {
echo '<p>Configurez les paramètres de YAWC.</p>';
}
function yawc_api_key_callback() {
$value = get_option('yawc_api_key', '');
printf(
'<input type="text" name="yawc_api_key" value="%s" class="regular-text">',
esc_attr($value)
);
}
function yawc_enable_cache_callback() {
$value = get_option('yawc_enable_cache', false);
printf(
'<input type="checkbox" name="yawc_enable_cache" value="1" %s>',
checked(1, $value, false)
);
}
Actions de base de données
<?php
/**
* Actions lors de la sauvegarde d'un post
*/
add_action('save_post', 'yawc_save_post_meta', 10, 3);
function yawc_save_post_meta($post_id, $post, $update) {
// Vérifications de sécurité
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
if (wp_is_post_revision($post_id)) {
return;
}
// Sauvegarder des métadonnées personnalisées
if (isset($_POST['yawc_custom_field'])) {
update_post_meta(
$post_id,
'_yawc_custom_field',
sanitize_text_field($_POST['yawc_custom_field'])
);
}
// Générer automatiquement un slug SEO-friendly
if ($post->post_type === 'livre') {
$seo_title = sanitize_title($post->post_title);
update_post_meta($post_id, '_yawc_seo_slug', $seo_title);
}
// Logger l'activité
yawc_log_post_update($post_id, $post, $update);
}
/**
* Action lors de la suppression d'un post
*/
add_action('before_delete_post', 'yawc_cleanup_post_data');
function yawc_cleanup_post_data($post_id) {
// Nettoyer les données associées
global $wpdb;
// Supprimer les métadonnées personnalisées
$wpdb->delete(
$wpdb->postmeta,
array('post_id' => $post_id, 'meta_key' => '_yawc_custom_data'),
array('%d', '%s')
);
// Supprimer les fichiers uploadés associés
$attachments = get_posts(array(
'post_type' => 'attachment',
'post_parent' => $post_id,
'numberposts' => -1,
));
foreach ($attachments as $attachment) {
wp_delete_attachment($attachment->ID, true);
}
// Logger la suppression
error_log(sprintf('Post %d deleted at %s', $post_id, current_time('mysql')));
}
4. Utiliser les filtres existants
Filtres de contenu
<?php
/**
* Modifier le titre des articles
*/
add_filter('the_title', 'yawc_modify_title', 10, 2);
function yawc_modify_title($title, $post_id) {
// Ajouter un indicateur "Nouveau" aux articles récents
if (is_main_query() && !is_admin()) {
$post = get_post($post_id);
if ($post && $post->post_type === 'post') {
$days_old = (time() - strtotime($post->post_date)) / DAY_IN_SECONDS;
if ($days_old <= 7) {
$title .= ' <span class="badge-new">Nouveau</span>';
}
}
}
return $title;
}
/**
* Modifier l'extrait
*/
add_filter('the_excerpt', 'yawc_custom_excerpt');
function yawc_custom_excerpt($excerpt) {
// Ajouter un lien "Lire la suite" personnalisé
if (has_excerpt()) {
$excerpt = rtrim($excerpt);
$excerpt .= sprintf(
' <a href="%s" class="read-more-link">Continuer la lecture →</a>',
get_permalink()
);
}
return $excerpt;
}
/**
* Modifier la longueur de l'extrait
*/
add_filter('excerpt_length', 'yawc_excerpt_length');
function yawc_excerpt_length($length) {
return is_front_page() ? 20 : 55;
}
/**
* Personnaliser le texte "Lire la suite"
*/
add_filter('excerpt_more', 'yawc_excerpt_more');
function yawc_excerpt_more($more) {
return '...';
}
Filtres de requête
<?php
/**
* Modifier la requête principale
*/
add_filter('pre_get_posts', 'yawc_modify_main_query');
function yawc_modify_main_query($query) {
// Ne modifier que la requête principale sur le front-end
if (!is_admin() && $query->is_main_query()) {
// Exclure une catégorie de la page d'accueil
if ($query->is_home()) {
$query->set('cat', '-5'); // Exclure la catégorie ID 5
}
// Changer le nombre d'articles par page pour les archives
if ($query->is_archive()) {
$query->set('posts_per_page', 12);
}
// Trier par titre sur les pages de catégorie
if ($query->is_category()) {
$query->set('orderby', 'title');
$query->set('order', 'ASC');
}
// Recherche personnalisée
if ($query->is_search()) {
// Rechercher dans plusieurs post types
$query->set('post_type', array('post', 'page', 'livre'));
// Exclure les posts privés des résultats de recherche
$query->set('post_status', 'publish');
}
}
return $query;
}
/**
* Modifier les arguments de WP_Query
*/
add_filter('posts_where', 'yawc_filter_posts_where', 10, 2);
function yawc_filter_posts_where($where, $query) {
global $wpdb;
// Ajouter un filtre de date personnalisé
if ($custom_date = $query->get('yawc_custom_date')) {
$where .= $wpdb->prepare(
" AND $wpdb->posts.post_date >= %s",
date('Y-m-d', strtotime($custom_date))
);
}
return $where;
}
// Utilisation:
// $query = new WP_Query(array('yawc_custom_date' => '-30 days'));
Filtres d’authentification et de permissions
<?php
/**
* Modifier les capacités utilisateur
*/
add_filter('user_has_cap', 'yawc_modify_user_capabilities', 10, 4);
function yawc_modify_user_capabilities($allcaps, $caps, $args, $user) {
// Permettre aux éditeurs de gérer les options
if (isset($allcaps['editor']) && $allcaps['editor']) {
$allcaps['manage_options'] = true;
}
// Restriction basée sur l'heure
$current_hour = (int) current_time('H');
if ($current_hour >= 18 || $current_hour < 8) {
// Retirer certaines capacités hors des heures de bureau
unset($allcaps['delete_published_posts']);
}
return $allcaps;
}
/**
* Personnaliser le message de connexion
*/
add_filter('login_message', 'yawc_custom_login_message');
function yawc_custom_login_message($message) {
if (empty($message)) {
return '<p class="message">Bienvenue ! Connectez-vous pour accéder à votre espace.</p>';
}
return $message;
}
/**
* Modifier l'URL de redirection après connexion
*/
add_filter('login_redirect', 'yawc_login_redirect', 10, 3);
function yawc_login_redirect($redirect_to, $request, $user) {
if (!isset($user->roles) || !is_array($user->roles)) {
return $redirect_to;
}
// Rediriger les admins vers le dashboard
if (in_array('administrator', $user->roles)) {
return admin_url();
}
// Rediriger les autres utilisateurs vers leur profil
return admin_url('profile.php');
}
Filtres d’administration
<?php
/**
* Personnaliser les colonnes de la liste des posts
*/
add_filter('manage_posts_columns', 'yawc_add_custom_columns');
add_filter('manage_posts_custom_column', 'yawc_custom_column_content', 10, 2);
function yawc_add_custom_columns($columns) {
// Réorganiser les colonnes
$new_columns = array();
foreach ($columns as $key => $value) {
$new_columns[$key] = $value;
// Ajouter après le titre
if ($key === 'title') {
$new_columns['word_count'] = 'Nombre de mots';
$new_columns['reading_time'] = 'Temps de lecture';
}
}
return $new_columns;
}
function yawc_custom_column_content($column, $post_id) {
switch ($column) {
case 'word_count':
$content = get_post_field('post_content', $post_id);
$word_count = str_word_count(strip_tags($content));
echo number_format_i18n($word_count);
break;
case 'reading_time':
$content = get_post_field('post_content', $post_id);
$word_count = str_word_count(strip_tags($content));
$minutes = ceil($word_count / 200);
printf('%d min', $minutes);
break;
}
}
/**
* Rendre les colonnes personnalisées triables
*/
add_filter('manage_edit-post_sortable_columns', 'yawc_sortable_columns');
function yawc_sortable_columns($columns) {
$columns['word_count'] = 'word_count';
return $columns;
}
/**
* Personnaliser le footer de l'admin
*/
add_filter('admin_footer_text', 'yawc_custom_admin_footer');
function yawc_custom_admin_footer($text) {
return sprintf(
'Développé avec ❤️ par <a href="%s">Votre Entreprise</a> | WordPress %s',
'https://votresite.com',
get_bloginfo('version')
);
}
5. Créer vos propres hooks personnalisés
Créer des actions personnalisées
<?php
/**
* Plugin principal avec hooks personnalisés
*/
class YAWC_Newsletter {
public function __construct() {
add_action('admin_post_newsletter_subscribe', array($this, 'handle_subscription'));
}
public function handle_subscription() {
// Vérifications de sécurité
if (!isset($_POST['newsletter_nonce']) ||
!wp_verify_nonce($_POST['newsletter_nonce'], 'newsletter_subscribe')) {
wp_die('Erreur de sécurité');
}
$email = sanitize_email($_POST['email']);
if (!is_email($email)) {
wp_die('Email invalide');
}
// Hook AVANT l'inscription
do_action('yawc_before_newsletter_subscribe', $email);
// Logique d'inscription
$result = $this->add_subscriber($email);
if ($result) {
// Hook APRÈS l'inscription réussie
do_action('yawc_after_newsletter_subscribe', $email, $result);
wp_redirect(add_query_arg('subscribed', '1', wp_get_referer()));
} else {
// Hook en cas d'erreur
do_action('yawc_newsletter_subscribe_failed', $email);
wp_redirect(add_query_arg('subscribe_error', '1', wp_get_referer()));
}
exit;
}
private function add_subscriber($email) {
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_subscribers';
$result = $wpdb->insert(
$table_name,
array(
'email' => $email,
'subscribe_date' => current_time('mysql'),
'status' => 'active',
),
array('%s', '%s', '%s')
);
return $result !== false;
}
}
new YAWC_Newsletter();
/**
* Utilisation des hooks personnalisés par d'autres plugins/thèmes
*/
// Envoyer un email de bienvenue
add_action('yawc_after_newsletter_subscribe', 'yawc_send_welcome_email');
function yawc_send_welcome_email($email, $subscriber_id) {
$subject = 'Bienvenue dans notre newsletter !';
$message = sprintf(
"Bonjour,\n\nMerci de vous être inscrit à notre newsletter.\n\nVous recevrez désormais nos dernières actualités.\n\nCordialement,\nL'équipe"
);
wp_mail($email, $subject, $message);
}
// Logger les tentatives échouées
add_action('yawc_newsletter_subscribe_failed', 'yawc_log_failed_subscription');
function yawc_log_failed_subscription($email) {
error_log(sprintf(
'[Newsletter] Failed subscription attempt for email: %s at %s',
$email,
current_time('mysql')
));
}
// Ajouter à une liste MailChimp
add_action('yawc_after_newsletter_subscribe', 'yawc_sync_to_mailchimp', 20);
function yawc_sync_to_mailchimp($email) {
// Intégration avec l'API MailChimp
$api_key = get_option('yawc_mailchimp_api_key');
$list_id = get_option('yawc_mailchimp_list_id');
if (empty($api_key) || empty($list_id)) {
return;
}
// Appel API MailChimp...
}
Créer des filtres personnalisés
<?php
/**
* Système de templating avec filtres personnalisés
*/
class YAWC_Template_Engine {
public static function render($template_name, $data = array()) {
// Permettre la modification des données avant le rendu
$data = apply_filters('yawc_template_data', $data, $template_name);
$data = apply_filters("yawc_template_data_{$template_name}", $data);
// Obtenir le chemin du template
$template_path = self::get_template_path($template_name);
// Permettre la modification du chemin
$template_path = apply_filters('yawc_template_path', $template_path, $template_name);
if (!file_exists($template_path)) {
return '';
}
// Extraire les variables
extract($data);
// Commencer le buffer
ob_start();
// Hook avant le rendu
do_action('yawc_before_template_render', $template_name, $data);
// Inclure le template
include $template_path;
// Hook après le rendu
do_action('yawc_after_template_render', $template_name, $data);
// Récupérer le contenu
$output = ob_get_clean();
// Permettre la modification du output final
$output = apply_filters('yawc_template_output', $output, $template_name, $data);
return $output;
}
private static function get_template_path($template_name) {
return get_template_directory() . '/templates/' . $template_name . '.php';
}
}
/**
* Utilisation des filtres personnalisés
*/
// Ajouter des données globales à tous les templates
add_filter('yawc_template_data', 'yawc_add_global_template_data');
function yawc_add_global_template_data($data) {
$data['site_name'] = get_bloginfo('name');
$data['current_user'] = wp_get_current_user();
$data['is_logged_in'] = is_user_logged_in();
return $data;
}
// Modifier les données pour un template spécifique
add_filter('yawc_template_data_product-card', 'yawc_enrich_product_data');
function yawc_enrich_product_data($data) {
if (isset($data['product_id'])) {
$data['product_reviews'] = yawc_get_product_reviews($data['product_id']);
$data['related_products'] = yawc_get_related_products($data['product_id']);
}
return $data;
}
// Minifier le HTML en sortie
add_filter('yawc_template_output', 'yawc_minify_html', 999);
function yawc_minify_html($output) {
if (!is_admin() && !current_user_can('edit_posts')) {
$output = preg_replace('/\s+/', ' ', $output);
$output = preg_replace('/>\s+</', '><', $output);
}
return $output;
}
Plugin avec architecture complète de hooks
<?php
/**
* Plugin: YAWC Events Manager
* Démonstration d'une architecture complète avec hooks
*/
class YAWC_Events_Manager {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
}
private function init_hooks() {
add_action('init', array($this, 'register_post_type'));
add_action('save_post_event', array($this, 'save_event_meta'), 10, 2);
}
public function register_post_type() {
$labels = apply_filters('yawc_event_post_type_labels', array(
'name' => 'Événements',
'singular_name' => 'Événement',
'add_new' => 'Ajouter un événement',
'add_new_item' => 'Ajouter un nouvel événement',
'edit_item' => 'Modifier l\'événement',
));
$args = array(
'labels' => $labels,
'public' => true,
'has_archive' => true,
'supports' => array('title', 'editor', 'thumbnail'),
'rewrite' => array('slug' => 'evenements'),
);
$args = apply_filters('yawc_event_post_type_args', $args);
register_post_type('event', $args);
}
public function save_event_meta($post_id, $post) {
// Hook avant la sauvegarde
do_action('yawc_before_save_event_meta', $post_id, $post);
$meta_fields = array('event_date', 'event_location', 'event_price');
foreach ($meta_fields as $field) {
if (isset($_POST[$field])) {
$value = $_POST[$field];
// Permettre le filtrage de chaque valeur
$value = apply_filters("yawc_event_meta_{$field}", $value, $post_id);
$value = apply_filters('yawc_event_meta_value', $value, $field, $post_id);
update_post_meta($post_id, '_' . $field, $value);
}
}
// Hook après la sauvegarde
do_action('yawc_after_save_event_meta', $post_id, $post);
}
/**
* Obtenir les événements à venir
*/
public function get_upcoming_events($limit = 10) {
$args = array(
'post_type' => 'event',
'posts_per_page' => $limit,
'meta_key' => '_event_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => '_event_date',
'value' => date('Y-m-d'),
'compare' => '>=',
'type' => 'DATE',
),
),
);
// Permettre la modification de la requête
$args = apply_filters('yawc_upcoming_events_query_args', $args);
$query = new WP_Query($args);
// Permettre la modification des résultats
$events = apply_filters('yawc_upcoming_events', $query->posts, $query);
return $events;
}
}
// Initialiser le plugin
YAWC_Events_Manager::get_instance();
/**
* Exemples d'utilisation des hooks par des extensions
*/
// Extension 1: Ajouter un champ personnalisé
add_filter('yawc_event_post_type_args', 'yawc_add_event_custom_fields');
function yawc_add_event_custom_fields($args) {
$args['supports'][] = 'custom-fields';
return $args;
}
// Extension 2: Valider le prix
add_filter('yawc_event_meta_event_price', 'yawc_validate_event_price', 10, 2);
function yawc_validate_event_price($value, $post_id) {
$value = floatval($value);
return max(0, $value); // S'assurer que le prix n'est pas négatif
}
// Extension 3: Envoyer une notification
add_action('yawc_after_save_event_meta', 'yawc_notify_new_event', 10, 2);
function yawc_notify_new_event($post_id, $post) {
if ($post->post_status === 'publish' && !get_post_meta($post_id, '_notification_sent', true)) {
// Envoyer des notifications aux abonnés
yawc_send_event_notifications($post_id);
// Marquer comme envoyé
update_post_meta($post_id, '_notification_sent', true);
}
}
6. Priorités et ordre d’exécution
Comprendre les priorités
<?php
/**
* La priorité détermine l'ordre d'exécution
* Priorité par défaut: 10
* Plus le nombre est bas, plus tôt la fonction s'exécute
*/
// Ces fonctions s'exécuteront dans cet ordre:
add_action('init', 'fonction_priorite_5', 5); // 1er
add_action('init', 'fonction_priorite_10', 10); // 2ème (défaut)
add_action('init', 'fonction_priorite_10_bis', 10); // 3ème (même priorité, ordre d'ajout)
add_action('init', 'fonction_priorite_15', 15); // 4ème
add_action('init', 'fonction_priorite_100', 100); // 5ème (dernier)
function fonction_priorite_5() {
echo "1. Priorité 5\n";
}
function fonction_priorite_10() {
echo "2. Priorité 10 (première)\n";
}
function fonction_priorite_10_bis() {
echo "3. Priorité 10 (deuxième)\n";
}
function fonction_priorite_15() {
echo "4. Priorité 15\n";
}
function fonction_priorite_100() {
echo "5. Priorité 100\n";
}
Cas pratiques de gestion des priorités
<?php
/**
* Exemple: Chaîne de filtres avec priorités
*/
// Filtre de base
add_filter('the_title', 'yawc_uppercase_title', 10);
function yawc_uppercase_title($title) {
return strtoupper($title);
}
// Ce filtre s'exécute AVANT (priorité plus basse)
add_filter('the_title', 'yawc_add_prefix', 5);
function yawc_add_prefix($title) {
return '[ARTICLE] ' . $title;
}
// Ce filtre s'exécute APRÈS (priorité plus haute)
add_filter('the_title', 'yawc_add_suffix', 20);
function yawc_add_suffix($title) {
return $title . ' ✓';
}
// Résultat final: "[ARTICLE] MON TITRE ✓"
// Ordre d'exécution:
// 1. yawc_add_prefix (5): "[ARTICLE] Mon titre"
// 2. yawc_uppercase_title (10): "[ARTICLE] MON TITRE"
// 3. yawc_add_suffix (20): "[ARTICLE] MON TITRE ✓"
/**
* Surcharger un hook existant
*/
// Hook d'un plugin tiers (priorité 10)
add_filter('content_filter', 'other_plugin_function', 10);
// Pour modifier APRÈS le plugin tiers
add_filter('content_filter', 'yawc_modify_after', 20);
// Pour s'exécuter AVANT le plugin tiers
add_filter('content_filter', 'yawc_modify_before', 5);
// Pour désactiver complètement le hook du plugin tiers
remove_filter('content_filter', 'other_plugin_function', 10);
Gestion avancée avec nombre d’arguments
<?php
/**
* Le 4ème paramètre définit le nombre d'arguments acceptés
*/
// Hook WordPress avec plusieurs arguments
do_action('example_action', $arg1, $arg2, $arg3);
// Recevoir 1 seul argument (défaut)
add_action('example_action', 'fonction_1_arg', 10, 1);
function fonction_1_arg($arg1) {
// Accès uniquement à $arg1
}
// Recevoir 2 arguments
add_action('example_action', 'fonction_2_args', 10, 2);
function fonction_2_args($arg1, $arg2) {
// Accès à $arg1 et $arg2
}
// Recevoir tous les arguments
add_action('example_action', 'fonction_all_args', 10, 3);
function fonction_all_args($arg1, $arg2, $arg3) {
// Accès à tous les arguments
}
/**
* Exemple concret: Modification de requête
*/
add_action('pre_get_posts', 'yawc_modify_search', 10, 1);
function yawc_modify_search($query) {
// $query est le seul argument nécessaire
if ($query->is_search() && !is_admin()) {
$query->set('posts_per_page', 20);
}
}
/**
* Exemple: Callback de sauvegarde avec arguments multiples
*/
add_action('save_post', 'yawc_save_handler', 10, 3);
function yawc_save_handler($post_id, $post, $update) {
// 3 arguments sont disponibles
if ($update) {
// C'est une mise à jour, pas une nouvelle publication
yawc_log_update($post_id, $post);
}
}
7. Hooks conditionnels et contextuels
Exécution conditionnelle
<?php
/**
* N'exécuter des hooks que dans certaines conditions
*/
// Seulement sur la page d'accueil
add_action('wp_footer', 'yawc_homepage_footer');
function yawc_homepage_footer() {
if (!is_front_page()) {
return;
}
echo '<div class="homepage-cta">Découvrez nos services !</div>';
}
// Seulement pour les articles d'une certaine catégorie
add_filter('the_content', 'yawc_add_category_disclaimer');
function yawc_add_category_disclaimer($content) {
if (!is_single() || !in_category('tutoriels')) {
return $content;
}
$disclaimer = '<div class="tutorial-disclaimer">';
$disclaimer .= '<strong>Note:</strong> Ce tutoriel nécessite des connaissances de base en PHP.';
$disclaimer .= '</div>';
return $disclaimer . $content;
}
// Seulement pour certains rôles utilisateur
add_action('admin_notices', 'yawc_admin_notice_for_editors');
function yawc_admin_notice_for_editors() {
$user = wp_get_current_user();
if (!in_array('editor', $user->roles)) {
return;
}
?>
<div class="notice notice-info">
<p>Rappel: N'oubliez pas de vérifier l'orthographe avant de publier.</p>
</div>
<?php
}
// Seulement sur mobile
add_action('wp_footer', 'yawc_mobile_download_app');
function yawc_mobile_download_app() {
if (!wp_is_mobile()) {
return;
}
?>
<div class="mobile-app-banner">
<p>Téléchargez notre application mobile pour une meilleure expérience !</p>
</div>
<?php
}
Hooks dynamiques
<?php
/**
* Créer des hooks dynamiques basés sur le contexte
*/
class YAWC_Dynamic_Hooks {
public static function init() {
// Hook générique
add_action('yawc_process_form', array(__CLASS__, 'process'), 10, 2);
}
public static function process($form_id, $data) {
// Hook spécifique au formulaire
do_action("yawc_process_form_{$form_id}", $data);
// Hook basé sur le type de données
if (isset($data['type'])) {
do_action("yawc_process_form_type_{$data['type']}", $form_id, $data);
}
// Hook basé sur l'utilisateur
if (is_user_logged_in()) {
$user = wp_get_current_user();
do_action("yawc_process_form_user_{$user->ID}", $form_id, $data);
foreach ($user->roles as $role) {
do_action("yawc_process_form_role_{$role}", $form_id, $data);
}
}
}
}
YAWC_Dynamic_Hooks::init();
/**
* Utilisation des hooks dynamiques
*/
// Hook pour un formulaire spécifique
add_action('yawc_process_form_contact', 'yawc_handle_contact_form');
function yawc_handle_contact_form($data) {
// Logique spécifique au formulaire de contact
wp_mail(get_option('admin_email'), 'Nouveau contact', print_r($data, true));
}
// Hook pour un type de données
add_action('yawc_process_form_type_newsletter', 'yawc_handle_newsletter_signup');
function yawc_handle_newsletter_signup($form_id, $data) {
// Ajouter à la liste de newsletter
yawc_add_to_newsletter($data['email']);
}
// Hook pour un rôle spécifique
add_action('yawc_process_form_role_subscriber', 'yawc_subscriber_form_bonus');
function yawc_subscriber_form_bonus($form_id, $data) {
// Bonus pour les abonnés
yawc_add_loyalty_points(get_current_user_id(), 10);
}
Hooks avec taxonomies et post types
<?php
/**
* Hooks spécifiques aux post types personnalisés
*/
// WordPress fournit des hooks dynamiques pour chaque post type
add_action('save_post_livre', 'yawc_save_book', 10, 3);
add_action('save_post_film', 'yawc_save_movie', 10, 3);
function yawc_save_book($post_id, $post, $update) {
// Logique spécifique aux livres
if (!$update) {
// Nouveau livre
yawc_notify_book_editors($post_id);
}
}
function yawc_save_movie($post_id, $post, $update) {
// Logique spécifique aux films
yawc_update_imdb_rating($post_id);
}
/**
* Filtres sur les requêtes de taxonomie
*/
add_action('create_genre', 'yawc_new_genre_created', 10, 2);
function yawc_new_genre_created($term_id, $tt_id) {
// Un nouveau genre a été créé
yawc_rebuild_genre_cache();
}
/**
* Hooks sur les métadonnées
*/
add_action('added_post_meta', 'yawc_meta_added', 10, 4);
function yawc_meta_added($meta_id, $post_id, $meta_key, $meta_value) {
// Réagir à l'ajout de métadonnées spécifiques
if ($meta_key === '_featured_book') {
yawc_update_featured_books_list();
}
}
8. Performance et optimisation
Éviter les hooks coûteux
<?php
/**
* MAUVAIS: Hook exécuté à chaque fois
*/
add_action('the_post', 'yawc_bad_expensive_operation');
function yawc_bad_expensive_operation($post) {
// Cette requête s'exécute pour CHAQUE post dans une boucle !
$related_posts = get_posts(array(
'category__in' => wp_get_post_categories($post->ID),
'post__not_in' => array($post->ID),
'posts_per_page' => 5,
));
// Traitement coûteux...
}
/**
* BON: Hook avec mise en cache
*/
add_action('the_post', 'yawc_good_expensive_operation');
function yawc_good_expensive_operation($post) {
// Vérifier le cache d'abord
$cache_key = 'related_posts_' . $post->ID;
$related_posts = wp_cache_get($cache_key);
if ($related_posts === false) {
// Seulement si pas en cache
$related_posts = get_posts(array(
'category__in' => wp_get_post_categories($post->ID),
'post__not_in' => array($post->ID),
'posts_per_page' => 5,
));
// Mettre en cache pour 1 heure
wp_cache_set($cache_key, $related_posts, '', 3600);
}
// Utiliser $related_posts...
}
/**
* MEILLEUR: Hook conditionnel
*/
add_action('the_post', 'yawc_best_expensive_operation');
function yawc_best_expensive_operation($post) {
// N'exécuter que si nécessaire
if (!is_single() || is_admin()) {
return;
}
// Utiliser un transient pour un cache persistant
$cache_key = 'related_posts_' . $post->ID;
$related_posts = get_transient($cache_key);
if ($related_posts === false) {
$related_posts = get_posts(array(
'category__in' => wp_get_post_categories($post->ID),
'post__not_in' => array($post->ID),
'posts_per_page' => 5,
'fields' => 'ids', // Récupérer seulement les IDs
'no_found_rows' => true, // Pas besoin de pagination
));
set_transient($cache_key, $related_posts, HOUR_IN_SECONDS);
}
}
/**
* Nettoyer le cache quand nécessaire
*/
add_action('save_post', 'yawc_clear_related_posts_cache');
function yawc_clear_related_posts_cache($post_id) {
$cache_key = 'related_posts_' . $post_id;
delete_transient($cache_key);
// Nettoyer aussi les caches des posts de la même catégorie
$categories = wp_get_post_categories($post_id);
foreach ($categories as $cat_id) {
delete_transient('category_posts_' . $cat_id);
}
}
Limiter l’utilisation des hooks
<?php
/**
* Retirer des hooks quand ils ne sont plus nécessaires
*/
class YAWC_Conditional_Hook {
private static $hook_added = false;
public static function maybe_add_hook() {
// Ajouter le hook seulement une fois et seulement si nécessaire
if (!self::$hook_added && self::should_run()) {
add_filter('the_content', array(__CLASS__, 'modify_content'));
self::$hook_added = true;
}
}
private static function should_run() {
return is_single() && in_category('premium');
}
public static function modify_content($content) {
// Après la première exécution, retirer le hook
remove_filter('the_content', array(__CLASS__, 'modify_content'));
return self::add_premium_content($content);
}
private static function add_premium_content($content) {
return '<div class="premium-badge">Contenu Premium</div>' . $content;
}
}
// Initialiser conditionnellement
if (is_single()) {
YAWC_Conditional_Hook::maybe_add_hook();
}
Débouncing et throttling
<?php
/**
* Limiter la fréquence d'exécution d'un hook coûteux
*/
class YAWC_Hook_Throttle {
private static $last_run = array();
/**
* N'exécuter qu'une fois par période définie
*/
public static function throttle($hook_name, $callback, $wait_seconds = 60) {
$current_time = time();
if (!isset(self::$last_run[$hook_name]) ||
($current_time - self::$last_run[$hook_name]) >= $wait_seconds) {
call_user_func($callback);
self::$last_run[$hook_name] = $current_time;
return true;
}
return false;
}
}
/**
* Utilisation
*/
add_action('save_post', 'yawc_rebuild_cache_throttled');
function yawc_rebuild_cache_throttled($post_id) {
// Ne rebuild le cache que toutes les 5 minutes maximum
YAWC_Hook_Throttle::throttle('rebuild_cache', function() {
yawc_rebuild_entire_cache();
}, 300);
}
/**
* Debouncing: Attendre que l'activité se calme
*/
class YAWC_Hook_Debounce {
private static $timers = array();
public static function debounce($hook_name, $callback, $delay_seconds = 5) {
// Annuler le timer précédent s'il existe
if (isset(self::$timers[$hook_name])) {
wp_clear_scheduled_hook('yawc_debounced_' . $hook_name);
}
// Programmer une nouvelle exécution
wp_schedule_single_event(
time() + $delay_seconds,
'yawc_debounced_' . $hook_name
);
// Enregistrer le callback
if (!has_action('yawc_debounced_' . $hook_name)) {
add_action('yawc_debounced_' . $hook_name, $callback);
}
self::$timers[$hook_name] = time();
}
}
/**
* Utilisation: N'exécuter qu'après 10 secondes d'inactivité
*/
add_action('wp_ajax_autosave', 'yawc_autosave_debounced');
function yawc_autosave_debounced() {
YAWC_Hook_Debounce::debounce('autosave', function() {
// Cette fonction ne s'exécutera que 10 secondes après
// la dernière tentative de sauvegarde automatique
yawc_perform_heavy_autosave_operations();
}, 10);
}
9. Debugging des hooks
Lister tous les hooks actifs
<?php
/**
* Afficher tous les hooks enregistrés pour un tag donné
*/
function yawc_list_hooks($tag = '') {
global $wp_filter;
if (empty($tag)) {
echo '<h2>Tous les hooks WordPress</h2>';
foreach ($wp_filter as $hook_name => $hook) {
yawc_display_hook_details($hook_name, $hook);
}
} else {
if (isset($wp_filter[$tag])) {
yawc_display_hook_details($tag, $wp_filter[$tag]);
} else {
echo "<p>Aucun hook trouvé pour: $tag</p>";
}
}
}
function yawc_display_hook_details($hook_name, $hook) {
echo "<h3>$hook_name</h3>";
echo '<table border="1" cellpadding="5">';
echo '<tr><th>Priorité</th><th>Fonction</th><th>Arguments acceptés</th></tr>';
foreach ($hook->callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
$function_name = yawc_get_callback_name($callback['function']);
echo '<tr>';
echo "<td>$priority</td>";
echo "<td>$function_name</td>";
echo "<td>{$callback['accepted_args']}</td>";
echo '</tr>';
}
}
echo '</table><br>';
}
function yawc_get_callback_name($callback) {
if (is_string($callback)) {
return $callback;
} elseif (is_array($callback)) {
if (is_object($callback[0])) {
return get_class($callback[0]) . '->' . $callback[1];
} else {
return $callback[0] . '::' . $callback[1];
}
} elseif ($callback instanceof Closure) {
return 'Closure';
} else {
return 'Callback inconnu';
}
}
// Utilisation en mode debug
if (WP_DEBUG && current_user_can('manage_options')) {
add_action('wp_footer', function() {
echo '<div style="background: #f0f0f0; padding: 20px; margin: 20px;">';
yawc_list_hooks('wp_enqueue_scripts');
echo '</div>';
});
}
Logger l’exécution des hooks
<?php
/**
* Logger toutes les exécutions de hooks
*/
class YAWC_Hook_Logger {
private static $logs = array();
private static $enabled = false;
public static function enable() {
if (self::$enabled) {
return;
}
self::$enabled = true;
// Intercepter tous les hooks
add_filter('all', array(__CLASS__, 'log_hook'), 1);
}
public static function log_hook($tag) {
if (!self::$enabled) {
return $tag;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
// Déterminer si c'est une action ou un filtre
$type = 'unknown';
foreach ($backtrace as $trace) {
if (isset($trace['function'])) {
if (in_array($trace['function'], array('do_action', 'do_action_ref_array'))) {
$type = 'action';
break;
} elseif (in_array($trace['function'], array('apply_filters', 'apply_filters_ref_array'))) {
$type = 'filter';
break;
}
}
}
self::$logs[] = array(
'tag' => $tag,
'type' => $type,
'time' => microtime(true),
'memory' => memory_get_usage(),
);
return $tag;
}
public static function get_logs() {
return self::$logs;
}
public static function display_logs() {
if (empty(self::$logs)) {
echo '<p>Aucun log disponible.</p>';
return;
}
$start_time = self::$logs[0]['time'];
$start_memory = self::$logs[0]['memory'];
echo '<table border="1" cellpadding="5">';
echo '<tr><th>#</th><th>Tag</th><th>Type</th><th>Temps (ms)</th><th>Mémoire (KB)</th></tr>';
foreach (self::$logs as $index => $log) {
$time_diff = ($log['time'] - $start_time) * 1000;
$memory_diff = ($log['memory'] - $start_memory) / 1024;
printf(
'<tr><td>%d</td><td>%s</td><td>%s</td><td>%.2f</td><td>%.2f</td></tr>',
$index + 1,
esc_html($log['tag']),
esc_html($log['type']),
$time_diff,
$memory_diff
);
}
echo '</table>';
}
public static function save_to_file($filename = 'hook-logs.json') {
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['basedir'] . '/' . $filename;
file_put_contents($file_path, json_encode(self::$logs, JSON_PRETTY_PRINT));
return $file_path;
}
}
// Activer en mode debug
if (WP_DEBUG && isset($_GET['log_hooks'])) {
YAWC_Hook_Logger::enable();
add_action('shutdown', function() {
$file = YAWC_Hook_Logger::save_to_file();
error_log('Hooks logged to: ' . $file);
});
}
Debug d’un hook spécifique
<?php
/**
* Tracer l'exécution d'un hook particulier
*/
function yawc_trace_hook($tag) {
add_action($tag, function() use ($tag) {
$args = func_get_args();
echo '<div style="background: #fffacd; border: 2px solid #ffd700; padding: 10px; margin: 10px;">';
echo "<h4>Hook déclenché: $tag</h4>";
if (!empty($args)) {
echo '<pre>';
print_r($args);
echo '</pre>';
}
// Backtrace pour voir d'où vient l'appel
echo '<details><summary>Backtrace</summary><pre>';
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
echo '</pre></details>';
echo '</div>';
}, 1, 99); // Priorité 1 pour s'exécuter en premier, 99 args max
}
// Utilisation
if (current_user_can('manage_options') && isset($_GET['trace'])) {
yawc_trace_hook('save_post');
yawc_trace_hook('wp_enqueue_scripts');
yawc_trace_hook('the_content');
}
10. Cas d’usage avancés et patterns
Pattern: Hook Proxy
<?php
/**
* Créer un système de proxy pour intercepter et modifier les hooks
*/
class YAWC_Hook_Proxy {
private $original_tag;
private $proxy_tag;
private $middleware = array();
public function __construct($original_tag, $proxy_tag = null) {
$this->original_tag = $original_tag;
$this->proxy_tag = $proxy_tag ?: $original_tag . '_proxied';
$this->setup();
}
private function setup() {
// Intercepter le hook original
add_filter($this->original_tag, array($this, 'intercept'), 1, 99);
}
public function intercept() {
$args = func_get_args();
// Exécuter les middlewares
foreach ($this->middleware as $middleware) {
$args = call_user_func_array($middleware, $args);
if ($args === false) {
// Un middleware a arrêté l'exécution
return $args[0];
}
}
// Exécuter le hook proxifié
array_unshift($args, $this->proxy_tag);
return call_user_func_array('apply_filters', $args);
}
public function addMiddleware($callback) {
$this->middleware[] = $callback;
return $this;
}
}
/**
* Utilisation du proxy
*/
$content_proxy = new YAWC_Hook_Proxy('the_content');
// Ajouter un middleware de logging
$content_proxy->addMiddleware(function($content) {
error_log('Content length before filters: ' . strlen($content));
return func_get_args();
});
// Ajouter un middleware de sécurité
$content_proxy->addMiddleware(function($content) {
// Bloquer si le contenu contient certains mots
if (strpos($content, 'BLOCKED_WORD') !== false) {
error_log('Content blocked');
return false; // Arrête l'exécution
}
return func_get_args();
});
// Utiliser le hook proxifié
add_filter('the_content_proxied', function($content) {
return $content . '<p>Contenu ajouté via proxy</p>';
});
Pattern: Event Dispatcher
<?php
/**
* Système d'événements avancé basé sur les hooks
*/
class YAWC_Event_Dispatcher {
private static $instance = null;
private $listeners = array();
public static function instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Enregistrer un écouteur d'événement
*/
public function listen($event, $callback, $priority = 10) {
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = array();
}
$this->listeners[$event][] = array(
'callback' => $callback,
'priority' => $priority,
);
// Trier par priorité
usort($this->listeners[$event], function($a, $b) {
return $a['priority'] - $b['priority'];
});
return $this;
}
/**
* Déclencher un événement
*/
public function dispatch($event, $payload = array()) {
// Hook WordPress standard
do_action("yawc_event_{$event}", $payload);
// Système d'événements personnalisé
if (!isset($this->listeners[$event])) {
return null;
}
$result = null;
foreach ($this->listeners[$event] as $listener) {
$result = call_user_func($listener['callback'], $payload, $result);
// Permettre l'arrêt de la propagation
if ($result === false) {
break;
}
}
return $result;
}
/**
* Écouter un événement une seule fois
*/
public function once($event, $callback, $priority = 10) {
$wrapper = function($payload) use ($event, $callback, &$wrapper) {
// Retirer l'écouteur après exécution
$this->forget($event, $wrapper);
return call_user_func($callback, $payload);
};
return $this->listen($event, $wrapper, $priority);
}
/**
* Retirer un écouteur
*/
public function forget($event, $callback = null) {
if ($callback === null) {
unset($this->listeners[$event]);
return $this;
}
if (isset($this->listeners[$event])) {
$this->listeners[$event] = array_filter(
$this->listeners[$event],
function($listener) use ($callback) {
return $listener['callback'] !== $callback;
}
);
}
return $this;
}
}
/**
* Utilisation du Event Dispatcher
*/
$events = YAWC_Event_Dispatcher::instance();
// Écouter un événement personnalisé
$events->listen('user.registered', function($payload) {
$user = $payload['user'];
// Envoyer un email de bienvenue
wp_mail($user->user_email, 'Bienvenue !', 'Merci de vous être inscrit.');
});
$events->listen('user.registered', function($payload) {
// Ajouter des points de fidélité
yawc_add_loyalty_points($payload['user']->ID, 100);
}, 20);
// Écouter une seule fois
$events->once('first.purchase', function($payload) {
// Bonus uniquement pour le premier achat
yawc_send_first_purchase_bonus($payload['user_id']);
});
// Déclencher l'événement
function yawc_register_user($user_data) {
$user = wp_insert_user($user_data);
if (!is_wp_error($user)) {
YAWC_Event_Dispatcher::instance()->dispatch('user.registered', array(
'user' => get_userdata($user),
'timestamp' => current_time('timestamp'),
));
}
return $user;
}
Pattern: Hook Chain
<?php
/**
* Chaîner plusieurs transformations avec validation
*/
class YAWC_Filter_Chain {
private $value;
private $filters = array();
public function __construct($initial_value) {
$this->value = $initial_value;
}
/**
* Ajouter un filtre à la chaîne
*/
public function pipe($callback) {
$this->filters[] = $callback;
return $this;
}
/**
* Ajouter un filtre conditionnel
*/
public function when($condition, $callback) {
if (is_callable($condition)) {
$should_apply = call_user_func($condition, $this->value);
} else {
$should_apply = (bool) $condition;
}
if ($should_apply) {
$this->filters[] = $callback;
}
return $this;
}
/**
* Valider la valeur
*/
public function validate($callback, $error_message = 'Validation failed') {
$this->filters[] = function($value) use ($callback, $error_message) {
if (!call_user_func($callback, $value)) {
throw new Exception($error_message);
}
return $value;
};
return $this;
}
/**
* Exécuter la chaîne et retourner le résultat
*/
public function get() {
$value = $this->value;
try {
foreach ($this->filters as $filter) {
$value = call_user_func($filter, $value);
}
} catch (Exception $e) {
error_log('Filter chain error: ' . $e->getMessage());
return $this->value; // Retourner la valeur originale en cas d'erreur
}
return $value;
}
}
/**
* Utilisation de la chaîne de filtres
*/
$processed_content = (new YAWC_Filter_Chain($raw_content))
->pipe('trim')
->pipe('strip_tags')
->validate(function($value) {
return strlen($value) > 10;
}, 'Content too short')
->pipe(function($content) {
return wpautop($content);
})
->when(is_user_logged_in(), function($content) {
return $content . '<p>Contenu premium pour membres</p>';
})
->pipe(function($content) {
return apply_filters('my_custom_content_filter', $content);
})
->get();
Conclusion
Le système de hooks WordPress est l’un des patterns architecturaux les plus puissants et élégants du CMS. Maîtriser actions et filtres vous permet de:
- Étendre WordPress sans modifier le core
- Créer des plugins modulaires et maintenables
- Intégrer facilement des fonctionnalités tierces
- Construire des architectures event-driven sophistiquées
- Optimiser les performances avec un contrôle fin de l’exécution
Récapitulatif des bonnes pratiques
- Nommer clairement vos hooks personnalisés avec un préfixe unique
- Toujours échapper et valider les données dans vos callbacks
- Utiliser les priorités judicieusement pour contrôler l’ordre d’exécution
- Optimiser avec des conditions pour éviter l’exécution inutile
- Documenter vos hooks pour faciliter leur utilisation
- Nettoyer après vous en retirant les hooks quand nécessaire
- Utiliser la mise en cache pour les opérations coûteuses
- Tester l’impact performance de vos hooks
Ressources complémentaires
- Plugin API Reference – WordPress Codex
- Adam Brown’s Hook Database
- Query Monitor plugin pour debugging
- Developer Tools dans le WordPress Dashboard
Le système de hooks est au cœur de l’écosystème WordPress. En les maîtrisant, vous rejoignez la communauté de développeurs qui construit collaborativement l’un des CMS les plus extensibles au monde.