Передача параметра в функции filter и action
Существует способ передать собственные параметры в функцию через add_filter
или add_action
.
Рассмотрим следующий код:
function my_content($content, $my_param)
{
// что-то делаем...
// используем $my_param здесь ...
return $content;
}
add_filter('the_content', 'my_content', 10, 1);
Можно ли передать свой параметр? Например так:
add_filter('the_content', 'my_content($my_param)', 10, 1)
или
add_filter('the_content', 'my_content', 10, 1, $my_param)
По умолчанию это невозможно. Есть обходные пути, если использовать ООП подход.
Вы можете создать класс для хранения значений, которые хотите использовать позже.
Пример:
/**
* Хранит значение и вызывает любую существующую функцию с этим значением.
*/
class WPSE_Filter_Storage
{
/**
* Заполняется через __construct(). Используется в __call().
*
* @type mixed Любой нужный вам тип.
*/
private $values;
/**
* Сохраняет значения для последующего использования.
*
* @param mixed $values
*/
public function __construct( $values )
{
$this->values = $values;
}
/**
* Перехватывает все вызовы функций, кроме __construct().
*
* Внимание: Даже если функция вызывается с одной строкой в качестве
* аргумента, она будет передана как массив.
*
* @param string $callback Имя функции
* @param array $arguments
* @return mixed
* @throws InvalidArgumentException
*/
public function __call( $callback, $arguments )
{
if ( is_callable( $callback ) )
return call_user_func( $callback, $arguments, $this->values );
// Вызвана неверная функция.
throw new InvalidArgumentException(
sprintf( 'Файл: %1$s<br>Строка %2$d<br>Не вызывается: %3$s',
__FILE__, __LINE__, print_r( $callback, TRUE )
)
);
}
}
Теперь вы можете вызывать класс с любой функцией — если функция существует где-то, она будет вызвана с вашими сохраненными параметрами.
Создадим демонстрационную функцию …
/**
* Функция фильтра.
* @param array $content
* @param array $numbers
* @return string
*/
function wpse_45901_add_numbers( $args, $numbers )
{
$content = $args[0];
return $content . '<p>' . implode( ', ', $numbers ) . '</p>';
}
… используем её один раз …
add_filter(
'the_content',
array (
new WPSE_Filter_Storage( array ( 1, 3, 5 ) ),
'wpse_45901_add_numbers'
)
);
… и ещё раз …
add_filter(
'the_content',
array (
new WPSE_Filter_Storage( array ( 2, 4, 6 ) ),
'wpse_45901_add_numbers'
)
);
Вывод:
Ключевой момент — повторное использование: вы можете повторно использовать класс (и в наших примерах также функцию).
PHP 5.3+
Если вы используете PHP версии 5.3 или новее, замыкания сделают это намного проще:
$param1 = '<p>Это работает!</p>';
$param2 = 'Это тоже работает!';
add_action( 'wp_footer', function() use ( $param1 ) {
echo $param1;
}, 11
);
add_filter( 'the_content', function( $content ) use ( $param2 ) {
return t5_param_test( $content, $param2 );
}, 12
);
/**
* Добавляет строку к содержимому поста
*
* @param string $content
* @param string $string В нашем примере это $param2.
* @return string
*/
function t5_param_test( $content, $string )
{
return "$content <p><b>$string</b></p>";
}
Недостаток в том, что вы не можете писать unit-тесты для замыканий.

Вы не только получаете голос за качественный ответ на проблему, которая должна иметь встроенное решение в ядре WP, но и за то, что вернулись через пять месяцев, чтобы обновить свой ответ примером с замыканием (closure) для PHP 5.3+.

Отличный ответ! Но как я могу удалить этот фильтр, созданный анонимной функцией, позже?

@ViniciusTavares Не можете. Подумайте, прежде чем использовать это. :)

Однако обратите внимание, что если сохранить анонимную функцию в переменную (например, $func = function() use ( $param1 ) { $param1; };
и add_action( $func, 11);
), то её можно будет удалить с помощью remove_action( $func, 11 );

