2026年7月14日

2026年7月14日

WordPressのフック・フィルターが動作しない問題を解決する方法

はじめに

WordPressでadd_action()add_filter()でフックを登録してもコールバックが実行されない・remove_action()remove_filter()を呼んでも対象のフックが削除されない・フックの優先順位の数値が大きいほど後に実行されることを忘れてタイミングがずれる・クロージャー(無名関数)で登録したフックは後から削除できないといった問題は、WordPressのフックシステムの仕組みを正しく理解することで解決できます。

症状・原因

  • add_action()のコールバックを登録した時点でそのフックはすでに実行済みの場合(フックのタイミングが遅すぎる)
  • remove_action()の優先順位引数(第3引数)がadd_action()で指定した優先順位と一致していない
  • クラスメソッドを[$this, 'method']形式で登録したが削除時に異なる形式を使っている
  • add_filter()のコールバックが値をreturnせずnullを返している

解決手順

ステップ1:フックの状態を診断する

# フックが実行済みか確認
wp eval "var_dump(did_action('init'));"
wp eval "var_dump(did_action('wp_head'));"

# 特定フックに登録されているコールバックを確認
wp eval "
global \$wp_filter;
if (isset(\$wp_filter['save_post'])) {
    foreach (\$wp_filter['save_post']->callbacks as \$priority => \$callbacks) {
        foreach (\$callbacks as \$id => \$cb) {
            echo 'Priority ' . \$priority . ': ' . \$id . PHP_EOL;
        }
    }
}
"

# 現在実行中のフックを確認
wp eval "echo current_filter();"

# フィルターの返り値を確認
wp eval "echo apply_filters('the_title', 'テストタイトル', 0);"

ステップ2:フックを正しく登録・削除する

// add_action / add_filter の正しい使い方
add_action('init',          'my_init_callback',    10);     // 優先度10(デフォルト)
add_action('init',          'my_init_callback_2',  20);     // 優先度20 → 後に実行
add_action('wp_head',       'my_head_callback',    5);      // 優先度5 → 早く実行

// フィルターは必ず値を return する
add_filter('the_title', function(string $title, int $id): string {
    if (get_post_type($id) === 'product') {
        return '【商品】' . $title;
    }
    return $title;  // 必ずreturn!
}, 10, 2);  // 第4引数: コールバックが受け取る引数の数

// 名前付き関数でフックを登録(後で削除できる)
function my_custom_title_filter(string $title): string {
    return $title . ' | ' . get_bloginfo('name');
}
add_filter('the_title', 'my_custom_title_filter', 20);

// 削除(優先度を一致させる)
remove_filter('the_title', 'my_custom_title_filter', 20);

ステップ3:クラスメソッドのフックを正しく管理する

class My_Plugin {
    private static ?self $instance = null;

    public static function get_instance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function init_hooks(): void {
        add_action('the_content', [$this, 'filter_content'], 10);
        add_action('save_post',   [$this, 'on_save_post'],   10);
    }

    public function filter_content(string $content): string {
        return $content . '<p>追加コンテンツ</p>';
    }

    public function on_save_post(int $post_id): void {
        // 保存時の処理
    }

    // フックを削除する場合は同じオブジェクトインスタンスを使う
    public function remove_hooks(): void {
        remove_action('the_content', [$this, 'filter_content'], 10);
        remove_action('save_post',   [$this, 'on_save_post'],   10);
    }
}

// シングルトンで初期化
$plugin = My_Plugin::get_instance();
$plugin->init_hooks();

// 後から削除する場合
// My_Plugin::get_instance()->remove_hooks();

ステップ4:フックのタイミングを正しく理解する

// フックの実行タイミング(おおよその順序)
// muplugins_loaded → plugins_loaded → setup_theme → after_setup_theme
// → init → wp_loaded → template_redirect → wp_head → the_post
// → the_content → wp_footer

// すでに実行済みのフックに追加しても動かない
add_action('plugins_loaded', function(): void {
    // ここで init フックに登録するのは正しい(initはまだ)
    add_action('init', 'my_function');
});

// フックが実行済みかチェックして即実行
function my_plugin_init(): void {
    if (did_action('init')) {
        // init はすでに実行済み → 直接実行
        my_setup_function();
    } else {
        // まだ → フックに登録
        add_action('init', 'my_setup_function');
    }
}

// フックの実行回数を確認
$count = did_action('save_post');  // 実行された回数(0なら未実行)

// 一度だけ実行するフック
add_action('init', function(): void {
    static $done = false;
    if ($done) {
        return;
    }
    $done = true;
    // 処理...
});

ステップ5:フィルターチェーンのデバッグ

// フィルターチェーンをデバッグ(全コールバックの前後で値を記録)
add_filter('the_content', function(string $content): string {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('the_content before all filters: ' . mb_substr($content, 0, 100));
    }
    return $content;
}, PHP_INT_MIN);  // 最初に実行

add_filter('the_content', function(string $content): string {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('the_content after all filters: ' . mb_substr($content, 0, 100));
    }
    return $content;
}, PHP_INT_MAX);  // 最後に実行

// フィルターを一時的に全て無効化してデバッグ
remove_all_filters('the_content');

// 特定のフックに何が登録されているか確認するユーティリティ
function debug_hook(string $hook_name): void {
    global $wp_filter;
    if (!isset($wp_filter[$hook_name])) {
        echo "フック '{$hook_name}' にコールバックなし" . PHP_EOL;
        return;
    }
    foreach ($wp_filter[$hook_name]->callbacks as $priority => $cbs) {
        foreach ($cbs as $cb) {
            $name = is_array($cb['function'])
                ? get_class($cb['function'][0]) . '::' . $cb['function'][1]
                : (is_string($cb['function']) ? $cb['function'] : '[closure]');
            echo "Priority {$priority}: {$name}" . PHP_EOL;
        }
    }
}

注意事項

  • remove_action()remove_filter()add_action()add_filter()に、かつ同じ優先度を指定して呼ぶ必要があります。優先度が一致しないと削除されません
  • クロージャー(無名関数)で登録したフックは後から削除できません。後で削除が必要なコールバックは必ず名前付き関数またはクラスメソッドとして登録してください

まとめ

WordPress フック・フィルター問題の解決は①did_action()でフックの実行済み確認・$wp_filterグローバルで登録コールバックを確認・current_filter()で現在のフック名を確認、②add_filterのコールバックは必ず値をreturnする・第4引数でコールバックが受け取る引数の数を指定・remove_action/filterは同じ優先度で呼ぶ、③クラスメソッドは[$this, 'method']形式で登録・削除は同じオブジェクトインスタンスで同じ優先度・シングルトンで管理すると削除が容易、④did_action()でフックが実行済みか確認して即実行orフック登録を切り替え・フックの実行順序を把握してタイミングを設計、⑤PHP_INT_MIN/MAX優先度でフィルターの前後を記録してデバッグ・remove_all_filters()で一時的に無効化・debug_hook()ユーティリティで登録状況を可視化の手順で解決します。

お気軽にご相談ください

お見積りへ お問い合わせへ