В WordPress можно встретить множество стилей программирования. Это огромный мир, который вмещает в себя разные стили и подходы.
Но если изучать ядро и качественные плагины, то можно обнаружить 3 базовых подхода к программированию классов.
Классический класс ?
Это наиболее частый и понятный метод. Используется метод __construct() для создания экземпляра класса. Но он же наиболее проблемный из-за того что многие программисты могут его не правильно готовить.
Есть 2 способа инициализации:
Анонимный:
new MyPlugin1;
Через присвоение переменной:
$myplugin1 = new MyPlugin;
Назначение
- подходит для задач где требуется наследование
- либо написание каких-то утилит, которые надо запускать в разных местах системы время от времени
Риски и ошибки
Сложный рефакторинг
Класс в котором есть сохранение состояния и много обращений типа $this — далее бывает сложно расцепить, разнести и как то перестроить.
Образуется жесткая сцепка. Очень тяжело отделять части методов и переносить между классами. Растет технический долг, копятся ошибки и т. д.
Цементирование и блокировка хуков
Часто молодые программисты блокируют хуки. Можно случайно зацементировать хуки и заблокировать позднее связывание.
Если класс вызывается через $var = new MyClass; вне глобальной области видимости, то далее все хуки, которые были прицеплены в конструкторе уже никто не сможет отключить. Методы типа remove_action перестают работать с таким классом.
Решение: использовать $GLOBALS или глобальные переменные. Первый вариант рекомендуется.
Но мне лично ни один не нравится.
Избыточные вычисления в конструкторе
Класс дает себя запустить несколько раз подряд. И каждый раз исполняется код в конструкторе. Иногда программисты умудряются туда засунуть сложные механики, которые тратят время. И потом этот класс вызывается по многу раз в разных местах системы. Создавая множество тормозов.
Пример
/**
* The "normal" way. Actions are added in a constructor. Create a new instance
* of the class to "bootstrap" the plugin.
*
* Two ways to instantiate:
* 1. anonymous:
* new MyPlugin1;
* 2. assign it to a variable
* $myplugin1 = new MyPlugin;
*
* The first makes it nearly impossible to unhook things. The second doesn't.
*
* Benefits:
* - Easy to understand and implement
* - Most documentation on using classes in plugins does this
*
* Questionable:
* - Makes it harder to unhook things?
*/
class MyPlugin1
{
public function __construct()
{
add_action('plugins_loaded', array($this, 'loaded'));
}
public function loaded()
{
// do stuff
}
}
Статический класс
Мой выбор. Этот подход использую чаще всего. За его гибкость и возможность переключения в другие стили и подходы. Часто такие механики можно встретить в WooCommerce.
Особенности
- Здорово подходит для инкапсуляции без наследования. Когда нужно сгруппировать методы и данные в рамках решаемой задачи. Но при этом надо запустить 1 раз за запрос.
- Все методы класса статические и публичные.
- Методы относительно легко отцепляются, переносятся между классами, конвертируются в функции, или весь класс относительно легко может быть переработан в обычный
- Вместо __construct() используется метод init()
- Запуск через My_Plugin2::init();
- Плохо подходит для задач с наследование, т.к. унаследование классы могут вести себя непредсказуемо
Пример
class My_Plugin2
{
public static function init()
{
add_action('plugins_loaded', array(__CLASS__, 'loaded'));
}
public static function loaded()
{
// do stuff
}
}
My_Plugin2::init();
Синглтон
Часто применяются там где нужно гарантированно иметь один экземпляр объекта в системе без дублирования.
Плюс более предсказуемая и понятная работа с состоянием объекта чем у Статического подхода.
Быва два типа старта: через init() или instance()
MyPlugin3::init();
MyPlugin3::instnace();
Пример когда нужен 1 Синглтон
class MyPlugin3
{
private static $ins;
public static function init()
{
add_action('plugins_loaded', array(__CLASS__, 'instance'));
}
public static function instance()
{
is_null(self::$ins) && self::$ins = new self;
return self::$ins;
}
private function __construct()
{
add_action('plugins_loaded', array($this, 'loaded'), 20);
}
public function loaded()
{
// do stuff
}
}
Пример образования множества Синглтонов через Абстракцию
/**
* A way to do the singleton pattern with a base class and PHP 5.3+
*
*/
abstract class Singleton
{
/**
* Container for the objects.
*
* @since 0.1
*/
private static $registry = array();
/**
* Get an instance of the current, called class.
*
* @since 0.1
* @access public
* @return object An instance of $cls
*/
public static function instance()
{
$cls = get_called_class();
!isset(self::$registry[$cls]) && self::$registry[$cls] = new $cls;
return self::$registry[$cls];
}
/**
* Init method that adds the `instance` method of the called class to the
* `plugins_loaded` hook.
*
* @since 0.1
* @uses add_action
* @return void
*/
public static function init()
{
add_action('plugins_loaded', array(get_called_class(), 'instance'));
}
/**
* Kill the __clone method.
*
* @since 0.1
*/
private final function __clone()
{
// empty
}
/**
* Subclasses must define construct, this should be where all the other
* actions/filters are added.
*
* @since 0.1
* @access protectect
*/
abstract protected function __construct();
}
class MyPlugin extends Singleton
{
protected function __construct()
{
add_action('init', array($this, 'do_stuff'));
}
public function do_stuff()
{
// do stuff
}
}
Резюме
Все 3 подхода применяются в мире WordPress. К сожалению часто бывает 1-й подход с цементированием событий. Который сильно портит возможности доработки функционала под себя и отключения хуков (remove_action & remove_filter).
Первый подход я применяю только в ситуации программирования утилит, которые нужны время от времени или когда нужно наследование.
Второй подход применяю чаще всего за его гибкость и возможность быстрого рефакторинга при необходимости.
Третий подход — применял один раз для интереса. Но в целом еще не встречал задач где он был бы нужен. Однако в WP через этот механизм работает класс WP_Post и ряд других.
Главное тут понимать что нет единственного правильного подхода. Правильно выбирать подходы адекватные задаче.
Примеры и комментарии к коду взял тут:
https://gist.github.com/chrisguitarguy/3803077
Источник: https://wpcraft.ru/2018/3-podhoda-k-programmirovaniyu-klassov-v-wordpress/