Лучший способ начать класс в плагине WP?

90

Я создал плагин, и, конечно же, я, я хотел пойти с хорошим подходом ОО. Теперь я создал этот класс, а затем чуть ниже создаю экземпляр этого класса:

class ClassName {

    public function __construct(){

    }
}

$class_instance = new ClassName();  

Я предполагаю, что есть более WP способ инициировать этот класс, а потом я наткнулся на людей, которые говорят, что они предпочитают иметь init()функцию, а не функцию __construct(). И точно так же я нашел несколько человек, использующих следующий хук:

class ClassName {

    public function init(){

    }
}
add_action( 'load-plugins.php', array( 'ClassName', 'init' ) );

Что обычно считается лучшим способом создания экземпляра класса WP при загрузке и использования его в качестве глобальной переменной?

ПРИМЕЧАНИЕ. В качестве интересного побочного замечания я заметил, что хотя его register_activation_hook()можно вызвать изнутри __construct, его нельзя вызвать изнутри, init()используя второй пример. Возможно, кто-то мог бы просветить меня по этому вопросу.

Редактировать: Спасибо за все ответы, ясно, что есть довольно много споров о том, как обрабатывать инициализацию внутри самого класса, но я думаю, что в целом есть довольно хороший консенсус, который add_action( 'plugins_loaded', ...);является лучшим способом на самом деле начать его ...

Редактировать: просто, чтобы запутать вещи, я также видел, как это используется (хотя я сам не использовал бы этот метод, потому что превращение классного ОО в функцию, похоже, побеждало его):

// Start up this plugin
add_action( 'init', 'ClassName' );
function ClassName() {
    global $class_name;
    $class_name = new ClassName();
}
kalpaitch
источник
1
Что касается последнего редактирования, если оно содержится в том же файле плагина, что и класс, оно становится несколько бесполезным. Вы можете также создать экземпляр класса согласно методу, который я описал. Даже если это в отдельном файле, это все еще несколько бессмысленно. Единственный вариант использования, который я вижу, - это если вы хотите создать функцию-оболочку, которая позволяет создавать экземпляры класса вне ваших файлов плагинов, в темах и так далее. Тем не менее, я должен был бы спросить, какая логика скрывается за этим, потому что правильное использование условных выражений и ловушек должно позволить точный контроль зерна над созданием экземпляров, позволяя вам сосредоточиться на использовании плагина.
Адам
Я с этим согласен, но я подумал, что это стоит добавить, поскольку я нашел это в паре плагинов WP.
Кальпаич

Ответы:

60

Хороший вопрос, есть ряд подходов, и это зависит от того, чего вы хотите достичь.

Я часто делаю;

add_action( 'plugins_loaded', array( 'someClassy', 'init' ));

class someClassy {

    public static function init() {
        $class = __CLASS__;
        new $class;
    }

    public function __construct() {
           //construct what you see fit here...
    }

    //etc...
}

Более подробный пример, появившийся в результате недавних дискуссий по этой самой теме в чате, можно увидеть в этом документе члена WPSE toscho .

Пустой конструктор подход.

Вот выдержка из преимуществ / недостатков, взятых из вышеупомянутой сущности, которая полностью иллюстрирует подход пустого конструктора.

  • Преимущества:

    • Модульные тесты могут создавать новые экземпляры без автоматической активации каких-либо ловушек. Нет синглтона.

    • Глобальная переменная не требуется.

    • Тот, кто хочет работать с экземпляром плагина, может просто вызвать T5_Plugin_Class_Demo :: get_instance ().

    • Легко деактивировать.

    • Все еще настоящий ООП: никакие методы работы не являются статичными.

  • Недостаток:

    • Может быть, сложнее читать?

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


примечание: мне нужно найти основной пример из toscho, который провел 3 или 4 сравнения того, как создать экземпляр класса в плагине, который рассматривал плюсы и минусы каждого, который вышеупомянутая ссылка была предпочтительным способом сделать это, но другие примеры хорошо контрастируют с этой темой. Надеюсь, у toscho все еще есть это в файле.

Примечание: В ПРМЭ ответа на эту тему с соответствующими примерами и сравнениями. Также лучшее решение, например, класс в WordPress.

add_shortcode( 'baztag', array( My_Plugin::get_instance(), 'foo' ) );
class My_Plugin {

    private $var = 'foo';

    protected static $instance = NULL;

    public static function get_instance() {

        // create an object
        NULL === self::$instance and self::$instance = new self;

        return self::$instance; // return the object
    }

