ГлавнаяРазноеСобытия и фильтры в WordPress

События и фильтры в WordPress

Фильтры и события — это то, что позволяет темам и плагинам изменять поведение ядра, не изменяя исходные файлы самого ядра WordPress. В этой статье мы расскажем вам о том, как работают фильтры и события в WordPress и как ими пользоваться в ваших темах и плагинах.

События

События или действия (actions) в WordPress очень похожи на события в JavaScript. Событие выполняется вызовом функции do_action(), а добавить функцию к любому событию можно с помощью функции add_action().

При выполнении события или действия, выполняются все функции, добавленные к событию в определенном порядке. Это легче всего понять с помощью простого примера. Определяем три функции, которые будут выводить 1, 2 и 3 соответственно:

function one() { echo 1; }
function two() { echo 2; }
function three() { echo 3; }

Добавляем функции к событию foo с помощью функции add_action():

add_action( 'foo', 'one' );
add_action( 'foo', 'two' );
add_action( 'foo', 'three' );

И выполняем наше событие с помощью функции do_action():

do_action( 'foo' ); // выведет 123

Первый аргумент к функциям — это строка foo — название события. Название может быть любым, но во избежание конфликтов с другими плагинами и темами в собственных событиях лучше использовать префикс, например myplugin_foo, где myplugin это название вашего плагина.

Второй аргумент к функции add_action() — это функция, которая вызывается в момент выполнения события. Функции в рамках одного события выполняются в том же порядке, в котором они были добавлены к событию, за исключением когда функция добавляется к событию с повышенным или пониженным приоритетом. О приоритетах чуть позже.

Итак, наш пример вызовет функции one(), two() и three() по порядку, что выведет на экран 123. Конечно мы могли самостоятельно вызвать эти функции в этом же порядке на месте do_action(), что дало бы тот же самый результат. Так зачем использовать события?

Зачем использовать события

Любой другой плагин или тема смогут легко добавить или удалить функции из вашего события без необходимости изменять код вашего плагина. Такой подход делает ваш плагин более гибким. Например:

/* В другом плагине */
function four() { echo 4; }

remove_action( 'foo', 'three' );
add_action( 'foo', 'four' );

Таким образом, когда дело дойдет до вызова события foo в вашем плагине, на экран выведется уже не 123, а 124, поскольку другой плагин удалил функцию three() из вашего события с помощью функции remove_action(), и добавил на ее место новую функцию four().

Пример с выводом чисел не самый полезный. На практике достаточно легко придумать события в вашей теме или в плагине, которые могли бы быть полезными для других плагинов, например:

  • Ваша тема может выполнить событие в момент вывода шапки, и любой плагин сможет вывести на этом месте баннер, не изменяя код вашей темы
  • Ваша тема может выполнить событие в подвале, и с помощью плагина можно будет вставить в подвал любой текст
  • Ваш плагин выводит блок «поделиться» в конце каждой статьи, другой плагин сможет добавить новую социальную сеть в этот же блок

Стоит так же отметить, что в самом ядре WordPress есть более 1500 фильтров и событий, которые можно использовать в темах и плагинах.

Фильтры

Фильтры в WordPress очень похожи на события. Главным отличием является то, что у фильтров есть значение, которое они передают каждой привязанной функции, соответственно каждая функция должна вернуть это же или измененное значение. Рассмотрим простой пример:

function plus_one( $value ) {
    $value = $value + 1;
    return $value;
}

Эта функция принимает один аргумент, добавляет к нему единицу и возвращает результат. Добавим нашу функцию к новому фильтру с помощью add_filter():

add_filter( 'foo', 'plus_one' );

Теперь все функции добавленные к фильтру foo (в нашем случае это всего одна функция) можно легко вызвать или «применить» с помощью функции apply_filters():

echo apply_filters( 'foo', 5 ); // 6

В функцию apply_filters() вторым аргументом мы передаем фильтруемое значение. Оно может быть любым и оно будет передаваться каждой функции привязанной к фильтру, при этом любой фильтр может это значение изменить.

В данном случае на экран выведется значение 6, поскольку значение 5 было пропущено через функцию plus_one(), которая изменила оригинальную переменную. Если убрать функцию у фильтра с помощью remove_filter(), то наш код выведет первоначальное значение 5:

remove_filter( 'foo', 'plus_one' );
echo apply_filters( 'foo', 5 ); // 5

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

Пример хорошего фильтра

Рассмотрим более интересный пример: создадим массив, который будет содержать ссылки на наши профили в социальных сетях и выводить эти ссылки в шапке нашей темы WordPress. В functions.php:

function get_my_social_profiles() {
    $profiles = array(
        'twitter' => 'http://twitter.com/wpmagru',
        'facebook' => 'http://facebook.com/wpmagru',
    );
    return $profiles;
}

Возвращаемый массив можно использовать в цикле в нашем файле header.php:

$profiles = get_my_social_profiles();
foreach ( $profiles as $service => $url ) {
    printf( '<a href="%s">%s</a>', esc_url( $url ), $service );
}

Данный код выведет ссылки на Twitter и Facebook в шапке нашей темы. Для того чтобы добавить новую ссылку в список, нам придется изменять саму тему, или же создать новый фильтр в функции get_my_social_profiles():

/* В functions.php */
function get_my_social_profiles() {
    $profiles = array(
        'twitter' => 'http://twitter.com/wpmagru',
        'facebook' => 'http://facebook.com/wpmagru',
    );
    return apply_filters( 'my_social_profiles', $profiles );
}

Таким образом любой плагин или дочерняя тема смогут легко управлять списком социальных профилей, не трогая оригинальную тему. Например, убрать ссылку на Twitter и добавить ссылку на Google+ можно с помощью следующего кода в плагине:

function change_my_social_profiles( $profiles ) {
    unset( $profiles['twitter'] );
    $profiles['google-plus'] = 'https://plus.google.com/+wpmagru';
    return $profiles;
}
add_filter( 'my_social_profiles', 'change_my_social_profiles' );

Фильтры и события в ядре WordPress

Как мы уже упомянули, в WordPress есть более 2000 фильтров и событий, которыми можно воспользоваться в плагинах или темах для того, чтобы изменить поведение ядра. Рассмотрим несколько интересных примеров.

Отключить комментирование

Отключить комментирование на всем сайте, на зависимо от настроек в параметрах можно с помощью следующего кода:

function my_comments_open() { return false; }
add_filter( 'comments_open', 'my_comments_open' );

Фильтр comments_open используется в ядре WordPress каждый раз для того, чтобы проверить открыты ли комментарии к той или иной статье. Наша функция всегда возвращает значение false для этого фильтра, поэтому комментарии будут закрыты везде.

Кстати, ядро WordPress определяет несколько вспомогательных функций для работы с подобными фильтрами:

  • __return_true() — возвращает true
  • __return_false() — возвращает false
  • __return_zero() — возвращает 0
  • __return_empty_string() — возвращает пустую строку
  • __return_empty_array() — возвращает пустой массив
  • __return_null() — возвращает null

То есть наш фильтр на comments_open можно переписать в одну строку:

add_filter( 'comments_open', '__return_false' );

Изменить длину автоматических цитат

За длину автоматических цитат отвечает фильтр excerpt_length:

function my_excerpt_length( $length ) {
    $length = 10;
    return $length;
}
add_filter( 'excerpt_length', 'my_excerpt_length' );

С помощью фильтра excerpt_more можно изменить текст, который который ставится в конце автоматический цитаты, по умолчанию это [...]:

function my_excerpt_more( $more ) {
    $more = '&rarr;';
    return $more
}
add_filter( 'excerpt_more', 'my_excerpt_more' );

Добавить баннер к содержимому каждой статьи

Баннер с помощью фильтра the_content

Баннер с помощью фильтра the_content

Фильтр the_content выполняется перед выводом содержимого каждой статьи. Через фильтр проходит само содержимое статьи, поэтому в него легко добавить баннер «на лету» с помощью плагина:

function my_banner( $content ) {
    $banner = '<a href="#"><img src="..." /></a>';
    $content = $banner . $content;
    return $content;
}
add_filter( 'the_content', 'my_banner' );

Добавить favicon.ico в раздел <head>

В разделе <head> в каждой теме выполняется событие wp_head. Во время этого события можно вывести ссылку на файл favicon.ico, вставить произвольный код JavaScript или CSS и многое другое:

function my_favicon() {
    echo '<link rel="shortcut icon" href="http://example.org/favicon.ico" />';
}
add_action( 'wp_head', 'my_favicon' );

Учтите, что если вам необходимо подключить внешние .js или .css файлы, делать это стоит с помощью функций wp_enqueue_script() и wp_enqueue_style() во время события wp_enqueue_scripts, а не напрямую в wp_head.

В каждой версии WordPress добавляются все больше и больше новых и полезных фильтров и событий. Список большинства фильтров и событий в ядре можно посмотреть на сайте Адама Брауна, или просканировав файлы ядра на «do_action» и «apply_filters».