Но не рекомендуется использовать анонимные функции в плагинах или темах, которые вы выпускаете в мир (в своих проектах их использовать можно). Проблема в том, что их потом нельзя будет отключить. Любой выбранный вами подход должен позволять отключать функции в будущем.

Используйте анонимные функции в PHP:
$my_param = 'my theme name';
add_filter('the_content', function ($content) use ($my_param) {
//Теперь $my_param доступен внутри функции
if (is_page()) {
$content = $my_param . ':<br>' . $content;
}
return $content;
}, 10, 1);

Правильный, действительно короткий и наиболее эффективный способ передачи любого количества аргументов в фильтры и действия WordPress от @Wesam Alalem можно найти здесь, где используется замыкание (closure).
Я бы только добавил, что можно сделать код еще более понятным и гибким, отделив основной рабочий метод от анонимного замыкания. Для этого просто вызовите метод из замыкания, как показано ниже (модифицированный пример из ответа @Wesam Alalem).
Таким образом, вы можете написать любую сложную логику лексически вне замыкания, которое используется для вызова основного метода.
// ... внутри какого-либо класса
private function myMethod() {
$my_param = 'название моей темы';
add_filter('the_content', function ($content) use ($my_param) {
// Это анонимное замыкание, которое позволяет передавать
// любое количество параметров через ключевое слово 'use'.
// Это всего лишь однострочник.
// $my_param теперь доступен через ключевое слово 'use' выше
return $this->doThings($content, $my_param);
}, 10, 2);
}
private function doThings($content, $my_param) {
// Здесь можно вызвать другой метод для выполнения дополнительных действий,
// какой бы сложной ни была ваша логика.
$morethings = '';
if ($content = 'еще что-то') {
$morethings = (new MoreClass())->get();
}
return $my_param . ':<br>' . $content . $morethings;
}

Создайте функцию с необходимыми аргументами, которая возвращает другую функцию. Передайте эту функцию (анонимную функцию, также известную как замыкание) в хук WordPress.
Пример показан для административного уведомления в бэкенде WordPress.
public function admin_notice_func( $message = '')
{
$class = 'error';
$output = sprintf('<div class="%s"><p>%s</p></div>',$class, $message);
$func = function() use($output) { print $output; };
return $func;
}
$func = admin_notice_func('Сообщение');
add_action('admin_notices', $func);

Как упоминалось в других ответах, передача параметра в функцию обратного вызова по умолчанию невозможна. ООП и анонимные функции в PHP — это обходные пути, НО:
- Ваш код может не быть ООП
- Вам может понадобиться удалить этот фильтр впоследствии
Если это ваш случай, есть ещё один обходной путь, который вы можете использовать: воспользуйтесь функциями add_filter
и apply_filters
, чтобы сделать нужный параметр доступным в функции обратного вызова:
// Обходной путь для "сохранения" параметра, который нужно передать в функцию обратного вызова.
add_filter( 'pass_param', function() use ( $param ){ return $param; } );
// Подключите вашу функцию к фильтру, который нужно обработать.
add_filter( 'actual_filter', 'myCallback' );
// Ваша функция обратного вызова, которая фактически фильтрует данные.
function myCallback()
{
// Получаем параметр, который не смогли передать напрямую в эту функцию.
$param = apply_filters( 'pass_param', '' );
// Делаем что-то с параметром, переданным через обходной путь, чтобы использовать его для фильтрации.
return $param;
}

Это не является ответом на вопрос. Когда у вас будет достаточно репутации, вы сможете оставлять комментарии к любым сообщениям; вместо этого предоставляйте ответы, не требующие уточнений от автора вопроса. - Из обзора

@cjbj На самом деле является. Вопрос в том, можно ли передавать параметры в "функцию", которая находится в add_filter или add_action. Не было ясно, хотел ли пользователь передать их в саму функцию add_filter или add_action, хотя это и подразумевается. :)

