Использование register_activation_hook в классах

10 авг. 2017 г., 16:36:08
Просмотры: 22.2K
Голосов: 10

Я пытаюсь разработать плагин для базовой SEO-оптимизации, так как многие мои знакомые не любят использовать Yoast. Я только начал разработку плагина и создаю сообщение об активации, которое отображается пользователю при активации плагина. У меня возникли проблемы с совмещением ООП и встроенных функций WordPress, и я не уверен, где именно допускаю ошибку.

Единственный способ, которым я могу заставить это работать - создать новый экземпляр класса SEO_Plugin_Activation внутри конструктора моего основного файла, а затем вызвать метод activatePlugin в этом классе. Мне кажется, что это излишне. Я также думаю, что то, как я выполняю функции в файле класса активации, тоже не особо логично. Но сейчас это единственный способ заставить это работать.

Я не уверен, связано ли это с тем, что я не на 100% понимаю методы ООП, или с тем, что я неправильно использую API WordPress. Я привел три примера кода в следующем порядке:

  1. Основной файл плагина
  2. Файл класса, который обрабатывает требования активации
  3. То, как я надеялся это реализовать.

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();
?>

Я пытался сделать это способом, описанным в третьем скрипте, но безуспешно. На данный момент сообщение отображается правильно, без сообщений об ошибках.

4
Комментарии

Вы ищете подтверждение или правильный способ использовать ООП в WordPress? Боюсь, здесь нет канонического пути, которым можно следовать. Единственное, чем я могу поделиться, это общие принципы ООП. Например, не создавайте SEO_Plugin_Activation внутри вашего класса SEO, используйте dependency injection и передавайте его как аргумент. Также не определяйте и не используйте класс в одном файле — загрузка файла с описанием класса не должна выполнять его код, иначе невозможно будет писать unit-тесты.

Tom J Nowell Tom J Nowell
10 авг. 2017 г. 16:47:12

Спасибо за ответ. Честно говоря, мне не так важно придерживаться "100% WordPress Way". Судя по вашему комментарию, мне явно не хватает понимания принципов ООП. У меня было впечатление, что мне нужно создавать новый класс внутри класса, чтобы вызывать его методы.

Dan Zuzevich Dan Zuzevich
10 авг. 2017 г. 16:50:59

Вы можете так делать, но это менее гибко. Например, если вы захотите протестировать класс SEO, как вы замените класс SEO_Plugin_Activation на mock-объект без его изменения? В любом случае, похоже, этот вопрос возник из-за непонимания, как работает register_activation_hook.

Tom J Nowell Tom J Nowell
10 авг. 2017 г. 17:04:04

Понял, спасибо большое. Честно говоря, я не думаю, что буду проводить какие-либо тесты, так как это пока что выше моего уровня понимания.

Dan Zuzevich Dan Zuzevich
10 авг. 2017 г. 17:21:13
Все ответы на вопрос 4
6
14

Перечитав ваш вопрос, я понял проблему, и она связана с неправильным пониманием работы 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 я добавляю все свои фильтры и хуки. Именно во время выполнения этих хуков запускаются все остальные части плагина.

Важная часть здесь — это четко определенная структура и передача необходимых данных на этапе создания/бутстраппинга, с четким жизненным циклом.

10 авг. 2017 г. 17:02:24
Комментарии

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

Dan Zuzevich Dan Zuzevich
10 авг. 2017 г. 17:17:21

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

Tom J Nowell Tom J Nowell
10 авг. 2017 г. 17:25:59

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

Dan Zuzevich Dan Zuzevich
10 авг. 2017 г. 17:28:15

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

Tom J Nowell Tom J Nowell
10 авг. 2017 г. 17:28:41

Следует ли мне переформулировать вопрос или он в порядке?

Dan Zuzevich Dan Zuzevich
10 авг. 2017 г. 17:29:19

уточняющие правки всегда приветствуются, главное не меняйте смысл вопроса

Tom J Nowell Tom J Nowell
10 авг. 2017 г. 17:30:00
Показать остальные 1 комментариев
0

Вы можете задать константу, которая будет содержать значение __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();
12 сент. 2019 г. 04:33:38
2

Вам действительно следует регистрировать хуки активации/деактивации/удаления вне класса вашего плагина, как описано в ответе kaiser здесь, где дается гораздо более подробное объяснение темы, чем я мог бы написать.

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

10 авг. 2017 г. 16:52:31
Комментарии

Это было полезно, и я благодарен за ответ.

Dan Zuzevich Dan Zuzevich
10 авг. 2017 г. 17:19:23

Я проголосовал за ответ, так как это определенно полезный ресурс.

Dan Zuzevich Dan Zuzevich
11 авг. 2017 г. 16:22:44
1

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

10 авг. 2017 г. 17:38:29
Комментарии

Это очень хорошая идея. Я совсем забыл про boilerplate для плагинов. Я просматривал его раньше, но он показался мне довольно запутанным. Но я определенно собираюсь скачать его снова и использовать в качестве примера. Большое спасибо за предложение.

Dan Zuzevich Dan Zuzevich
11 авг. 2017 г. 16:20:47