Приоритеты

Функции добавленные к фильтрам и событиям выполняются в том же порядке, в котором они были добавлены, но порядок легко изменить с помощью приоритетов. Приоритет указывается третьим аргументом к функциям add_action() и add_filter().

Без указания этого аргумента функции к фильтрам и событиям добавляются по умолчанию с приоритетом 10. Функции выполняются от меньшего приоритета к большему, т.е. чем меньше приоритет, тем раньше выполняется функция.

Вернемся к примеру с цифрами:

add_action( 'foo', 'one' );
add_action( 'foo', 'two' );
add_action( 'foo', 'three' );

do_action( 'foo' ); // выведет 123

Если изменить приоритет выполнения функции three() на 9, то она выполнится раньше остальных:

add_action( 'foo', 'one' );
add_action( 'foo', 'two' );
add_action( 'foo', 'three', 9 );

do_action( 'foo' ); // выведет 312

Подобным образом, указав приоритет 11 для функции one(), она выполнится позднее всех остальных, несмотря на то, что она была добавлена первой с помощью add_action():

add_action( 'foo', 'one', 11 );
add_action( 'foo', 'two' );
add_action( 'foo', 'three', 9 );

do_action( 'foo' ); // выведет 321

Дополнительные параметры

В каждую функцию, привязанную к фильтру или событию, легко передать дополнительные параметры. Делается это при вызове функций do_action() или apply_filters(), например:

do_action( 'foo', $arg1, $arg2, $arg3 );
$value = apply_filters( 'foo', $value, $arg1, $arg2, $arg3 );

Ключевым моментом является то, что при добавлении функции к фильтру или событию нам необходимо указать количество принимаемых аргументов, за это отвечает четвертый параметр функций add_action() и add_filter().

Например, если в функции к событию foo мы хотим принять все три аргумента, необходимо указать 3 в качестве четвертого параметра к add_action():

function my_func( $arg1, $arg2, $arg3 ) { ... }
add_action( 'foo', 'my_func', 10, 3 );

Подобным образом, если в функции добавленной к фильтру мы хотим принять только $arg1 в качестве дополнительного аргумента, то просим add_filter() передать всего два аргумента — первый аргумент $value, и второй дополнительный аргумент $arg1:

function my_func( $value, $arg1 ) { ... }
add_filter( 'foo', 'my_func', 10, 2 );

Пример

Хорошим примером передачи дополнительных аргументов является фильтр allow_password_reset, с помощью которого можно запретить сброс пароля для пользователей:

add_filter( 'allow_password_reset', '__return_false' );

Это запретит сброс пароля для всех пользователей на сайте, но если нам необходимо запретить сброс пароля только супер-администраторам в сети мультисайт (в целях безопасности), мы можем воспользоваться дополнительным аргументом:

function my_filter( $allow, $user_id ) {
    if ( is_super_admin( $user_id ) )
        $allow = false;

    return $allow;
}
add_filter( 'allow_password_reset', 'my_filter', 10, 2 );
Запрет восстановления пароля с помощью фильтра

Запрет восстановления пароля с помощью фильтра

Учтите, что функции привязанные к фильтрам могут изменять только первый аргумент, передаваемый в фильтр. То есть функция приведенная выше может изменить только аргумент $allow, но не $user_id.

Для того, чтобы дать функциям возможно изменять более одного передаваемого аргумента, фильтры (и события) могут воспользоваться передачей переменных и объектов по ссылке в PHP, как это делает например событие pre_get_posts.

ООП, классы, объекты и анонимные функции

Разработчики тем и плагинов WordPress часто предпочитают объектно-ориентированный стиль программирования, где большая часть кода выполняется внутри объекта, а не в глобальном пространстве. Если функциям add_action() и add_filter() необходимо передать не функцию для вызова, а метод объекта, его необходимо передать в специальном формате массивом:

class My_Class {
    function __construct() {
        add_filter( 'the_content', array( $this, 'filter_content' ) );
    }

    function filter_content( $content ) {
        // ...
        return $content;
    }
}
new My_Class();

Похожим образом можно передать статический метод класса:

add_filter( 'the_content', array( 'My_Class', 'filter_content' ) );
add_filter( 'the_content', 'My_Class::filter_content' ); // PHP >= 5.2.3

Фильтры и события поддерживают и анонимные функции, например:

add_filter( 'the_content', create_function( '$content', 'return $content;' ) );
add_filter( 'the_content', function( $content ) { return $content; } ); // PHP >= 5.3