    public function foo() {

        return $this->var; // never echo or print in a shortcode!
    }
}
Адам
источник
какая разница между add_action ('plugins_loaded', ...); и add_action ('load-plugins.php', ...); Пример, который я взял, использовал последний
kalpaitch
1
Из того, что я понимаю, load-plugins.php, хотя он и работает, связан с основным update.phpфайлом и не является частью обычных действий по умолчанию, на которые следует полагаться, когда речь идет о последовательности событий, которые запускаются во время инициализации, и по этой причине я предпочитаю использовать те крючки, которые применяются, в этом случае plugins_loaded. Это то, что я часто называю быстрым снимком того, что происходит при действии . Мое объяснение не является полным во всей полноте.
Адам
4
Мне нравится этот синглтоноподобный подход. Однако я подвергаю сомнению использование plugins_loaded в качестве инициализирующего действия. Этот хук предназначен для запуска после загрузки всех плагинов. Подключившись к нему, вы как бы угоняете этот хук и можете столкнуться с конфликтами или проблемами последовательности запуска с другими плагинами или темами, которые подключаются к plugins_loaded. Я бы не стал вмешиваться в какие-либо действия для запуска вашего метода инициализации Архитектура плагина была разработана для работы в режиме реального времени, а не в действии.
Том Оджер
2
Обратите внимание, что если вы используете, register_activation_hook()вам нужно вызвать эту функцию до того, как plugins_loadedдействие будет запущено.
Герт
1
В качестве дополнительной информации см. Этот пост от @mikeschinkel и dicuss в комментариях. hardcorewp.com/2012/…
bueltge
79

Прибыв сюда ровно через 2 года после того, как был задан первоначальный вопрос, я бы хотел отметить несколько вещей . (Не проси меня указывать на многое , никогда)

Правильный крюк

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

Использование очень ранних хуков типа like "plugins_loaded"часто не имеет смысла, потому что подобный хук запускается для запросов admin, frontend и AJAX, но очень часто поздний хук гораздо лучше, потому что он позволяет создавать экземпляры классов плагинов только при необходимости.

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

Вообще говоря, очень редко нужно создавать экземпляр класса до того, "wp_loaded"как его уволят.

Нет бога класса

Большинство классов, используемых в качестве примеров в более старых ответах, используют класс с именем like "Prefix_Example_Plugin"или "My_Plugin"... Это указывает на то, что, вероятно, существует основной класс для плагина.

Хорошо, если плагин не сделан одним единственным классом (в этом случае его имя после имени плагина абсолютно разумно), чтобы создать класс, который управляет всем плагином (например, добавление всех хуков, которые нужны плагину, или создание экземпляров всех других классов плагинов). ) можно считать плохой практикой, как пример объекта бога .

В объектно-ориентированном программном коде, как правило, должен быть твердым, где «S» означает «принцип единой ответственности» .

Это означает, что каждый класс должен делать одну вещь. В разработке плагинов WordPress это означает, что разработчики должны избегать использования единственного хука для создания экземпляра основного класса плагина, но разные хуки должны использоваться для создания экземпляров разных классов в соответствии с ответственностью класса.

Избегайте хуков в конструкторе

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

Почти 2015: PHP 5.2 для зомби

С 14 августа 2014 года PHP 5.3 достиг конца своей жизни . Это определенно мертвый. PHP 5.4 будет поддерживаться на весь 2015 год, то есть, на тот момент, когда я пишу, еще один год.

Тем не менее, WordPress по-прежнему поддерживает PHP 5.2, но никто не должен писать ни одной строки кода, поддерживающей эту версию, особенно если код является ООП.

Есть разные причины:

  • PHP 5.2 давно мертв, для него не выпущены исправления безопасности, это означает, что он небезопасен
  • PHP 5.3 добавил множество функций в PHP, анонимные функции и пространства имен über alles
  • Новые версии PHP намного быстрее . PHP бесплатный. Обновление это бесплатно. Зачем использовать более медленную, небезопасную версию, если вы можете использовать более быструю, более безопасную версию бесплатно?

Если вы не хотите использовать код PHP 5.4+, используйте как минимум 5.3+

пример

На данный момент пришло время рассмотреть более старые ответы, основанные на том, что я сказал до здесь.

Если нам больше не нужно заботиться о 5.2, мы можем и должны использовать пространства имен.

Для лучшего объяснения принципа единой ответственности в моем примере будут использоваться 3 класса: один, который делает что-то на внешнем интерфейсе, один на внутреннем и третий, используемый в обоих случаях.

Админ класс:

namespace GM\WPSE\Example;

class AdminStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}

Фронтенд класс:

namespace GM\WPSE\Example;

class FrontStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}

Интерфейс инструментов:

namespace GM\WPSE\Example;

interface ToolsInterface {

   function doSomething();

}

И класс Tools, используемый двумя другими:

namespace GM\WPSE\Example;

class Tools implements ToolsInterface {

   function doSomething() {
      return 'done';
   }

}

Имея эти классы, я могу создавать их экземпляры, используя правильные хуки. Что-то вроде:

require_once plugin_dir_path( __FILE__ ) . 'src/ToolsInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'src/Tools.php';

add_action( 'admin_init', function() {

   require_once plugin_dir_path( __FILE__ ) . 'src/AdminStuff.php';
   $tools = new GM\WPSE\Example\Tools;
   global $admin_stuff; // this is not ideal, reason is explained below
   $admin_stuff = new GM\WPSE\Example\AdminStuff( $tools ); 
} );

add_action( 'template_redirect', function() {

   require_once plugin_dir_path( __FILE__ ) . 'src/FrontStuff.php';
   $tools = new GM\WPSE\Example\Tools;
   global $front_stuff; // this is not ideal, reason is explained below
   $front_stuff = new GM\WPSE\Example\FrontStuff( $tools );    
} );

Инверсия зависимости и инъекция зависимости

В приведенном выше примере я использовал пространства имен и анонимные функции для создания экземпляров разных классов с разными хуками, применяя на практике то, что я сказал выше.

Обратите внимание, как пространства имен позволяют создавать классы с именами без префиксов.

Я применил другую концепцию, которая была косвенно упомянута выше: внедрение зависимости , это один из методов применения принципа зависимости зависимости , «D» в аббревиатуре SOLID.

ToolsКласс «впрыскивается» в двух других классах , когда они инстанцируется, так что в этом случае можно разделить ответственность.

Кроме того, AdminStuffи FrontStuffклассы используют подсказки типов, чтобы объявить, что им нужен класс, который реализует ToolsInterface.

Таким образом, мы или пользователи, которые используют наш код, могут использовать разные реализации одного и того же интерфейса, что делает наш код не связанным с конкретным классом, а с абстракцией: это именно то, о чем говорит принцип инверсии зависимости.

Однако приведенный выше пример может быть дополнительно улучшен. Посмотрим как.

автопогрузчик

Хороший способ написать более читаемый код ООП - это не смешивать определения типов (интерфейсы, классы) с другим кодом, а помещать каждый тип в отдельный файл.

Это правило также является одним из стандартов кодирования PSR-1 1 .

Тем не менее, перед тем, как использовать класс, нужно запросить файл, который его содержит.

Это может быть ошеломляющим, но PHP предоставляет служебные функции для автоматической загрузки класса, когда это требуется, используя обратный вызов, который загружает файл на основе его имени.

Использование пространств имен становится очень простым, потому что теперь можно сопоставить структуру папок со структурой пространства имен.

Это не только возможно, но и является еще одним стандартом PSR (или лучше 2: PSR-0 теперь не рекомендуется, а PSR-4 ).

Следуя этим стандартам, можно использовать различные инструменты, которые обрабатывают автозагрузку, без необходимости кодировать собственный автозагрузчик.

Я должен сказать, что стандарты кодирования WordPress имеют разные правила именования файлов.

Поэтому при написании кода для ядра WordPress разработчики должны следовать правилам WP, но при написании пользовательского кода это выбор разработчика, но использование стандарта PSR проще в использовании уже написанных инструментов 2 .

Шаблоны глобального доступа, реестра и поиска сервисов.

Одна из самых больших проблем при создании экземпляров классов плагинов в WordPress - как получить к ним доступ из различных частей кода.

Сам WordPress использует глобальный подход: переменные сохраняются в глобальной области видимости, делая их доступными везде. Каждый разработчик WP набирает слово globalтысячи раз в своей карьере.

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

Этот ответ уже слишком длинный, чтобы я мог объяснить, почему, но чтение первых результатов в поисковой выдаче для «глобальных переменных зла» является хорошей отправной точкой.

Но как можно избежать глобальных переменных?

Есть разные способы.

Некоторые из старых ответов здесь используют подход статического экземпляра .

public static function instance() {

  if ( is_null( self::$instance ) ) {
    self::$instance = new self;
  }

  return self::$instance;
}

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

Более того, во многих случаях этот подход позволяет решить проблему классов бога, поскольку разработчики делают доступным основной класс с помощью этого метода, а затем используют его для доступа ко всем другим классам.

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

