Вопрос или проблема
Я в настоящее время работаю над созданием своего первого OOP плагина для WordPress.
Чтобы немного помочь со структурой, я искал и нашел шаблон, который помогает мне настроить основы.
В Main.php
есть метод, который загружает JS и CSS ресурсы для админки:
/**
* Зарегистрировать все хуки, связанные с функциональностью
* админской области плагина.
*
* @since 0.1.0
* @access private
*/
private function define_admin_hooks() {
$plugin_admin = new Admin\Controller( $this->get_plugin_name(), $this->get_version(), $this->get_plugin_path() );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
}
Пока всё хорошо. Но по мере того как мой плагин растет в сложности, кажется, что этот метод станет неуправляемым с множеством хуков.
Вот пример с настройкой CPT и добавленной страницей настроек:
/**
* Зарегистрировать все хуки, связанные с функциональностью
* админской области плагина.
*
* @since 0.1.0
* @access private
*/
private function define_admin_hooks() {
$plugin_admin = new Admin\Controller( $this->get_plugin_name(), $this->get_version(), $this->get_plugin_path() );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
$interactWithVimeo = new Admin\InteractWithVimeo();
$this->loader->add_action ( 'admin_init', $interactWithVimeo, 'setCredentials');
$cpt = new Admin\CustomPostType();
// Создать пользовательский тип записи
$this->loader->add_action ( 'init', $cpt, 'create_post_type' );
// Удалить действия строк записей
$this->loader->add_filter ( 'post_row_actions', $cpt, 'remove_row_actions', 10, 2 );
$settingsPage = new Admin\SettingsPage();
// Добавить страницу настроек в меню CPT
$this->loader->add_action ( 'admin_menu', $settingsPage, 'add_settings_page' );
}
На этом этапе я задаюсь вопросом, не будет ли лучше настроить различные классы для загрузки своих собственных хуков внутри себя, чтобы избежать нагромождения в Main.php
Что-то вроде:
/**
* Зарегистрировать все хуки, связанные с функциональностью
* админской области плагина.
*
* @since 0.1.0
* @access private
*/
private function define_admin_hooks() {
$myExensiveClassWithAlotOfMethods = new Admin\MyExensiveClassWithAlotOfMethods();
$this->loader->add_action ( 'admin_init', $myExensiveClassWithAlotOfMethods, 'init' );
}
А метод init в классе тогда содержит все хуки, которые нужны классу.
Это плохая идея? Понятно, что есть разные способы это сделать. Мне просто интересно, какой подход кажется более соответствующим шаблону.
Sally CJ поднимает хороший вопрос в своем комментарии, что, вероятно, лучше, если каждый класс будет инициализировать свои собственные действия и фильтры.
Я работал с тем же шаблоном в течение некоторого времени и создал свою собственную версию этого шаблона на GitHub.
В этой версии у меня есть Back\Hooks
и General\Hooks
, оба Singleton и передаются Loader
. Вот как выглядит General\Hooks
в одном из проектов:
<?php
namespace ACME\General;
class Hooks
{
protected static $instance;
protected $loader;
public static function getInstance($loader)
{
if (self::$instance === null) {
self::$instance = new self($loader);
}
return self::$instance;
}
protected function __construct($loader)
{
$this->loader = $loader;
}
public function run()
{
$this->addActions();
$this->addFilters();
}
protected function addActions()
{
AdvancedCustomFields\Options::add($this->loader);
$this->loader->addAction('init', Shortcode\News::class, 'init');
$this->loader->addAction('init', Shortcode\Courses::class, 'init');
$this->loader->addAction('wp_footer', WooCommerce\Foo::class, 'print_modal');
// и многие другие
}
protected function addFilters()
{
$this->loader->addFilter('the_content', Bar\Baz::class, 'filter_the_content', 999);
// и многие другие
}
}
Теперь в случае AdvancedCustomFields\Options
я уже передаю загрузчик и делаю инициализацию внутри этого класса, делая то же самое для всех остальных случаев, вероятно, будет намного чище.
General\Shortcode\News
выглядит так:
<?php
namespace ACME\General\Shortcode;
class News
{
public static function init()
{
add_shortcode('acme_news', [__CLASS__, 'render']);
}
public static function render($raw_atts = [], $content = null, $tag = '')
{
$raw_atts = array_change_key_case((array)$raw_atts, CASE_LOWER);
$atts = shortcode_atts([
'size' => 'large',
'amount' => '4',
'offset' => '0',
'exclude' => '',
], $raw_atts, $tag);
/* фильтровать $atts */
$atts['amount'] = intval($atts['amount']);
$atts['offset'] = intval($atts['offset']);
/* получить результат */
$result="";
// фактический код, выполняющий WP_Query и т.д.
// никогда не выводится напрямую, а только сохраняется в $result
return $result;
}
Ответ или решение
OOP-разработка и хуки в WordPress
Разработка плагинов для WordPress с использованием объектно-ориентированного программирования (ООП) становится всё более распространённой практикой. Использование структурированного подхода, такого как концепция хуков (hooks), является важным элементом для создания чистого и поддерживаемого кода. В условиях ваших требований стоит глубже рассмотреть, как вы можете оптимизировать организацию хуков в вашем плагине, чтобы избежать загромождения основного файла и придерживаться принципов лучшей практики.
Проблема управления хуками
Вы правильно заметили, что по мере усложнения вашего плагина метод define_admin_hooks()
может стать излишне загромождённым. Очень важно поддерживать читаемость и структурированность кода, особенно когда ваш проект расцветает и добавляются новые функции. Хуки в WordPress позволяют вам вмешиваться в процесс выполнения, что делает их мощным инструментом, однако при неправильном использовании они могут быстрее превращаться в источник путаницы.
Лучшие практики для упрощения вашего кода
-
Разделение ответственности:
- Рассмотрите возможность создания отдельных классов, которые будут отвечать за свою функциональность, и позволять им инициализировать свои собственные хуки. Таким образом, ваша структура будет более модульной и понятной.
- Например, класс
Admin\CustomPostType
может отвечать только за создание пользовательского типа записи и связанные с ним действия. Это уменьшит количество строк в вашем главном файле.
-
Использование паттерна Singleton:
- Вы можете использовать паттерн Singleton для управления экземплярами ваших классов, как вы уже начали делать с
General\Hooks
. Это обеспечивает единую точку доступа и помогает избежать создания лишних экземпляров классов, которые могут привести к конфликтам.
- Вы можете использовать паттерн Singleton для управления экземплярами ваших классов, как вы уже начали делать с
-
Инициализация хуков внутри классов:
- Как вы упомянули, классы могут сами регистрировать свои хуки, что способствует лучшей организации кода. Это также облегчит тестирование и повторное использование кода.
- Например, класс
Admin\SettingsPage
может включать методinitialize_hooks()
, который будет регистрировать все необходимые хуки в рамках одной функции.
-
Создание отдельных методов для действий и фильтров:
- Внутри каждого класса создайте отдельные методы для добавления действий (actions) и фильтров (filters), чтобы четко разделять логику. Это поможет сделать ваш код ещё более читабельным и упрощает его дальнейшее обслуживание.
Пример оптимизированной организации
Используя представленные выше методы, вот как может выглядеть ваша структура:
// В классе Admin\CustomPostType
namespace Admin;
class CustomPostType
{
public function init()
{
$this->create_post_type();
$this->remove_row_actions();
}
private function create_post_type()
{
// Логика создания пользовательского типа записи
}
private function remove_row_actions($actions, $post)
{
// Логика удаления действий
return $actions;
}
}
// В Main.php
private function define_admin_hooks()
{
$cpt = new Admin\CustomPostType();
$this->loader->add_action('init', $cpt, 'init');
}
Заключение
Ваше стремление к улучшению кода и упрощению управления хуками в WordPress является правильным шагом к созданию качественного и поддерживаемого проекта. Распределение ответственности и использование принципов ООП помогут вам предотвратить путаницу и улучшить пригодность вашего плагина для обновлений и изменений в будущем. Следуя лучшим практикам, вы не только улучшаете свой код, но и создаёте более структурированную и понятную архитектуру приложения.