Использование register_activation_hook в классах
Я пытаюсь разработать плагин для базовой SEO-оптимизации, так как многие мои знакомые не любят использовать Yoast. Я только начал разработку плагина и создаю сообщение об активации, которое отображается пользователю при активации плагина. У меня возникли проблемы с совмещением ООП и встроенных функций WordPress, и я не уверен, где именно допускаю ошибку.
Единственный способ, которым я могу заставить это работать - создать новый экземпляр класса SEO_Plugin_Activation внутри конструктора моего основного файла, а затем вызвать метод activatePlugin в этом классе. Мне кажется, что это излишне. Я также думаю, что то, как я выполняю функции в файле класса активации, тоже не особо логично. Но сейчас это единственный способ заставить это работать.
Я не уверен, связано ли это с тем, что я не на 100% понимаю методы ООП, или с тем, что я неправильно использую API WordPress. Я привел три примера кода в следующем порядке:
- Основной файл плагина
- Файл класса, который обрабатывает требования активации
- То, как я надеялся это реализовать.
seo.php (основной файл плагина)
<?php
/*
... общая информация о плагине
*/
require_once(dirname(__FILE__) . '/admin/class-plugin-activation.php');
class SEO {
function __construct() {
$activate = new SEO_Plugin_Activation();
$activate->activatePlugin();
}
}
new SEO();
?>
class-plugin-activation.php
<?php
class SEO_Plugin_Activation {
function __construct() {
register_activation_hook(__FILE__ . '../seo.php', array($this, 'activatePlugin'));
add_action('admin_notices', array($this, 'showSitemapInfo'));
}
function activatePlugin() {
set_transient('show_sitemap_info', true, 5);
}
function showSitemapInfo() {
if(get_transient('show_sitemap_info')) {
echo '<div class="updated notice is-dismissible">' .
'Ваши файлы карты сайта можно найти по следующим ссылкам: ' .
'</div>';
delete_transient('show_sitemap_info');
}
}
}
?>
seo.php (как я надеялся реализовать)
<?php
/*
... блабла
*/
require_once(dirname(__FILE__) . '/admin/class-plugin-activation.php');
class SEO {
function __construct() {
register_activation_hook(__FILE__, array($this, 'wp_install'));
}
function wp_install() {
$activate = new SEO_Plugin_Activation();
// Выполнить здесь какой-то метод(ы), который позаботится
// обо всей начальной настройке активации плагина
}
}
new SEO();
?>
Я пытался сделать это способом, описанным в третьем скрипте, но безуспешно. На данный момент сообщение отображается правильно, без сообщений об ошибках.

Перечитав ваш вопрос, я понял проблему, и она связана с неправильным пониманием работы register_activation_hook
, а также с путаницей в том, как вы подключаете код и что означает "бутстраппинг".
Часть 1: register_activation_hook
Эта функция принимает 2 параметра:
register_activation_hook( string $file, callable $function )
Первый параметр $file
— это главный файл плагина, а не файл, содержащий код, который нужно выполнить. Он используется так же, как plugins_url
, поэтому вам нужно значение __FILE__
, а именно его значение в корневом файле плагина с заголовком вашего плагина.
Второй параметр — это вызываемая функция и работает так, как вы ожидаете, но на самом деле внутри использует add_action
.
При активации плагина вызывается хук ‘activate_НАЗВАНИЕПЛАГИНА’. В имени этого хука НАЗВАНИЕПЛАГИНА заменяется на имя плагина, включая опциональную поддиректорию. Например, если плагин находится в wp-content/plugins/sampleplugin/sample.php, то имя хука будет ‘activate_sampleplugin/sample.php’.
Часть 2: Бутстраппинг и __FILE__
Основная проблема здесь в том, что __FILE__
имеет разные значения в разных местах, а вам нужно конкретное значение.
Также у вас есть проблема с бутстраппингом — он должен собирать граф объектов, но вы этого не делаете. Все ваши объекты создаются сразу при их определении или внутри друг друга, что затрудняет или делает невозможным передачу им значений.
Например, я мог бы написать плагин так:
plugin.php
:
<?php
/**
* Plugin Name: Мой плагин
* Version: 0.1
*/
// этап загрузки
require_once( 'php/app.php' );
// этап бутстраппинга
$app = new App( __FILE__ );
// этап выполнения
$app->run();
Я определил все свои классы в подпапке php
и загрузил их в одном месте. Теперь PHP знает, какие у меня есть классы, их имена и т.д., но пока ничего не происходит.
Затем я создаю все свои объекты, передавая им то, что им нужно. В данном случае App
требует значение __FILE__
, поэтому я передаю его. Обратите внимание, что это создает объекты в памяти, но не выполняет никакой работы. Плагин готов к работе, но находится в фазе подготовки.
Следующим шагом может быть передача этих объектов в набор модульных тестов, но сейчас я собираюсь их запустить. Я завершил процесс бутстраппинга, и приложение готово к работе, поэтому я вызываю метод run
. В run
не нужно ничего передавать, так как все необходимое уже передано в конструкторы. В методе run
я добавляю все свои фильтры и хуки. Именно во время выполнения этих хуков запускаются все остальные части плагина.
Важная часть здесь — это четко определенная структура и передача необходимых данных на этапе создания/бутстраппинга, с четким жизненным циклом.

