Intermédiaire 34 min de lecture · 7 441 mots

WordPress Hooks : Actions et Filtres expliqués avec exemples

WordPress Hooks : Actions et Filtres expliqués avec exemples
Intermédiaire · 2026.05.09

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

  1. Architecture du système de hooks
  2. Actions vs Filtres : comprendre la différence
  3. Utiliser les actions existantes
  4. Utiliser les filtres existants
  5. Créer vos propres hooks personnalisés
  6. Priorités et ordre d’exécution
  7. Hooks conditionnels et contextuels
  8. Performance et optimisation
  9. Debugging des hooks
  10. 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

  1. Nommer clairement vos hooks personnalisés avec un préfixe unique
  2. Toujours échapper et valider les données dans vos callbacks
  3. Utiliser les priorités judicieusement pour contrôler l’ordre d’exécution
  4. Optimiser avec des conditions pour éviter l’exécution inutile
  5. Documenter vos hooks pour faciliter leur utilisation
  6. Nettoyer après vous en retirant les hooks quand nécessaire
  7. Utiliser la mise en cache pour les opérations coûteuses
  8. 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.

À propos de l'auteur

Pascal

Pascal — SRE & Observability Engineer. J'applique les pratiques SRE (SLI/SLO, alerting, observabilité) à mon homelab Proxmox, avec les mêmes exigences qu'en production.

Une remarque, un retour ?

Cet article est vivant - corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisis celui qui te convient.

Laisser un commentaire