Вопрос или проблема
Это чертовски плохая практика, однако я должен сказать. Провел последние два часа, ища решение для удаления действий и фильтров, добавленных через анонимные функции.
Это код, использованный в родительской теме, и мне нужно его удалить.
/**
* Добавить пользовательские колонки в админскую сетку комментариев
* Оценка, установленная пользователем.
*/
add_filter( 'manage_edit-comments_columns', function( $default ) {
$columns['smr_comment_rate'] = __( 'Оценка', 'txtdmn' );
return array_slice( $default, 0, 3, true ) + $columns + array_slice( $default, 2, NULL, true );
});
Получил ответ toscho, много с ним экспериментировал, но безрезультатно. Есть ли другой способ, который позволит удалить действия/фильтры, добавленные через анонимные функции?
Спасибо
Проблема в том, что вы не можете отличить одну анонимную функцию от другой, так что да, возможно удалить замыкание (т.е. анонимную функцию), но если более одного замыкания действует на один и тот же фильтр с одинаковым приоритетом, вам придется делать выбор: удалить все или удалить только одно (не зная точно какое).
Я покажу, как удалить их все, используя функцию, сильно основанную на той, что в ответе @toscho:
/**
* Удалить объектный фильтр.
*
* @param string $tag Имя хука.
* @param string $class Имя класса. Используйте 'Closure' для анонимных функций.
* @param string|void $method Имя метода. Оставьте пустым для анонимных функций.
* @param string|int|void $priority Приоритет
* @return void
*/
function remove_object_filter( $tag, $class, $method = NULL, $priority = NULL ) {
$filters = $GLOBALS['wp_filter'][ $tag ];
if ( empty ( $filters ) ) {
return;
}
foreach ( $filters as $p => $filter ) {
if ( ! is_null($priority) && ( (int) $priority !== (int) $p ) ) continue;
$remove = FALSE;
foreach ( $filter as $identifier => $function ) {
$function = $function['function'];
if (
is_array( $function )
&& (
is_a( $function[0], $class )
|| ( is_array( $function ) && $function[0] === $class )
)
) {
$remove = ( $method && ( $method === $function[1] ) );
} elseif ( $function instanceof Closure && $class === 'Closure' ) {
$remove = TRUE;
}
if ( $remove ) {
unset( $GLOBALS['wp_filter'][$tag][$p][$identifier] );
}
}
}
}
Я переименовал функцию remove_object_filter
, потому что она может удалять все типы объектных фильтров: статические методы классов, динамические методы объектов и замыкания.
Аргумент $priority
является необязательным, но при удалении замыканий его всегда следует использовать, иначе функция удалит любое замыкание, добавленное к фильтру, вне зависимости от приоритета, потому что когда $priority
пропущен, все фильтры, использующие целевой класс/метод или замыкание, будут удалены.
Как использовать
// удалить статический метод
remove_object_filter( 'a_filter_hook', 'AClass', 'a_static_method', 10 );
// удалить динамический метод
remove_object_filter( 'a_filter_hook', 'AClass', 'a_dynamic_method', 10 );
// удалить замыкание
remove_object_filter( 'a_filter_hook', 'Closure', NULL, 10 );
Анонимные фильтры и действия могут быть удалены нативно с использованием следующего:
remove_filter( $tag, function(){}, $priority )
При генерации уникального идентификатора с помощью spl_object_hash()
анонимные функции сопоставимы друг с другом, поэтому полный объект замыкания не нужно создавать заново.
Если несколько фильтров или действий подключены к одному тегу с одинаковым приоритетом, то будет удален последний фильтр или действие, которое было добавлено. Если нужно оставить один, вам нужно будет удалить все фильтры до нужного, а затем повторно добавить остальные по мере необходимости.
// Фильтр, который был добавлен и должен быть удален
add_filter( 'manage_edit-comments_columns', function( $default ) {
$columns['smr_comment_rate'] = __( 'Оценка', 'txtdmn' );
return array_slice( $default, 0, 3, true ) + $columns + array_slice( $default, 2, NULL, true );
} );
// Удаляет последний анонимный фильтр, который был добавлен
remove_filter( 'manage_edit-comments_columns', function(){} );
Это, в конечном итоге, сводится к лучшим практикам. Я буду использовать анонимные функции только как часть пользовательской темы, которую я разрабатываю для клиента, где не хочу, чтобы фильтр был перезаписан или удален. В любой публичной теме или плагине, которые я разрабатываю, я использую фабрику для инициализации класса, добавляю все мои фильтры и действия, затем сохраняю экземпляр как статическую переменную.
ИЗМЕНЕНИЕ
remove_filter
, использующий анонимную функцию, по-видимому, не работает с последними версиями WordPress и PHP. Функция _wp_filter_build_unique_id
была обновлена с WordPress 5.3.0, и она удалила некоторые обходные пути с использованием `spl_object_hash`, что, в свою очередь, мешает удалять фильтры таким образом.
Единственный способ, который я сейчас вижу для удаления фильтров, это вручную изменить глобальную переменную $wp_filter
.
global $wp_filter;
unset( $wp_filter[ $tag ]->callbacks[ $priority ][ $identifier ] );
// или
foreach ( $wp_filter[ $tag ]->callbacks[ $priority ] as $identifier => $callback ) {
// Сравнивайте идентификатор по мере необходимости
}
// или
array_pop( $wp_filter[ $tag ]->callbacks[ $priority ] );
Может кому-то это будет нужно: я немного модифицировал код функции remove_object_filter
в ответе @gmazzap, чтобы она работала с WP 4.7+:
/**
* Удалить объектный фильтр.
*
* @param string $tag Имя хука.
* @param string $class Имя класса. Используйте 'Closure' для анонимных функций.
* @param string|void $method Имя метода. Оставьте пустым для анонимных функций.
* @param string|int|void $priority Приоритет
* @return void
*/
function remove_object_filter( $tag, $class, $method = NULL, $priority = NULL ) {
global $wp_version;
$new_wp_filter_struct = false;
$filters = isset( $GLOBALS['wp_filter'][ $tag ] ) ? $GLOBALS['wp_filter'][ $tag ] : array();
if ( empty ( $filters ) ) {
return;
}
if (version_compare( $wp_version, '4.7', '>=') && isset($filters->callbacks)) { // новая структура 'wp_filter' (WP >= 4.7)
$filters = $filters->callbacks;
$new_wp_filter_struct = true;
}
foreach ( $filters as $p => $filter ) {
if ( ! is_null($priority) && ( (int) $priority !== (int) $p ) ) continue;
$remove = FALSE;
foreach ( $filter as $identifier => $function ) {
$function = $function['function'];
if (
is_array( $function )
&& (
is_a( $function[0], $class )
|| ( is_array( $function ) && $function[0] === $class )
)
) {
$remove = ( $method && ( $method === $function[1] ) );
} elseif ( $function instanceof Closure && $class === 'Closure' ) {
$remove = TRUE;
}
if ( $remove ) {
if ($new_wp_filter_struct) {
unset( $GLOBALS['wp_filter'][$tag]->callbacks[$p][$identifier] );
if (count($GLOBALS['wp_filter'][$tag]->callbacks[$p]) == 0) {
unset($GLOBALS['wp_filter'][$tag]->callbacks[$p]);
}
}
else {
unset( $GLOBALS['wp_filter'][$tag][$p][$identifier] );
}
}
}
}
}
Я немного протестировал это с анонимной функцией, и похоже, что это работает. Код также должен быть совместим с версиями WP < 4.7 (но я этого не тестировал).
Что если вы добавите свой фильтр с приоритетом 11, чтобы он оказался после? Это некрасиво, но может сработать в вашем случае.
add_filter( 'manage_edit-comments_columns', function( $default ) {
unset( $default['smr_comment_rate'] );
return $default;
}, 11, 1 );
Ответ или решение
Удаление действий и фильтров, добавленных с помощью анонимных функций, является распространенной задачей в разработке с использованием WordPress. Однако этот процесс может показаться сложным из-за особенностей работы с анонимными функциями. В данном ответе мы подробно рассмотрим, как эффективно удалить такие фильтры, сохраняя при этом чистоту и порядок в коде вашего проекта.
Почему удаление анонимных фильтров является сложной задачей
Анонимные функции (или замыкания) не имеют имени, что делает их идентификацию затруднительной. WordPress использует массивы для хранения зарегистрированных фильтров и действий, но поскольку анонимные функции не имеют уникального идентификатора, вы не сможете удалить конкретную анонимную функцию напрямую, если не имеете ее первой копии.
Решение проблемы: удаление анонимных фильтров
Тем не менее, существует ряд подходов для решения этой проблемы.
-
Удаление всех фильтров с помощью глобальной переменной:
Вы можете получить доступ к глобальной переменной
$wp_filter
и программно удалить фильтры. Вот пример функции, которая поможет вам удалить анонимные функции и другие фильтры:function remove_object_filter( $tag, $class, $method = NULL, $priority = NULL ) { $filters = $GLOBALS['wp_filter'][$tag]; if ( empty( $filters ) ) { return; } foreach ( $filters as $p => $filter ) { if ( !is_null($priority) && ( (int) $priority !== (int) $p ) ) continue; $remove = FALSE; foreach ( $filter as $identifier => $function ) { $function = $function['function']; if ( $function instanceof Closure && $class === 'Closure' ) { $remove = TRUE; } if ( $remove ) { unset( $GLOBALS['wp_filter'][$tag][$p][$identifier] ); } } } }
-
Использование
remove_filter
с анонимной функцией:В старых версиях WordPress вы могли попытаться использовать функцию
remove_filter
, хотя это будет работать не для всех случаев. На данный момент это может не сработать, так как WordPress обновил свою реализацию управления фильтрами:remove_filter( 'manage_edit-comments_columns', function() { }, 10 );
Однако этот метод, учитывая последние обновления WordPress, может не дать ожидаемого результата.
-
Добавление нового фильтра для переопределения поведения:
Если вам нужно временно отключить анонимный фильтр, можно добавить новый фильтр с более высоким приоритетом, который будет переопределять поведение оригинального фильтра:
add_filter( 'manage_edit-comments_columns', function( $default ) { // Убираем элемент из столбца unset( $default['smr_comment_rate'] ); return $default; }, 11 );
Лучшие практики
Использование анонимных функций в коде может привести к трудностям в поддержке и понимании кода в будущем. Рекомендуется придерживаться следующих практик:
- Избегайте анонимных функций в публичных темах и плагинах. Это упростит управление вашими фильтрами и действиями.
- Используйте именованные функции или классы для добавления фильтров и действий. Это позволит вам легко обращаться к нужным фильтрам и действиям в дальнейшем.
- Документируйте код. Приведите комментарии в коде для объяснения логики работы с фильтрами и действиями, особенно если вы используете сложные конструкции.
Следуя этим рекомендациям, вы обеспечите поддерживаемость кода и упростите задачу удаления фильтров в будущем.