В 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