Приїхавши сюди рівно через 2 роки після того, як було задано оригінальне запитання, я хочу зазначити кілька речей . (Не вимагайте від мене вказувати багато речей , коли-небудь).
Правильний гачок
Для створення класу плагінів слід використовувати належний гачок. Не існує загального правила, для якого воно є, тому що це залежить від того, що робить клас.
Використовувати дуже ранній гачок, як "plugins_loaded"
правило, не має сенсу, оскільки такий гак запускається для запитів адміністратора, фронтену та AJAX, але дуже часто пізніший гак набагато кращий, оскільки він дозволяє інстанціювати класи плагінів лише за потреби.
Наприклад, клас, який робить речі для шаблонів, може бути ініційований "template_redirect"
.
Взагалі дуже рідко виникає необхідність екзаментувати клас, перш ніж "wp_loaded"
його звільнити.
Без класу Бога
Більшість з усіх класів, які використовуються як приклади в старих відповідях, використовують клас, який називається like "Prefix_Example_Plugin"
or "My_Plugin"
... Це вказує на те, що плагін є основним класом.
Ну, якщо плагін не буде зроблений одним класом (у такому випадку іменування його після імені плагіна абсолютно розумне), щоб створити клас, який керує всім плагіном (наприклад, додавання всіх гаків, які потрібні плагіну, або інстанціювання всіх інших класів плагіна ) можна вважати поганою практикою, як приклад об’єкта бога .
В об'єктно-орієнтованому коді програмування має бути SOLID, де "S" означає "принцип єдиної відповідальності" .
Це означає, що кожен клас повинен робити одну справу. У розробці плагінів WordPress це означає, що розробникам слід уникати використання одного гака для створення основного класу плагінів, але для створення екземплярів різних класів слід використовувати різні гачки відповідно до відповідальності класу.
Уникайте гачків у конструкторі
Цей аргумент був введений в інших відповідях тут, проте я хочу зауважити цю концепцію і пов'язати цю іншу відповідь там, де це було досить широко пояснено в рамках тестування одиниці.
Майже 2015 рік: PHP 5.2 призначений для зомбі
З 14 серпня 2014 р. PHP 5.3 досяг свого кінця . Це напевно мертвий. PHP 5.4 буде підтримуватися на весь 2015 рік, це означає ще один рік, в той момент, коли я пишу.
Однак WordPress все ще підтримує PHP 5.2, але ніхто не повинен писати єдиний рядок коду, який підтримує цю версію, особливо якщо код є OOP.
Є різні причини:
- 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
}
}
Клас Frontend:
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();
}
І клас Інструменти, використовуваний двома іншими:
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
.
Таким чином, ми самі або користувачі, які використовують наш код, можуть використовувати різні реалізації одного і того ж інтерфейсу, завдяки чому наш код не поєднується з конкретним класом, а з абстракцією: саме про це йдеться в принципі Dependpend Inversion.
Однак приклад вище можна вдосконалити. Подивимося, як.
Автонавантажувач
Хороший спосіб написати краще читаний код OOP - не змішувати визначення типів (інтерфейси, класи) з іншим кодом, а кожен тип вводити у власний файл.
Це правило також є одним із стандартів кодування PSR- 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;
}
Це легко і досить добре, але це змушує реалізувати схему для кожного класу, до якого ми хочемо отримати доступ.
Більше того, багато разів такий підхід ставить на шлях потрапляння до проблеми класу god, оскільки розробники роблять доступним основний клас за допомогою цього методу, а потім використовують його для доступу до всіх інших класів.
Я вже пояснив, наскільки поганий клас богів, тому статичний підхід екземпляра - хороший шлях, коли плагіну потрібно зробити доступним один або два класи.
Це не означає, що його можна використовувати лише для плагінів, що мають лише пару класів, адже, коли правильно застосовується принцип введення залежності, можна створити досить складні програми без необхідності зробити глобально доступною велику кількість об'єктів.
Однак іноді плагінам потрібно зробити доступними деякі класи, і в цьому випадку статичний підхід екземпляра є надзвичайним.
Іншим можливим підходом є використання шаблону реєстру .
Це дуже проста його реалізація:
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, коли ми повинні використовувати зовнішній LIB (так як контейнери DI, але будь-який код , який не є частиною програми), просто завантаживши його і покласти його в нашій папці програми не вважається хорошою практикою. Навіть якщо ми є авторами цього іншого кодексу.
Розв'язка коду програми від зовнішніх залежностей знак кращої організації, підвищення надійності і поліпшення осудності коди.
Композитор , це фактичний стандарт у спільноті PHP для управління залежностями PHP. Це далеко не мейнстрім у спільноті WP, це інструмент, про який кожен розробник PHP та WordPress повинен хоча б знати, якщо не використовувати.
Ця відповідь вже розміром у книжці, щоб дозволити подальше обговорення, а також обговорення Композитора тут, мабуть, поза темою, вона згадувалася лише заради повноти.
Для отримання більш докладної інформації відвідайте сайт Composer , і це також варто дати читання цієї Minisite Куратор @Rarst .
1 PSR - це правила стандартів PHP, оприлюднені групою PHP Framework Interop
2 Композитор (бібліотека, про яку буде сказано у цій відповіді), серед іншого, також містить утиліту автозавантажувача.