Пользоваться анонимными функциями с фильтрами и событиями в WordPress мы не рекомендуем, поскольку их сложно отлаживать (например с помощью плагина Debug Bar Slow Actions) а функция create_function() не кэшируется на уровне байт-кода, например в APC.

Заключение

Фильтры и события лежат в основе самого WordPress, и работу с ними следует освоить каждому разработчику тем и плагинов, ведь именно фильтры и события позволяют расширять ядро популярной системы.

Разработчикам тем и плагинов для WordPress так же не следует забывать о фильтрах и событиях внутри их собственных продуктов для того, чтобы делать их более модульными, гибкими и легко изменяемыми с помощью стороннего кода.

Если у вас возникнут вопросы о фильтрах и событиях в WordPress, оставьте комментарий и мы обязательно вам ответим.

Подписаться на рассылку

Подписаться → Подпишитесь на бесплатную рассылку журнала WP Magazine и получайте новости, события, подборки тем и плагинов, уроки, советы и многое другое в мире WordPress!

  • chumachkin_m

    Всем новичкам к прочтению — обязательна!

    • razboynikjm

      И не только, в инете много инфы, но 90% «веб мастеров» советуют подключать скрипты как на статике. Изучали бы литературу сначала, а потом и советы давали.

      • chumachkin_m

        Вы о чем?

        • razboynikjm

          Я о том, что большенство советует подключать скрипты напрямую в HEADER, применимо WordPress

          < script src=http//:

          • Ivan Panfilov

            это всего лишь один из способов. имеющий право на существование.
            кому какое дело как я подключаю скрипты и стили на своем сайте.

          • Егор

            Разница в том, что если ваш сайт тормозит, или не работает функционал?

            Если ваш JS скрипт не зависит ни от каких библиотек, и не используется никакими плагинами, то всё будет в порядке, но в противном случае ситуация сложнее.

            1 Многократная загрузка JS файлов.

            2 Неработающие» плагины

          • Вы что-то совсем не по теме, причем тут api событий и фильтров?

          • Ivan Panfilov

            статью не читай — сразу отвечай

          • Точно, о чем это я! )

          • Ivan Panfilov

            ну так да надо голову включать. при подключении ресурсов.
            ровно как и при использовании сторонних плагинов и тем. ибо качество зачастую оставляет желать лучшего.

  • Алексей

    Иногда возникает необходимость посмотреть какие события и фильтры включены на сайте вообще, все это хранится в глобальных переменных. Вот про это можно было еще написать.

    Вообще про глобальные переменные можно сделать отдельный материал, там хранится множество данных, которые при разработке для WP часто бывают необходимы.

    • Спасибо за комментарий Алексей! Действительно (увы) многие вещи в WordPress хранятся в глобальных переменных, но пользоваться ими напрямую мы не рекомендуем.

      Для отладки есть инструменты и по-интереснее, чем var_dump() глобального объекта, например для фильтров и событий есть плагины Debug Bar Slow Actions, Debug Bar Actions and Filters, Query Monitor и другие.

  • Владимир Петрозаводский

    Самое понятное описание из тех что я видел, огромное спасибо

  • Алексей

    Наконец-то нашел толковое описание. Автору добра!

  • Александр Каратаев

    Константин, а есть-ли какой-нибудь способ, позволяющий из плагина воткнуться между статьёй и комментариями, например? Т.е. как с помощью фильтра добавить что-либо в конец статьи понятно, а если надо именно не в конец, а после?

    • Если в том месте куда вы хотите что-либо вставить нет события или фильтра, то простым способом вставить это не получится. Вы конечно можете сделать это через буфер вывода, найти нужное место с помощью регулярного выражения и вставить что-нибудь, но это далеко не простой, не быстрый и не надежный метод.

      Если речь идет о какой-то конкретной теме, вы можете попросить автора добавить требуемый фильтр или событие.

      • Александр Каратаев

        Спасибо за ответ. В принципе я это знал, но надеялся что-нибудь посоветуете. Речь не о конкретной теме, а о том, чтобы из своего плагина воткнуться как раз между постом и комментариями. Тема может быть любая, плагин-то ставят разные люди.
        Ну раз нет такой возможности, значит не судьба. Сейчас плагин фильтром добавляет код в конец поста. Некоторые пользователи попросили ЗА постом… Отсюда и был вопрос.

  • razboynikjm

    Сильно ! Я в кодексе искал, но видно плохо. А тут все по полочкам. Спасибо большое за статью.

  • razboynikjm

    Константин, подскажите пожалуйста какой функцией можно добавить блок див с иконками соц сетей в навигационное меню.
    Или список ul

  • Фильтр и действие могут принимать callable тип (поддержка старых версий php дает о себе знать).
    Внутри все это дело хранится через некий строковый индификатор, который генерит _wp_filter_build_unique_id функция.

    Если мы обработччиком хука назначаем анонимную функцию:
    add_action(‘some’, function(){echo 1;});

    1) Как отвязать в будущем такой обработчик?
    2) Насколько оправдана работа с анонимными функциями?

    • 1. Если это действительно анонимная функция, то никак. Можно конечно циклом пробежаться через все функции у определенного фильтра или события, и если среди них всего одна анонимная функция, то есть неплохой шанс того, что это именно ваша анонимная функция.

      Но если отказаться от «анонимности» анонимной функции и присвоить ее внутренний адрес переменной, то по этой же переменной можно эту функцию затем и убрать:

      $f = function() { echo 1; };
      add_action( ‘init’, $f );
      remove_action( ‘init’, $f );

      Если не хочется хранить и передавать указатель на саму функцию, можно получить ее уникальный хэш с помощью функции spl_object_hash().

      2. Если вы разрабатываете плагин или тему для общего пользования, где другие разработчики возможно захотят убрать вашу функцию у фильтра или события, то лучше наверное не использовать анонимные функции. А если это какой-то ваш один определенный сайт или проект, то почему бы и нет.

      • 1. Если ее нельзя отвязать, то и добавлениее становится очень скользким моментом.
        Вряд ли в реальном коде будет присвоение к $f.


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

  • Вячеслав Галашин

    Все понятно

  • Promkomplekt Promkomplekt

    День добрый ! Пожалуйста как создать hook вставки банер а в header

  • Статья помогла.. Спасибо..

  • Мария Осипова

    Здравствуйте, скажите могу ли я через do_action выполнить только одну функцию прицепленную к этому экшну?
    Пример:
    вот экшн со списком хуков
    /**
    * woocommerce_single_product_summary hook.
    *
    * @hooked woocommerce_template_single_title — 5
    * @hooked woocommerce_template_single_rating — 10
    * @hooked woocommerce_template_single_price — 10
    * @hooked woocommerce_template_single_excerpt — 20
    * @hooked woocommerce_template_single_add_to_cart — 30
    * @hooked woocommerce_template_single_meta — 40
    * @hooked woocommerce_template_single_sharing — 50
    */
    do_action( ‘woocommerce_single_product_summary’ );
    а мне нужно вызвать только woocommerce_template_single_title как это сделать?

    • Если нужно вызвать только одну функцию где-то в произвольном месте, то можно и вызвать именно эту функцию и все. В данном случае это woocommerce_template_single_title. Т. е. можно спокойно в PHP-коде написать:
      woocommerce_template_single_title();
      И все :) Если же нужно от экшена woocommerce_single_product_summary удалить все, кроме чего-то одного, то можно remove_action() сделать или remove_all_actions(), а потом повторно добавить то, что надо было оставить :)

      • Мария Осипова

        Спасибо попробую

  • Сергей К.

    Почему при привязке к фильтру статического метода класса тут приведен пример:
    add_filter( ‘the_content’, array( ‘My_Class’, ‘filter_content’ ) );
    то есть указан My_Class вместо $this, как при вызове публичного метода? Ведь работает и при указании $this, как в примере выше.

    • Потому что $this используется для вызова метода объекта, а метод класса можно вызвать с помощью self:: или My_Class:: или static::. Если у вас есть объект, то возможно вы и можете использовать $this для вызова метода класса «данного» объекта, но смотрите лог ошибок, там наверняка сыпятся предупреждения.

      • Сергей К.

        Согласен, что использование $this тут не логично, но работает и ошибок/предупреждений не дает. Может WORDPRESS сам как-то этот момент корректирует.

        • Может WORDPRESS сам как-то этот момент корректирует.

          Вряд ли. Попробуйте включить вывод всех типов ошибок и предупреждений (E_ALL, E_STRICT, E_DEPRECATED, …)

  • Я бы еще посоветовал разработчикам не писать что-то вроде new My_Class(); внизу файла с самим классом (или где бы то еще без сохранения указателя). Часто сталкиваюсь с таким кодом даже в популярных плагинах и ругаюсь, потому что пойди потом удали все фильтры понаставленные с этим экземпляром класса. Да и архитектурно это как-то неправильно — файл должен содержать класс, а не класс и его инициализацию разом.