Это не означает, что его можно использовать только для плагинов, имеющих всего пару классов. Фактически, при правильном использовании принципа внедрения зависимостей можно создавать довольно сложные приложения без необходимости делать глобально доступным большое количество объектов.

Однако иногда плагинам необходимо сделать доступными некоторые классы, и в этом случае подход статического экземпляра является подавляющим.

Другой возможный подход заключается в использовании шаблона реестра .

Это очень простая реализация:

namespace GM\WPSE\Example;

class Registry {

   private $storage = array();

   function add( $id, $class ) {
     $this->storage[$id] = $class;
   }

   function get( $id ) {
      return array_key_exists( $id, $this->storage ) ? $this->storage[$id] : NULL;
   }

}

Используя этот класс, можно хранить объекты в объекте реестра по идентификатору, поэтому, имея доступ к реестру, можно получить доступ ко всем объектам. Конечно, когда объект создается впервые, его необходимо добавить в реестр.

Пример:

global $registry;

if ( is_null( $registry->get( 'tools' ) ) ) {
  $tools = new GM\WPSE\Example\Tools;
  $registry->add( 'tools', $tools );
}

if ( is_null( $registry->get( 'front' ) ) ) {
  $front_stuff = new GM\WPSE\Example\FrontStuff( $registry->get( 'tools' ) );    
  $registry->add( 'front', front_stuff );
}

add_action( 'wp', array( $registry->get( 'front' ), 'wp' ) );

Приведенный выше пример ясно показывает, что для того, чтобы реестр был полезным, он должен быть общедоступным. Глобальная переменная для единственного реестра не так уж плоха, однако для неглобальных пуристов можно реализовать подход статического экземпляра для реестра или, возможно, функцию со статической переменной:

function gm_wpse_example_registry() {
  static $registry = NULL;
  if ( is_null( $registry ) ) {
    $registry = new GM\WPSE\Example\Registry;
  }
  return $registry;
}

При первом вызове функции она создает экземпляр реестра, а при последующих вызовах просто возвращает его.

Еще один специфичный для WordPress метод, делающий класс глобально доступным, - это возвращение экземпляра объекта из фильтра. Что-то вроде этого:

$registry = new GM\WPSE\Example\Registry;

add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
  return $registry;
} );

После этого везде нужен реестр:

$registry = apply_filters( 'gm_wpse_example_registry', NULL );

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

Основная проблема с этим шаблоном состоит в том, что он скрывает зависимости классов, затрудняя поддержку и чтение кода.

DI Контейнеры

Независимо от метода, используемого для обеспечения глобального доступа к реестру или локатору службы, объекты должны храниться там, а перед тем, как они будут сохранены, их необходимо создать.

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

В последние годы появилось несколько библиотек PHP, которые помогают разработчикам PHP легко создавать экземпляры и хранить экземпляры объектов, автоматически разрешая их зависимости.

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

Обычно при использовании DI-контейнеров разработчикам приходится устанавливать зависимости для каждого класса приложения, и затем в первый раз, когда в коде необходим класс, он создается с надлежащими зависимостями, и один и тот же экземпляр возвращается снова и снова при последующих запросах. ,

Некоторые DI-контейнеры также способны автоматически обнаруживать зависимости без конфигурации, но с использованием PHP-отражения .

Некоторые хорошо известные контейнеры DI:

и много других.

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

Конечно, даже объекты-контейнеры DI должны быть доступны для использования в приложении, и для этого можно использовать один из методов, описанных выше, глобальную переменную, статическую переменную экземпляра, возвращение объекта через фильтр и так далее.

Композитор

Использовать DI-контейнер часто означает использование стороннего кода. В настоящее время в PHP, когда нам нужно использовать внешнюю библиотеку (то есть не только DI-контейнеры, но и любой код, который не является частью приложения), просто загрузить его и поместить в папку нашего приложения не считается хорошей практикой. Даже если мы являемся авторами этого другого куска кода.

Отделение кода приложения от внешних зависимостей является признаком лучшей организации, лучшей надежности и лучшей работоспособности кода.

Composer , является де-факто стандартом в сообществе PHP для управления зависимостями PHP. Это далеко не только мейнстрим в WP сообществе, но и инструмент, который должен знать каждый разработчик PHP и WordPress, если не использовать.

Этот ответ уже размером с книгу, чтобы можно было продолжить обсуждение, а также обсуждение Composer здесь, вероятно, не по теме, его упоминали только для полноты картины.

Для получения дополнительной информации посетите сайт Composer, а также стоит прочитать этот мини-сайт, созданный @Rarst .


1 PSR - это стандарты стандартов PHP, выпущенные PHP Framework Interop Group