Несмотря на возможность прямого вызова функции, сделайте это более элегантным способом: передайте анонимную функцию в качестве callback.
Например:
У меня есть единая функция для перевода заголовка, содержимого и отрывка из моих записей. Поэтому мне нужно передать в эту основную функцию несколько аргументов, указывающих, кто вызывает.
add_filter( 'the_title', function( $text ) {
return translate_text( $text, 'title', 'pl' );
});
add_filter( 'the_content', function( $text ) {
return translate_text( $text, 'content', 'pl' );
});
add_filter( 'the_excerpt', function( $text ) {
return translate_text( $text, 'excerpt', 'pl' );
});
Таким образом, основная функция translate_text
получает столько параметров, сколько я хочу, просто потому что я передал анонимную функцию в качестве callback.

Я согласен, что ответ fuxia выше предлагает предпочтительные подходы. Но пока я пытался разобраться с ООП-решением, я наткнулся на способ, который устанавливает, а затем снимает и фильтр, и глобальную переменную:
function my_function() {
// Объявляем глобальную переменную и устанавливаем её значение
global $my_global;
$my_global = 'что-то';
// Добавляем фильтр
add_filter( 'some_filter', 'my_filter_function' );
// Делаем то, для чего нам нужен был фильтр
echo $filtered_stuff;
// Удаляем фильтр (чтобы он не повлиял на что-то ещё, что выполнится позже)
remove_filter( 'some_filter', 'my_filter_function' );
// Удаляем глобальную переменную (потому что нам не нравятся глобальные переменные в коде)
my_unset_function( 'my_global' );
}
function my_filter_function( $arg ) {
// Объявляем глобальную переменную
global $my_global;
// Используем $my_global для работы с $arg
$arg = $arg . $my_global;
return $arg;
}
function my_unset_function( $var_name ) {
// Доступ к глобальной переменной
$GLOBALS[$var_name];
// Удаляем глобальную переменную
unset($GLOBALS[$var_name];
}
Я самоучка и работаю только над своими сайтами, поэтому воспринимайте этот набросок с долей скепсиса. У меня это работает, но если здесь есть какие-то ошибки, буду благодарен, если кто-то более опытный укажет на них.

В моем ООП-решении я просто использовал переменную-член класса, которая вызывается в callback-функции. В этом примере post_title фильтруется по поисковому запросу:
class MyClass
{
protected $searchterm = '';
protected function myFunction()
{
query = [
'numberposts' => -1,
'post_type' => 'my_custom_posttype',
'post_status' => 'publish'
];
$this->searchterm = 'xyz';
add_filter('posts_where', [$this, 'searchtermPostsWhere']);
$myPosts = get_posts($query);
remove_filter('posts_where', [$this, 'searchtermPostsWhere']);
}
public function searchtermPostsWhere($where)
{
$where .= ' AND ' . $GLOBALS['wpdb']->posts . '.post_title LIKE \'%' . esc_sql(like_escape($this->searchterm)) . '%\'';
return $where;
}
}

Если вы создаете собственный хук, вот пример:
// допустим, у нас есть три параметра [ https://codex.wordpress.org/Function_Reference/add_filter ]
add_filter( 'filter_name', 'my_func', 10, 3 );
my_func( $first, $second, $third ) {
// код
}
затем реализуем хук:
// [ https://codex.wordpress.org/Function_Reference/apply_filters ]
echo apply_filters( 'filter_name', $first, $second, $third );

Это не передает информацию из регистрации в callback. Здесь просто указано, сколько параметров может принимать callback.

Я знаю, что время прошло, но у меня были проблемы с передачей собственного параметра, пока я не обнаружил, что 4-й параметр в add_filter — это количество передаваемых параметров, включая контент для изменения. Поэтому, если вы передаёте 1 дополнительный параметр, число должно быть 2, а не 1, как в вашем случае.
add_filter('the_content', 'my_content', 10, 2, $my_param)
и использование
function my_content($content, $my_param) {...}

Вы уверены, что можно передать пятый параметр в add_filter
? Согласно официальной документации, это некорректно. Вы тестировали свой ответ? Пожалуйста, будьте осторожны с распространением дезинформации.

Я хотел сделать то же самое, но так как это невозможно, думаю, простым решением будет вызов другой функции, например
add_filter('the_content', 'my_content_filter', 10, 1);
Затем функция my_content_filter() может просто вызвать my_content(), передавая любой нужный аргумент.
