Удаление хуков в WordPress (событий или фильтров)

Чтобы удалить хук, нужно изменить исходный код и удалить вызов хука, обычно это сделать невозможно, потому что хуки находятся в ядре или в плагине. Здесь мы поговорим о том, как можно удалить прикрепленные к хуку коллбэк-функции.

Для удаления функции привязанной к фильтру/событию, используется одна из функций:

Для справки. Это полностью одинаковые функции и обе они удаляют хук, а фильтр это или событие, в данном случае, значения не имеет. Так например, событии и фильтр мы можем удалить используя только функцию remove_filter() или только remove_action(). Но для понятности кода, все же лучше фильтры удалять с помощью remove_filter(), а события с помощью remove_action().

Для удаления хука нужно знать:

  1. Название хука, например wp_footer;
  2. Название прикрепленной функции, например my_action_function из примера выше;
  3. Приоритет выполнения хука, если он был установлен при создании хука. Если при создании приоритет не был установлен, то он равен 10 и при удалении его указывать не обязательно.

    Если нужно удалить хук с приоритетом отличным от 10 и вы его не указали, то хук НЕ будет удален!

Пример удаления хука

Допустим где-то в плагине, к событию wp_footer прикреплена функция my_action_function, которая выводит текст в подвале темы:

add_action( 'wp_footer', 'my_action_function' );

function my_action_function( $text ){
	echo 'Это текст в подвале!';
}

В теме нам нужно удалить это событие, чтобы текст в подвале не выводился. Для этого в файле темы functions.php можно прописать такой код:

remove_action( 'wp_footer', 'my_action_function' );

Важный момент: удалять хук нужно после того как он добавлен, но еще не отработал.

Еще пример удаления хука

Демонстрация добавления и удаления хука:

// добавляем функцию к событию my_action
add_action( 'my_action', 'my_action_function' );

function my_action_function( $text ){
	echo 'Привет!';
}

// создаем событие
do_action( 'my_action' ); //> Привет!

// удаляем добавленное ранее событие
remove_action( 'my_action', 'my_action_function' );

// создаем событие еще раз
do_action( 'my_action' ); // ничего не выведет...

Удаление с учетом приоритета:

Приоритет должен совпадать с тем который был задан при добавлении хука.

// если так добавлен
add_filter( 'my_filter', 'function_name', 99 );

// то так нужно удалять
remove_filter( 'my_filter', 'function_name', 99 );

Удаление хука статического метода класса:

// так добавлен
add_filter( 'my_filter', [ 'My_Class', 'static_method_name' ], 15 );

// так нужно удалять
remove_filter( 'my_filter', [ 'My_Class', 'static_method_name' ], 15 );

Удаление хука не статического метода класса (если ЕСТЬ доступ к $this - экземпляру класса):

class A {

	function __construct(){
		add_action( 'my_action', [ $this, 'method_name' ], 15 );
	}

	function method_name(){
		echo 'Привет!';
	}
}

$class = new A(); // экземпляр

// для удаления нужно найти переменную в которую был сохранен
// экземпляр класса при создании, сейчас это $class
remove_action( 'my_action', [ $class, 'method_name' ], 15 );

Удаление хука не статического метода класса (если НЕТ доступа к $this - экземпляру класса):

Удалить хук для объекта класса к которому у нет доступа, стандартными функциями WP невозможно. Но сделать это можно, используя следующие кастомные функции:

/**
 * Remove filter without access to class object (instance).
 *
 * In order to use the core WordPress remove_filter() on a filter added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method. This function allows you to remove filters with a callback to a class
 * you don't have access to.
 *
 * @param string       $hook_name          Filter to remove.
 * @param string|array $class_method_name  Class and Method for the filter's callback.
 *                                         Eg: [ '\Space\My_Class', 'my_method' ] OR '\Space\My_Class::my_method'.
 * @param int          $priority           Priority of the filter (default 10).
 *
 * @return bool Whether the hook is removed.
 *
 * @requires WP 4.7+
 * @author  Kama (wp-kama.com)
 * @version 1.0
 */
function remove_object_filter( string $hook_name, $class_method_name, $priority = 10 ): bool {
	global $wp_filter;

	if( empty( $wp_filter[ $hook_name ]->callbacks[ $priority ] ) ){
		return false;
	}

	$wp_hooks = & $wp_filter[ $hook_name ];
	$hooks = $wp_hooks->callbacks[ $priority ];

	[ $class_name, $method_name ] = is_string( $class_method_name )
		? explode( '::', $class_method_name ) + [ '', '' ] // '\Space\My_Class::my_method'
		: $class_method_name;

	$class_name = ltrim( $class_name, '\\' ); //> \Space\My_Class >>> Space\My_Class

	foreach( $hooks as $hook ){

		if( ! isset( $hook['function'] ) || ! is_array( $hook['function'] ) ){
			continue;
		}

		[ $object, $current_method ] = $hook['function'];

		if( $current_method !== $method_name ){
			continue;
		}

		$is_our_object = is_object( $object ) && get_class( $object ) === $class_name;

		if( ! $is_our_object ){
			continue;
		}

		return $wp_hooks->remove_filter( $hook_name, $hook['function'], $priority );
	}

	return false;
}

