Actions и filters
Первый плагин регистрирует [pw_learning_note] и возвращает фиксированное пояснение. Теперь представим, что другая учебная площадка хочет изменить формулировку, сохранив сам механизм. Вместо копирования всей функции добавим filter — точку преобразования значения. Одновременно разберём отличие filters от actions. Результат урока — расширяемый текст с понятным моментом регистрации и безопасным конечным представлением.
Hooks позволяют подключить код к выбранному событию или обработке данных WordPress. Action сообщает об этапе, на котором можно выполнить действие; filter получает значение и возвращает его изменённую версию. Отличие описано в официальном руководстве. Фраза «оба вызывают функцию» верна технически, но недостаточна для понимания контракта. У нашего init важен момент регистрации, у будущего фильтра — полученная строка и возвращённый результат.
Преобразование текста
В pw-workshop-tools.php замените целиком функцию pw_workshop_learning_note следующим фрагментом. Остальной файл первого плагина, включая заголовок, проверку ABSPATH, регистрацию shortcode и add_action, сохраняется. В снимке урока есть полный файл после изменения.
function pw_workshop_learning_note(): string {
$message = 'Учебный пример нужно выполнить в собственной копии проекта.';
$message = apply_filters('pw_workshop_learning_note_text', $message);
if (!is_string($message)) {
$message = 'Учебный пример требует самостоятельного прохождения.';
}
return '<p class="pw-learning-note">' . esc_html($message) . '</p>';
}
apply_filters передаёт исходную строку зарегистрированным обработчикам и получает итог. Наш контракт допускает именно текст, не HTML-фрагмент. Проверка типа возвращает понятное запасное сообщение при неправильной реализации обработчика. Она не скрывает необходимость диагностики: в рабочем проекте такой случай должен попасть в журнал без лишних пользовательских данных. Учебный пример не добавляет подсистему журналирования и не обещает, что ошибка была зафиксирована автоматически.
Экранирование выполняется после фильтра. Если обработчик вернёт строку с <strong>, посетитель должен увидеть буквальные символы, а не новый элемент разметки. Это соответствует текстовому контракту. Смена на вывод разрешённого HTML потребовала бы других правил и проверки допустимых элементов. Не удаляйте esc_html только потому, что код расширения считается доверенным: такая правка меняет обещанный тип результата и создаёт новую поверхность обработки данных.
Отдельный потребитель фильтра
Чтобы показать расширение без изменения основной функции, создадим второй маленький плагин pw-workshop-policy/pw-workshop-policy.php. Это полная самостоятельная учебная добавка. Её активируют только в сценарии урока 16, а последующие основные снимки не требуют её присутствия:
<?php
/**
* Plugin Name: PW Workshop Policy
* Description: Educational wording variation.
* Version: 0.1.0
* Requires PHP: 8.3
*/
if (!defined('ABSPATH')) {
exit;
}
function pw_workshop_policy_note(string $message): string {
return 'Сохраните свою копию примера и запишите наблюдаемый результат.';
}
add_filter('pw_workshop_learning_note_text', 'pw_workshop_policy_note', 10, 1);
Собственное имя фильтра уменьшает риск столкновения с чужим механизмом. Последнее число 1 задаёт число аргументов, которые получает callback; в текущем контракте это одна строка. Приоритет 10 определяет порядок относительно других обработчиков того же фильтра. Он не является оценкой важности или гарантией, что никакой более поздний обработчик не изменит результат. Если несколько расширений используют одну точку, надо прочитать их последовательность и договор о данных.
После активации обоих плагинов ожидается новая формулировка в study и image-shift. Имена вставок и адреса страниц остаются прежними. При деактивации Policy основной Tools снова использует свой исходный текст. При деактивации Tools фильтр сам по себе не создаёт shortcode: он лишь ожидает вызова точки преобразования. Такая асимметрия помогает понять зависимость. Потребитель не обязан запускать функцию поставщика при загрузке своего файла.
Почему action не возвращает этот текст
Наш add_action('init', ...) подготавливает регистрацию shortcode. Возвращаемое значение функции регистрации не используется для изменения страницы. Если написать там return 'Новый текст', это не подменит содержимое [pw_learning_note]. Для значения нужен вызов фильтра в обработчике вывода. Action может изменить состояние системы, зарегистрировать объект или запланировать работу, но его назначение должно быть понятно из этапа, к которому подключается функция.
Рассмотрим ошибку обратного направления: callback фильтра печатает строку через echo, но ничего не возвращает. Значение может стать null, а побочный вывод появиться в неподходящем месте. В нашем коде сработает запасной текст по проверке типа, но неправильный echo всё равно является ошибкой расширения. Исправление — возвращать строку и не печатать её. Этот пример показывает, зачем чётко назвать контракт: «получает одну строку, возвращает одну строку».
Есть ещё различие между регистрацией фильтра и его выполнением. Строка add_filter помещает callback в список обработчиков, но не меняет страницу немедленно. Функция вызывается позже, когда Tools доходит до apply_filters при обработке shortcode. Поэтому расширение Policy не должно само вызывать shortcode, чтобы подтвердить подключение. Если нужного текста нет в странице, оно может остаться зарегистрированным, но не выполниться ни разу. Это нормальное состояние, а не неисправность события init.
Когда контракт фильтра расширяется, можно добавить контекст отдельным аргументом, например идентификатор материала. Старому callback, принимающему одну строку, такой контекст не требуется. Однако автор поставщика обязан описать порядок аргументов и сохранить исходное значение доступным. Мы не добавляем текущего пользователя в этот учебный фильтр: пояснение публичное и одинаковое. Привязка результата к аккаунту затронула бы кеширование и анонимный просмотр, которых небольшая текстовая функция пока не рассматривает.
Управление подпиской и порядком
Для удаления обработчика нужно указать тот же hook, callback и приоритет, с которыми он добавлен. Поэтому именованная функция удобнее случайного анонимного обработчика, ссылку на который никто не сохранил. В учебной Policy приоритет 10 известен, и механизм можно отключить деактивацией всего маленького расширения. В более сложном проекте снятие подписки должно происходить в подходящий момент после её регистрации. Не обещаем, что вызов remove_filter из любого места всегда найдёт ещё не подключённый callback.
Не используйте filter как повод выполнять дорогой сетевой запрос при каждом рендеринге абзаца. Вызов может повторяться в разных контекстах, а читатель ждёт обычный текст. Если формулировка зависит от удалённого сервиса, нужны кеширование, отказ и ограничение времени. Для нашей задачи данные локальные и постоянные. Это сохраняет полезный принцип расширяемости без скрытого усложнения скорости, доступности и восстановления сайта.
Ожидаемый итог — одна функция поставляет точку преобразования, другая меняет текст, а конечный HTML по-прежнему формируется и экранируется в Tools. Все активации и PHP-исполнение отмечены not_run. Проверьте обычный текст, буквальные HTML-символы и отказ при неверном типе на отдельном стенде; в рабочий код ошибочный callback не переносится. Следующий урок использует уже знакомый action init для регистрации самостоятельной сущности курса, сохраняя функцию пояснения в том же основном плагине.