Przybywając tutaj dokładnie 2 lata po zadaniu pierwotnego pytania, chciałbym wskazać kilka rzeczy . (Nigdy nie proś mnie o wskazanie wielu rzeczy ).
Właściwy hak
Aby utworzyć instancję klasy wtyczki, należy użyć odpowiedniego haka. Nie ma ogólnej zasady, dla której jest, ponieważ zależy to od tego, co robi klasa.
Używanie bardzo wczesnego haka, jak "plugins_loaded"
często, nie ma sensu, ponieważ taki hak jest uruchamiany dla żądań administratora, interfejsu użytkownika i AJAX, ale bardzo często późniejszy hak jest znacznie lepszy, ponieważ pozwala na tworzenie klas wtyczek tylko w razie potrzeby.
Na przykład można utworzyć instancję klasy wykonującej różne czynności dla szablonów "template_redirect"
.
Ogólnie rzecz biorąc, bardzo rzadko zdarza się, że klasa musi zostać utworzona, zanim "wp_loaded"
zostanie zwolniona.
Bez klasy Boga
Większość wszystkich klas użytych jako przykłady w starszych odpowiedziach używa klasy o nazwie like "Prefix_Example_Plugin"
lub "My_Plugin"
... Oznacza to, że prawdopodobnie istnieje główna klasa wtyczki.
Cóż, chyba że wtyczka jest utworzona przez jedną klasę (w takim przypadku nadanie jej nazwy po nazwie wtyczki jest absolutnie uzasadnione), aby utworzyć klasę, która zarządza całą wtyczką (np. Dodając wszystkie zaczepy, których potrzebuje wtyczka lub tworząc instancję wszystkich pozostałych klas wtyczek) ) można uznać za złą praktykę, jako przykład boskiego przedmiotu .
W obiektowym programowaniu kod powinien być SOLIDNY, gdy „S” oznacza „zasadę pojedynczej odpowiedzialności” .
Oznacza to, że każda klasa powinna zrobić jedną rzecz. W rozwoju wtyczek WordPress oznacza to, że programiści powinni unikać używania pojedynczego haka do tworzenia głównej klasy wtyczki, ale należy używać różnych haków do tworzenia różnych klas, zgodnie z odpowiedzialnością klasy.
Unikaj haków w konstruktorze
Ten argument został wprowadzony w innych odpowiedziach tutaj, jednak chciałbym zwrócić uwagę na tę koncepcję i połączyć tę inną odpowiedź tam, gdzie została dość szeroko wyjaśniona w zakresie testów jednostkowych.
Prawie 2015: PHP 5.2 jest dla zombie
Od 14 sierpnia 2014 r. PHP 5.3 dobiegł końca . To zdecydowanie nie żyje. PHP 5.4 będzie obsługiwane przez cały 2015 rok, co oznacza, że będę pisać przez kolejny rok.
Jednak WordPress nadal obsługuje PHP 5.2, ale nikt nie powinien pisać ani jednego wiersza kodu obsługującego tę wersję, szczególnie jeśli kod jest OOP.
Istnieją różne powody:
- PHP 5.2 nie żyje dawno temu, nie wydano dla niego żadnych poprawek bezpieczeństwa, co oznacza, że nie jest bezpieczny
- PHP 5.3 dodał wiele funkcji do PHP, anonimowych funkcji i przestrzeni nazw über alles
- nowsze wersje PHP są znacznie szybsze . PHP jest darmowe. Aktualizacja jest bezpłatna. Po co używać wolniejszej, niepewnej wersji, jeśli można użyć szybszej, bezpieczniejszej wersji za darmo?
Jeśli nie chcesz używać kodu PHP 5.4+, użyj co najmniej 5.3+
Przykład
W tym momencie nadszedł czas, aby przejrzeć starsze odpowiedzi w oparciu o to, co powiedziałem do tego momentu.
Kiedy już nie będziemy musieli przejmować się 5.2, możemy i powinniśmy używać przestrzeni nazw.
Dla lepszego wyjaśnienia zasady pojedynczej odpowiedzialności, mój przykład użyje 3 klas, jednej, która robi coś na frontendie, jednej na backendie i trzeciej używanej w obu przypadkach.
Klasa administracyjna:
namespace GM\WPSE\Example;
class AdminStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Klasa frontonu:
namespace GM\WPSE\Example;
class FrontStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Interfejs narzędzi:
namespace GM\WPSE\Example;
interface ToolsInterface {
function doSomething();
}
I klasa Narzędzia, używana przez pozostałe dwa:
namespace GM\WPSE\Example;
class Tools implements ToolsInterface {
function doSomething() {
return 'done';
}
}
Po tych zajęciach mogę utworzyć ich instancję za pomocą odpowiednich haków. Coś jak:
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 );
} );
Odwrócenie zależności i wstrzyknięcie zależności
W powyższym przykładzie użyłem przestrzeni nazw i funkcji anonimowych, aby utworzyć instancję różnych klas w różnych punktach zaczepienia, stosując w praktyce to, co powiedziałem powyżej.
Zwróć uwagę, jak przestrzenie nazw pozwalają na tworzenie klas nazwanych bez żadnego prefiksu.
Zastosowałem inną koncepcję, która została wspomniana pośrednio powyżej: Wstrzykiwanie zależności , jest to jedna metoda zastosowania zasady odwrócenia zależności , „D” w akronimie SOLID.
Tools
Klasa jest „wstrzykiwany” w pozostałych dwóch klas, gdy są one instancja, więc w ten sposób możliwe jest oddzielenie odpowiedzialności.
Ponadto AdminStuff
i FrontStuff
klas używać typów parametrów zadeklarować potrzebują klasy, która implementuje ToolsInterface
.
W ten sposób my lub użytkownicy korzystający z naszego kodu mogą korzystać z różnych implementacji tego samego interfejsu, dzięki czemu nasz kod nie jest sprzężony z konkretną klasą, ale z abstrakcją: na tym właśnie polega zasada odwrócenia zależności.
Powyższy przykład można jednak ulepszyć. Zobaczmy jak.
Autoloader
Dobrym sposobem na napisanie lepiej czytelnego kodu OOP nie jest mieszanie definicji typów (interfejsów, klas) z innym kodem i umieszczanie każdego typu we własnym pliku.
Zasada ta jest również jednym ze standardów kodowania PSR-1 1 .
Jednak w ten sposób, zanim będzie można skorzystać z klasy, trzeba wymagać pliku, który ją zawiera.
Może to być przytłaczające, ale PHP udostępnia funkcje narzędziowe do automatycznego ładowania klasy, gdy jest to wymagane, za pomocą wywołania zwrotnego, które ładuje plik na podstawie jego nazwy.
Korzystanie z przestrzeni nazw staje się bardzo łatwe, ponieważ teraz można dopasować strukturę folderów do struktury przestrzeni nazw.
Jest to nie tylko możliwe, ale jest to także kolejny standard PSR (lub lepiej 2: PSR-0 jest przestarzałe i PSR-4 ).
Zgodnie z tymi standardami można korzystać z różnych narzędzi obsługujących automatyczne ładowanie, bez konieczności kodowania niestandardowego automatycznego ładowania.
Muszę powiedzieć, że standardy kodowania WordPress mają różne zasady nazywania plików.
Więc pisząc kod dla rdzenia WordPress, programiści muszą przestrzegać zasad WP, ale podczas pisania niestandardowego kodu jest to wybór programisty, ale korzystanie ze standardu PSR jest łatwiejsze w użyciu już napisanych narzędzi 2 .
Wzorce globalnego dostępu, rejestru i lokalizatora usług.
Jednym z największych problemów podczas tworzenia instancji klas wtyczek w WordPress jest sposób dostępu do nich z różnych części kodu.
Sam WordPress stosuje podejście globalne : zmienne są zapisywane w zakresie globalnym, dzięki czemu są dostępne wszędzie. Każdy programista WP wpisuje to słowo global
tysiące razy w swojej karierze.
Takie podejście zastosowałem w powyższym przykładzie, ale jest złe .
Ta odpowiedź jest już o wiele za długa, aby pozwolić mi wyjaśnić, dlaczego, ale przeczytanie pierwszych wyników w SERP dla „zmiennych globalnych zło” jest dobrym punktem wyjścia.
Ale jak można uniknąć zmiennych globalnych?
Istnieją różne sposoby.
Niektóre starsze odpowiedzi tutaj wykorzystują podejście do wystąpienia statycznego .
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
Jest to łatwe i całkiem w porządku, ale wymusza wdrożenie wzorca dla każdej klasy, do której chcemy uzyskać dostęp.
Co więcej, wiele razy takie podejście stawia drogę do wpadnięcia w kwestię klasy boga, ponieważ programiści udostępniają klasę główną za pomocą tej metody, a następnie używają jej do uzyskania dostępu do wszystkich innych klas.
Wyjaśniłem już, jak zła jest klasa boga, więc podejście do instancji statycznych jest dobrym sposobem, gdy wtyczka musi tylko udostępnić jedną lub dwie klasy.
Nie oznacza to, że można go używać tylko do wtyczek posiadających tylko kilka klas. W rzeczywistości, gdy zasada wstrzykiwania zależności jest właściwie stosowana, możliwe jest tworzenie dość złożonych aplikacji bez konieczności udostępniania dużej liczby osób na całym świecie przedmiotów.
Jednak czasami wtyczki muszą udostępnić niektóre klasy, w takim przypadku podejście do instancji statycznej jest przytłaczające.
Innym możliwym podejściem jest użycie wzorca rejestru .
Jest to bardzo prosta implementacja:
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;
}
}
Za pomocą tej klasy możliwe jest przechowywanie obiektów w obiekcie rejestru według identyfikatora, więc mając dostęp do rejestru, można mieć dostęp do wszystkich obiektów. Oczywiście, kiedy obiekt jest tworzony po raz pierwszy, należy go dodać do rejestru.
Przykład:
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' ) );
Powyższy przykład wyjaśnia, że rejestr, aby był użyteczny, musi być globalnie dostępny. Zmienna globalna dla jedynego rejestru nie jest bardzo zła, jednak dla nie-globalnych purystów możliwe jest wdrożenie metody statycznej instancji dla rejestru, a może funkcja ze zmienną statyczną:
function gm_wpse_example_registry() {
static $registry = NULL;
if ( is_null( $registry ) ) {
$registry = new GM\WPSE\Example\Registry;
}
return $registry;
}
Przy pierwszym wywołaniu funkcja utworzy instancję rejestru, a przy kolejnych wywołaniach po prostu ją zwróci.
Inną specyficzną dla WordPress metodą globalnej dostępności klasy jest zwracanie instancji obiektu z filtra. Coś takiego:
$registry = new GM\WPSE\Example\Registry;
add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
return $registry;
} );
Po tym wszystkim potrzebny jest rejestr:
$registry = apply_filters( 'gm_wpse_example_registry', NULL );
Innym wzorem, który można zastosować, jest wzorzec lokalizatora usług . Jest podobny do wzorca rejestru, ale lokalizatory usług są przekazywane do różnych klas za pomocą wstrzykiwania zależności.
Główny problem z tym wzorcem polega na tym, że ukrywa on zależności klas, co utrudnia utrzymanie i odczytanie kodu.
Pojemniki DI
Bez względu na metodę globalnego dostępu do rejestru lub lokalizatora usług, obiekty muszą być tam przechowywane, a przed ich zapisaniem muszą zostać utworzone.
W złożonych aplikacjach, w których jest dość dużo klas, a wiele z nich ma kilka zależności, tworzenie instancji klas wymaga dużej ilości kodu, więc zwiększa się możliwość błędów: w kodzie, który nie istnieje, nie może być błędów.
W ostatnich latach pojawiły się biblioteki PHP, które pomagają programistom PHP w łatwym tworzeniu instancji i przechowywaniu instancji obiektów, automatycznie rozwiązując ich zależności.
Biblioteki te znane są jako kontenery wstrzykiwania zależności, ponieważ są one w stanie tworzyć instancje klasy rozwiązujące zależności, a także przechowywać obiekty i zwracać je w razie potrzeby, działając podobnie do obiektu rejestru.
Zwykle podczas korzystania z kontenerów DI programiści muszą konfigurować zależności dla każdej klasy aplikacji, a następnie, gdy klasa jest potrzebna w kodzie, jest tworzona instancja z odpowiednimi zależnościami, a ta sama instancja jest zwracana wielokrotnie na kolejne żądania .
Niektóre kontenery DI są również w stanie automatycznie wykrywać zależności bez konfiguracji, ale przy użyciu odbicie PHP .
Niektóre dobrze znane kontenery DI to:
i wiele innych.
Chcę zauważyć, że w przypadku prostych wtyczek, które obejmują tylko kilka klas, a klasy nie mają wielu zależności, prawdopodobnie nie warto używać kontenerów DI: metoda wystąpienia statycznego lub rejestr dostępny globalnie są dobrym rozwiązaniem, ale w przypadku złożonych wtyczek korzyści z pojemnika DI stają się oczywiste.
Oczywiście nawet obiekty kontenera DI muszą być dostępne, aby mogły być używane w aplikacji, w tym celu można zastosować jedną z metod opisanych powyżej, zmienną globalną, zmienną instancji statycznej, zwracający obiekt przez filtr i tak dalej.
Kompozytor
Korzystanie z kontenera DI często oznacza używanie kodu innej firmy. W dzisiejszych czasach, w PHP, kiedy musimy użyć zewnętrznej biblioteki lib (czyli nie tylko kontenerów DI, ale dowolnego kodu, który nie jest częścią aplikacji), po prostu pobranie go i umieszczenie w naszym folderze aplikacji nie jest uważane za dobrą praktykę. Nawet jeśli jesteśmy autorami tego innego fragmentu kodu.
Oddzielenie kodu aplikacji od zewnętrznych zależności jest oznaką lepszej organizacji, lepszej niezawodności i lepszego rozsądku kodu.
Kompozytor jest de facto standardem w społeczności PHP do zarządzania zależnościami PHP. Będąc daleko w głównym nurcie również w społeczności WP, jest to narzędzie, które każdy programista PHP i WordPress powinien przynajmniej wiedzieć, jeśli nie używać.
Ta odpowiedź ma już rozmiar książki, aby umożliwić dalszą dyskusję, a także omawianie tutaj Kompozytora prawdopodobnie nie jest tematem, zostało wspomniane tylko ze względu na kompletność.
Aby uzyskać więcej informacji, odwiedź witrynę kompozytor i warto też daje się czytać do tego minisite kurator @Rarst .
1 PSR to reguły standardów PHP wydane przez PHP Framework Interop Group
2 Kompozytor (biblioteka, która zostanie wymieniona w tej odpowiedzi) zawiera między innymi także narzędzie autoloadera.