/**
 * Remove Class Action Without Access to Class Object
 *
 * @param string       $hook_name          Action to remove.
 * @param string|array $class_method_name  Class and Method for the filter's callback.
 *                                         Eg: [ '\Space\My_Class', 'my_method' ] OR '\Space\My_Class::my_method'.
 * @param int          $priority           Priority of the action (default 10)
 *
 * @return bool Whether the hook is removed.
 */
function remove_object_action( $hook_name, $class_method_name, $priority = 10 ): bool {
	return remove_object_filter( $hook_name, $class_method_name, $priority );
}

Пример использования.

Допустим у нас есть такой класс:

namespace My\Space;

class MyClass {

	public function __construct() {
		add_action( 'some_action_hook', [ $this, 'my_method' ], 11 );
		add_filter( 'some_filter_hook', [ $this, 'my_method' ], 11 );
	}

	public function my_method() {
		die( 'my_method trigered: '. current_filter() );
	}
}

Мы инициализировали класс, в результате чего добавилось 2 хука:

new \My\Space\MyClass();

Теперь, удалить хуки этого класса, через стандартные функции WP будет невозможно, потому что для удаления хука, нам нужно иметь доступ к конкретному экземпляру класса, а его у нас нет. Но благодаря функциям выше, мы можем это сделать, просто указав название класса, метода и приоритет.

Рассмотрим все варианты как можно вызвать функцию, чтобы удалить хук:

remove_object_action( 'some_action_hook', [ 'My\Space\MyClass', 'my_method' ], 11 );
remove_object_action( 'some_action_hook', [ '\My\Space\MyClass', 'my_method' ], 11 );
remove_object_action( 'some_action_hook', [ '\\My\\Space\\MyClass', 'my_method' ], 11 );
remove_object_action( 'some_action_hook', [ \My\Space\MyClass::class, 'my_method' ], 11 );
remove_object_action( 'some_action_hook', '\My\Space\MyClass::my_method', 11 );
remove_object_action( 'some_action_hook', 'My\Space\MyClass::my_method', 11 );

// или аналогично для фильтров
remove_object_filter( 'some_filter_hook', [ 'My\Space\MyClass', 'my_method' ], 11 );
// etc...

Удаление хука добавленного анонимной функцией (closure)

Удалить хук с замыканием не так просто, например такой код не сработает:

add_action( 'my_action', function(){  echo 'Привет!';  } );

remove_action( 'my_action', function(){  echo 'Привет!';  } ); // не работает!

Надежно удалить хук с использованием замыкания, можно только если замыкание создавалось в переменную и у нас есть доступ к этой переменной:

$my_func = function(){
	echo 'Привет!';
};

add_action( 'my_action', $my_func );

remove_action( 'my_action', $my_func ); // Работает!

НЕ надежный, но все же способ удалить хук с замыканием, когда у нас нет доступа к замыканию:

/**
 * Removes the hook when it has been added by a closure.
 * The accuracy of the function is not guaranteed - the first hook
 * that matches the priority and the number of hook arguments will be removed.
 *
 * @param string $name
 * @param int    $priority
 * @param int    $accepted_args
 */
function remove_closure_hook( $name, $priority = 10, $accepted_args = 1 ): bool {
	global $wp_filter;

	if( empty( $wp_filter[ $name ]->callbacks[ $priority ] ) ){
		return false;
	}

	$callbacks = & $wp_filter[ $name ]->callbacks[ $priority ];

	// Find our hook.
	// It is not always possible to identify it unambiguously, but
	// at least we know that it was created with a closure
	// and we know it's priority and number of parameters.
	foreach( $callbacks as $key => $hook ){

		if( ! ( $hook['function'] instanceof Closure ) ){
			continue;
		}

		if( $hook['accepted_args'] !== $accepted_args ){
			continue;
		}

		// remove
		unset( $callbacks[ $key ] );

		// first suitable only
		return true;
	}

	return false;
}

Пример использования:

add_action( 'my_action', function(){
	echo 'Привет!';
} );

do_action( 'my_action' ); // Привет!

remove_closure_hook( 'my_action', 10, 1 );

do_action( 'my_action' ); // (пусто)

Эта заметка встроена в: Хуки в WordPress (фильтры и события)