Спасибо за развернутый ответ. Из вашего объяснения, по крайней мере, что я понял, это то что мне нужно придумать способ, чтобы мой основной плагин знал обо всех классах, которые я написал? Например, у меня вероятно будут файлы классов в папке "inc" и еще классы в папке "admin". Я был под впечатлением, что каждый раз, когда я хочу использовать класс где-то, мне нужно делать "$var = new ClassName();" чтобы получить доступ к методам. Вот почему вы видите, что я создаю класс внутри основного класса.

вам не нужно создавать объект внутри класса, просто передавайте $var
в него, конструкторы тоже могут принимать параметры и объекты можно передавать так же как любые другие переменные. Вы можете делать так как сделали, но у этого есть недостатки. Проблема с доступом к __FILE__
- один из них, это значение должно приходить из основного файла плагина, иначе оно будет неверным, так что его нужно передавать в другие части плагина

Ладно. Спасибо. Этот ответ определенно достаточен. Теперь я знаю, что мне нужно подтянуть.

Объектно-ориентированная сторона этого вопроса несколько выходит за рамки данного сайта, я оставил вопрос открытым под предлогом правильного использования register_activation_hook
. Что касается ООП-подхода, посмотрите https://tomjn.com/2015/06/24/root-composition-in-wordpress-plugins/, но есть и другие способы реализации. Не существует единственно правильного способа сделать это - у каждого подхода есть свои преимущества и недостатки, вы могли бы написать свой плагин, используя чисто функциональное программирование в стиле Haskell

Вы можете задать константу, которая будет содержать значение __FILE__ и использовать её в любом месте ваших классов.
Пример:
main-plugin-file.php
<?php
/**
* Plugin Name: Плагин с классами
*/
define( 'PLUGIN_WITH_CLASSES__FILE__', __FILE__ );
include( 'my-class.php' );
my-class.php
<?php
class myClass {
function __construct() {
// Выполняется при активации плагина
register_activation_hook( PLUGIN_WITH_CLASSES__FILE__, [ $this, 'create_plugin_database_table' ] );
}
function create_plugin_database_table(){
//Создание таблицы в БД ...
}
}
new myClass();

Вам действительно следует регистрировать хуки активации/деактивации/удаления вне класса вашего плагина, как описано в ответе kaiser здесь, где дается гораздо более подробное объяснение темы, чем я мог бы написать.
Этого должно быть достаточно, если вы пишете этот плагин в учебных целях. Если же вам просто нужен SEO-плагин, который хорошо работает и не завален безвкусной рекламой, как Yoast, я могу порекомендовать The SEO Framework.

Том МакФарлин создал очень обширный шаблон плагина (сейчас поддерживается Девином Винсоном), полностью написанный в ООП-стиле, который вы можете использовать для создания нового плагина или просто изучить логику работы, чтобы ответить на ваш вопрос. Я использовал его для нескольких кастомных плагинов и должен сказать, что он действительно открыл мне глаза на некоторые загадки ООП.