2 Composer (библиотека, которая будет упомянута в этом ответе) среди прочего также содержит утилиту автозагрузки.

gmazzap
источник
1
Дополнительное замечание, что PHP 5.3 также является концом жизни. Ответственный хост предложит по крайней мере 5,4 как вариант, если не по умолчанию
Том Дж. Новелл
2
«С 14 августа 2014 года PHP 5.3 подошел к концу. Он определенно мертв». Является ли первая строка в «PHP 5.2 для зомби» @TomJNowell
gmazzap
3
Просто интересно, как бы вы указали на «много вещей» ? ;-)
MikeSchinkel
В попытках избежать глобальных переменных мне нравится идея шаблона Registry с функцией и статической переменной. При первом вызове функции она создает экземпляр реестра, а при последующих вызовах просто возвращает его.
Майкл Эклунд
10

Я использую следующую структуру:

Prefix_Example_Plugin::on_load();

/**
 * Example of initial class-based plugin load.
 */
class Prefix_Example_Plugin {

    /**
     * Hooks init (nothing else) and calls things that need to run right away.
     */
    static function on_load() {

        // if needed kill switch goes here (if disable constant defined then return)

        add_action( 'init', array( __CLASS__, 'init' ) );
    }

    /**
     * Further hooks setup, loading files, etc.
     *
     * Note that for hooked methods name equals hook (when possible).
     */
    static function init(  ) {


    }
}

Примечания:

  • определил место для вещей, которые нужно бежать сразу
  • отключить / переопределить твики легко (отцепить один initметод)
  • Я не думаю, что когда-либо использовал / нуждался в объекте класса плагина - требует его отслеживания и т.д .; это действительно поддельное пространство имен по назначению, а не ООП (большую часть времени)

Отказ от ответственности Я еще не использую модульные тесты ( так много вещей на myplate ) и слышу, что статический может быть менее предпочтительным для них. Сделайте свое исследование по этому вопросу, если вам нужно проверить его.

Rarst
источник
3
Я знаю, что людям, которые занимаются модульным тестированием, действительно не нравятся статические / одноэлементные решения. Я думаю, что если вы полностью понимаете, чего вы пытаетесь достичь с помощью статики, и, по крайней мере, знаете о последствиях этого, то вполне возможно реализовать такие методы. Хорошие темы, связанные с этим, в переполнении стека
Адам
Это заставило меня задуматься. Так зачем тогда использовать классы, а не просто возвращаться к простым префиксным функциям. Мы делаем это только для того, чтобы иметь более чистые имена функций / методов? Я имею в виду то, что они вложены в «статические» b4, это намного удобнее для чтения? Вероятность возникновения конфликта имен примерно такая же, как и для одного имени класса, если вы используете префикс propper или я что-то упустил?
Джеймс Митч
1
@JamesMitch да, полностью статические методы - это в основном просто функции с поддельным пространством имен, которые используются в WP. Однако даже в этом случае классы имеют некоторые преимущества перед чистыми функциями, такие как автозагрузка и наследование. В последнее время я перешел от статических методов к реальным экземплярам объектов, организованным контейнером внедрения зависимостей.
Первый день
3

Все зависит от функциональности.

Однажды я создал плагин, который регистрировал скрипты, когда вызывался конструктор, поэтому мне пришлось его wp_enqueue_scriptsзацепить.

Если вы хотите вызвать его, когда ваш functions.phpфайл загружен, вы можете создать экземпляр самостоятельно, $class_instance = new ClassName();как вы упомянули.

Вы можете рассмотреть скорость и использование памяти. Я ничего об этом не знаю, но могу представить, что в некоторых случаях есть неназванные хуки. Создав свой экземпляр на этом хуке, вы можете сэкономить некоторые ресурсы сервера.

Тим С.
источник
Круто, спасибо за это, я полагаю, есть два момента и к вышеуказанному вопросу. Другое - пригодность __construct или лучший способ инициализации класса init ().
Кальпаич
1
Что ж, я бы выбрал статический init()метод, чтобы экземпляр класса вызывался в области видимости класса, а не в другой области видимости, где вы могли бы перезаписать существующие переменные.
Тим С.
0

Я знаю, что это пара лет, но в то же время php 5.3 поддерживает анонимные методы , поэтому я придумал это:

add_action( 'plugins_loaded', function() { new My_Plugin(); } );

и почему-то мне это нравится больше всего. Я могу использовать обычные конструкторы, и мне не нужно определять какие-либо методы "init" или "on_load", которые портят мои структуры ООП.

Басти
источник