Genau zwei Jahre nachdem die ursprüngliche Frage gestellt wurde, möchte ich Sie auf einige Dinge hinweisen. (Bitten Sie mich nicht , jemals auf eine Menge Dinge hinzuweisen ).
Richtiger Haken
Um eine Plugin-Klasse zu instanziieren, sollte der richtige Hook verwendet werden. Es gibt keine allgemeine Regel, für die es gilt, weil es davon abhängt, was die Klasse tut.
Die Verwendung eines sehr frühen Hooks macht "plugins_loaded"
oft keinen Sinn, da ein solcher Hook für Admin-, Frontend- und AJAX-Anfragen ausgelöst wird, aber sehr oft ist ein späterer Hook weitaus besser, da er die Instanziierung von Plugin-Klassen nur bei Bedarf ermöglicht.
Beispielsweise kann eine Klasse instanziiert werden, die Aufgaben für Vorlagen ausführt "template_redirect"
.
Im Allgemeinen ist es sehr selten, dass eine Klasse instanziiert werden muss, bevor "wp_loaded"
sie ausgelöst wird.
Keine Gottesklasse
Die meisten Klassen, die in älteren Antworten als Beispiele verwendet werden, verwenden eine Klasse mit dem Namen "like" "Prefix_Example_Plugin"
oder " "My_Plugin"
...". Dies weist darauf hin, dass es wahrscheinlich eine Hauptklasse für das Plugin gibt.
Nun, es sei denn, ein Plugin wird von einer einzelnen Klasse erstellt (in diesem Fall ist es absolut sinnvoll, es nach dem Namen des Plugins zu benennen), um eine Klasse zu erstellen, die das gesamte Plugin verwaltet (z. B. alle Hooks hinzufügen, die ein Plugin benötigt, oder alle anderen Plugin-Klassen instanziieren) ) kann als schlechtes Beispiel für einen Gottesgegenstand angesehen werden .
In der objektorientierten Programmierung sollte Code SOLID sein, wobei "S" für "Prinzip der Einzelverantwortung" steht .
Das bedeutet, dass jede Klasse eine einzige Sache machen sollte. Bei der Entwicklung von WordPress-Plugins sollten Entwickler vermeiden, einen einzelnen Hook zum Instanziieren einer Haupt- Plugin-Klasse zu verwenden. Je nach Klassenverantwortung sollten jedoch unterschiedliche Hooks zum Instanziieren verschiedener Klassen verwendet werden.
Vermeiden Sie Haken im Konstruktor
Dieses Argument wurde in anderen Antworten hier eingeführt, aber ich möchte dieses Konzept erwähnen und diese andere Antwort verknüpfen , wo es im Bereich der Komponententests ziemlich ausführlich erklärt wurde.
Fast 2015: PHP 5.2 ist für Zombies
Seit dem 14. August 2014 hat PHP 5.3 sein Ende erreicht . Es ist definitiv tot. PHP 5.4 wird für das ganze Jahr 2015 unterstützt, dh für ein weiteres Jahr, in dem ich gerade schreibe.
WordPress unterstützt zwar immer noch PHP 5.2, aber niemand sollte eine einzige Codezeile schreiben, die diese Version unterstützt, insbesondere wenn der Code OOP ist.
Es gibt verschiedene Gründe:
- PHP 5.2 ist vor langer Zeit tot. Es wurden keine Sicherheitsupdates veröffentlicht, das heißt, es ist nicht sicher
- PHP 5.3 fügte PHP viele Funktionen, anonyme Funktionen und Namespaces über alles hinzu
- Neuere Versionen von PHP sind viel schneller . PHP ist kostenlos. Die Aktualisierung ist kostenlos. Warum eine langsamere, unsichere Version verwenden, wenn Sie eine schnellere, sicherere kostenlos verwenden können?
Wenn Sie keinen PHP 5.4+ Code verwenden möchten, verwenden Sie mindestens 5.3+
Beispiel
An dieser Stelle ist es an der Zeit, ältere Antworten auf der Grundlage meiner bisherigen Ausführungen zu überprüfen.
Sobald wir uns nicht mehr um 5.2 kümmern müssen, können und sollten wir Namespaces verwenden.
Um das Prinzip der einzelnen Verantwortung besser zu erläutern, werden in meinem Beispiel drei Klassen verwendet, eine, die etwas im Frontend ausführt, eine im Backend und eine dritte, die in beiden Fällen verwendet wird.
Admin-Klasse:
namespace GM\WPSE\Example;
class AdminStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Frontend-Klasse:
namespace GM\WPSE\Example;
class FrontStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// setup class, maybe add hooks
}
}
Tools-Schnittstelle:
namespace GM\WPSE\Example;
interface ToolsInterface {
function doSomething();
}
Und eine Tools-Klasse, die von den anderen beiden verwendet wird:
namespace GM\WPSE\Example;
class Tools implements ToolsInterface {
function doSomething() {
return 'done';
}
}
Mit diesen Klassen kann ich sie mit geeigneten Hooks instanziieren. So etwas wie:
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 );
} );
Abhängigkeitsinversion & Abhängigkeitsinjektion
Im obigen Beispiel habe ich Namespaces und anonyme Funktionen verwendet, um verschiedene Klassen an verschiedenen Hooks zu instanziieren und das, was ich oben gesagt habe, in die Praxis umzusetzen.
Beachten Sie, wie mit Namespaces Klassen ohne Präfix erstellt werden können.
Ich habe ein anderes Konzept angewendet, das indirekt oben erwähnt wurde: Dependency Injection. Hierbei handelt es sich um eine Methode zur Anwendung des Dependency Inversion Principle , das "D" in der Abkürzung SOLID.
Die Tools
Klasse wird in die beiden anderen Klassen "injiziert", wenn sie instanziiert werden. Auf diese Weise ist es möglich, die Verantwortung zu trennen.
Außerdem verwenden AdminStuff
und FrontStuff
Klassen Typhinweise, um zu deklarieren, dass sie eine Klasse benötigen, die implementiert wird ToolsInterface
.
Auf diese Weise können wir selbst oder Benutzer, die unseren Code verwenden, unterschiedliche Implementierungen derselben Schnittstelle verwenden, sodass unser Code nicht an eine konkrete Klasse, sondern an eine Abstraktion gekoppelt ist: Genau darum geht es beim Prinzip der Abhängigkeitsinversion.
Das obige Beispiel kann jedoch weiter verbessert werden. Mal sehen wie.
Autoloader
Eine gute Möglichkeit, besser lesbaren OOP-Code zu schreiben, besteht darin , die Definition von Typen (Schnittstellen, Klassen) nicht mit anderem Code zu mischen und jeden Typ in einer eigenen Datei abzulegen.
Diese Regel ist auch einer der PSR-1-Codierungsstandards 1 .
Bevor man jedoch eine Klasse verwenden kann, muss man die Datei benötigen, die sie enthält.
Dies kann überwältigend sein, aber PHP bietet Dienstprogrammfunktionen zum automatischen Laden einer Klasse, wenn dies erforderlich ist. Dabei wird ein Rückruf verwendet, der eine Datei basierend auf ihrem Namen lädt.
Die Verwendung von Namespaces wird sehr einfach, da es jetzt möglich ist, die Ordnerstruktur mit der Namespace-Struktur abzugleichen.
Dies ist nicht nur möglich, sondern auch ein anderer PSR-Standard (oder besser 2: PSR-0 jetzt veraltet und PSR-4 ).
Nach diesen Standards ist es möglich, verschiedene Tools für das automatische Laden zu verwenden, ohne einen benutzerdefinierten Autoloader codieren zu müssen.
Ich muss sagen, dass WordPress-Codierungsstandards unterschiedliche Regeln für die Benennung von Dateien haben.
Wenn Entwickler Code für WordPress Core schreiben, müssen sie die WP-Regeln befolgen, aber wenn sie benutzerdefinierten Code schreiben, ist dies eine Entscheidung der Entwickler. Die Verwendung des PSR-Standards ist jedoch einfacher, wenn sie bereits geschriebene Tools 2 verwenden .
Muster für globalen Zugriff, Registrierung und Service Locator.
Eines der größten Probleme beim Instanziieren von Plugin-Klassen in WordPress ist der Zugriff von verschiedenen Teilen des Codes aus.
WordPress selbst verwendet den globalen Ansatz: Variablen werden im globalen Bereich gespeichert und sind somit überall verfügbar. Jeder WP-Entwickler gibt das Wort global
in seiner Karriere tausende Male ein.
Dies ist auch der Ansatz, den ich für das obige Beispiel verwendet habe, aber er ist böse .
Diese Antwort ist bereits viel zu lang, um das Warum näher erläutern zu können, aber das Lesen der ersten Ergebnisse im SERP für "globale Variablen böse" ist ein guter Ausgangspunkt.
Aber wie können globale Variablen vermieden werden?
Es gibt verschiedene Wege.
Einige der älteren Antworten hier verwenden den statischen Instanzansatz .
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
Es ist einfach und ziemlich gut, aber es zwingt dazu, das Muster für jede Klasse zu implementieren, auf die wir zugreifen möchten.
Darüber hinaus wird dieser Ansatz häufig zum Problem der God-Klasse, da Entwickler mit dieser Methode eine Hauptklasse zugänglich machen und dann auf alle anderen Klassen zugreifen.
Ich habe bereits erklärt, wie schlecht eine God-Klasse ist, daher ist der Ansatz mit statischen Instanzen ein guter Weg, wenn ein Plugin nur eine oder zwei Klassen zugänglich machen muss.
Dies bedeutet nicht, dass es nur für Plugins mit nur wenigen Klassen verwendet werden kann. Wenn das Prinzip der Abhängigkeitsinjektion richtig angewendet wird, können ziemlich komplexe Anwendungen erstellt werden, ohne dass eine große Anzahl global zugänglich gemacht werden muss von Objekten.
Manchmal müssen Plugins jedoch einige Klassen zugänglich machen , und in diesem Fall ist der Ansatz für statische Instanzen überwältigend.
Ein anderer möglicher Ansatz ist die Verwendung des Registrierungsmusters .
Dies ist eine sehr einfache Implementierung davon:
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;
}
}
Mit dieser Klasse ist es möglich, Objekte über eine ID im Registrierungsobjekt zu speichern. Wenn Sie also Zugriff auf eine Registrierung haben, können Sie auf alle Objekte zugreifen. Wenn ein Objekt zum ersten Mal erstellt wird, muss es natürlich zur Registrierung hinzugefügt werden.
Beispiel:
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' ) );
Das obige Beispiel verdeutlicht, dass die Registrierung global zugänglich sein muss, um nützlich zu sein. Eine globale Variable für die einzige Registrierung ist nicht sehr schlecht. Für nicht-globale Puristen ist es jedoch möglich, den statischen Instanzansatz für eine Registrierung oder eine Funktion mit einer statischen Variablen zu implementieren:
function gm_wpse_example_registry() {
static $registry = NULL;
if ( is_null( $registry ) ) {
$registry = new GM\WPSE\Example\Registry;
}
return $registry;
}
Beim ersten Aufruf der Funktion wird die Registrierung instanziiert, bei nachfolgenden Aufrufen wird sie nur zurückgegeben.
Eine andere WordPress-spezifische Methode, um eine Klasse global zugänglich zu machen, ist die Rückgabe einer Objektinstanz aus einem Filter. Etwas wie das:
$registry = new GM\WPSE\Example\Registry;
add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
return $registry;
} );
Danach wird überall die Registrierung benötigt:
$registry = apply_filters( 'gm_wpse_example_registry', NULL );
Ein anderes Muster, das verwendet werden kann, ist das Dienstlokalisierungsmuster . Es ähnelt dem Registrierungsmuster, aber Service-Locators werden mithilfe der Abhängigkeitsinjektion an verschiedene Klassen übergeben.
Das Hauptproblem bei diesem Muster besteht darin, dass Klassenabhängigkeiten ausgeblendet werden, was das Verwalten und Lesen von Code erschwert.
DI-Behälter
Unabhängig von der Methode, mit der die Registrierung oder der Service Locator global verfügbar gemacht werden, müssen Objekte dort gespeichert und vor dem Speichern instanziiert werden.
In komplexen Anwendungen, in denen es sehr viele Klassen gibt und viele von ihnen mehrere Abhängigkeiten haben, erfordert das Instanziieren von Klassen viel Code, sodass die Möglichkeit von Fehlern zunimmt: Code, der nicht existiert, kann keine Fehler enthalten.
In den letzten Jahren sind einige PHP-Bibliotheken erschienen, die PHP-Entwicklern helfen, Instanzen von Objekten einfach zu instanziieren und zu speichern und ihre Abhängigkeiten automatisch aufzulösen .
Diese Bibliotheken werden als Dependency Injection Containers bezeichnet, da sie in der Lage sind, Klassen zu instanziieren, die Abhängigkeiten auflösen, sowie Objekte zu speichern und bei Bedarf zurückzugeben, ähnlich wie ein Registrierungsobjekt.
In der Regel müssen Entwickler bei der Verwendung von DI-Containern die Abhängigkeiten für jede Klasse der Anwendung einrichten. Wenn eine Klasse im Code zum ersten Mal benötigt wird, wird sie mit den richtigen Abhängigkeiten instanziiert und dieselbe Instanz wird bei nachfolgenden Anforderungen immer wieder zurückgegeben .
Einige DI-Container sind auch in der Lage, Abhängigkeiten automatisch zu erkennen, ohne sie zu konfigurieren, jedoch mit PHP-Reflektion .
Einige bekannte DI-Container sind:
und viele andere.
Ich möchte darauf hinweisen, dass es für einfache Plugins, die nur wenige Klassen und Klassen betreffen, nicht viele Abhängigkeiten gibt, wahrscheinlich nicht sinnvoll ist, DI-Container zu verwenden: Die statische Instanzmethode oder eine global zugängliche Registrierung sind gute Lösungen, aber für komplexe Plugins Der Vorteil eines DI-Containers wird deutlich.
Selbstverständlich müssen auch DI-Containerobjekte zugänglich sein, um in der Anwendung verwendet werden zu können. Zu diesem Zweck kann eine der oben beschriebenen Methoden verwendet werden: globale Variable, statische Instanzvariable, zurückgegebenes Objekt über Filter usw.
Komponist
Die Verwendung von DI-Containern bedeutet häufig die Verwendung von Drittanbieter-Code. Heute in PHP, wenn wir eine externe lib (also nicht nur DI Container, sondern verwenden müssen , um jeden Code, der nicht Teil der Anwendung ist), einfach herunterladen und es in unserem Anwendungsordner setzen ist keine gute Praxis betrachtet. Auch wenn wir die Autoren dieses anderen Codes sind.
Einen Anwendungscode Entkoppelung von externen Abhängigkeiten ist Zeichen für eine bessere Organisation, bessere Zuverlässigkeit und bessere geistige Gesundheit des Codes.
Composer ist der De-facto- Standard in der PHP-Community zur Verwaltung von PHP-Abhängigkeiten. Weit davon entfernt, auch in der WP-Community Mainstream zu sein , ist es ein Tool, das jeder PHP- und WordPress-Entwickler zumindest kennen sollte, wenn er es nicht nutzt.
Diese Antwort ist bereits buchgroß, um eine weitere Diskussion zu ermöglichen, und die Diskussion über Composer hier ist wahrscheinlich nicht thematisch. Sie wurde nur der Vollständigkeit halber erwähnt.
Weitere Informationen finden Sie auf der Composer-Website. Es lohnt sich auch, diese von @Rarst kuratierte Minisite zu lesen .
1 PSR sind PHP-Standardregeln, die von der PHP Framework Interop Group veröffentlicht wurden
2 Composer (eine Bibliothek, die in dieser Antwort erwähnt wird) enthält unter anderem auch ein Autoloader-Dienstprogramm.