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