WP Plugins Part 2: Autoload und Struktur

WP Plugins Part 2: Autoload und Struktur
Im zweiten Part stellen wir die Weichen für unser Plugin. Wir treffen einige Entscheidung zur grundlegenden Struktur. Und auf dieser Basis legen wir uns dann einen Autoloader an, der die Arbeit einfacher macht.

Inhaltsverzeichnis

Einleitung

Wir haben im ersten Teil die Hauptdatei unseres Plugins erstellt. Jetzt wollen wir die nächsten Schritte gehen. Aber bevor wir ganz diese konkret gehen müssen wir uns einige Gedanken machen wohin wir gehen wollen.

Das bedeutet den weiteren Aufbau des Plugins zu planen. Dazu gehören verschiedene Themen wie das Einbinden und Laden von anderen PHP-Dateien. Hier ist das Autoloading ein wichtiger Stichpunkt. Genauso müssen wir uns darüber klar werden, ob unser Plugin in Zukunft nach Prinzipien der Objektorientierung aufgebaut sein wird oder Funktional. Auch die weitere Ordnerstruktur des Plugins sollten wir zu diesem Zeitpunkt festlegen. Auch wenn sie mit späteren Funktionen und Inhalten natürlich erweitert werden kann.

Und das ist vielleicht auch der wichtigste Punkt bei diesen Überlegungen. Wie bauen wir die Boilerplate auf, damit sie später beliebig erweiterbar ist.

Autoloader, Struktur & Namespacing

Um Entscheidungen zu treffen, stellen wir uns folgende Fragen:

  • Wie können wir das Plugin aufbauen damit es gut erweiterbar ist?
  • Wie können wir den gesamten Aufbau wartungsfreundlich gestalten?
  • Objektorientierung oder Funktionsorientiert?
  • Namespaces oder nicht?

Objektorientierung?

Der Unterschied zwischen Objektorientierung und Funktionaler Programmierung ist ziemlich einfach. Bei einer funktionalen Orientierung gibt es keine Klassen, sondern nur PHP-Funktionen. Bei der Objektorientierung werden die Funktionen in Klassen gekapselt und lassen sich über Objekte beliebig oft instanzieren.

Zu viele Informationen auf einmal? Okay, ich gebe zwei kurze Beispiele.

Der erste Part ist rein Funktional. Wir haben hier zwei Funktionen definiert. Die erste Funktion ist eine Funktion mit dem Namen init, die eine zweite Funktion mit dem Namen intern aufruft.

Am Ende wird die Funktion init durch einen Aufruf ausgeführt. Dieser Aufbau ist stark vereinfacht. Und auch die funktionale Programmierung ist eher simpel. Das bedeutet auch, dass mit steigendem Funktionsumfang unseres Plugins die Funktionsnamen immer ausgefallener und komplizierter werden. Irgendwann haben wir dann einen riesigen Berg an Funktionen mit ausgefallenen Namen wie my_special_secret_undercover_init und das ist dann irgendwann gar nicht mehr so schick.

function init(){
   $myrun = intern(); 
  return $myrun;  
}

function intern(){
    return "Ich bin ein Plugin";
}

init();

Der zweite Teil ist schon deutlich charmanter. Und ja, ich bin hier ein bisschen voreingenommen. Wir erstellen eine Klasse mit dem Namen myplugin. Diese Klasse enthält dann die Funktionen init und intern, wie sie auch im Funktionalen Aufbau zu finden waren.

Am Schluss wird dann der Variable $myplugin unsere zuvor erstellte Klasse zugewiesen. Damit haben wir uns eine Instanz unserer Klasse erzeugt. Nun können wir die Funktion init ausführen und damit das selbe Ergebnis erzeugen wie im funktionalen Aufbau. Bei einer Klasse haben wir außerdem noch die Möglichkeit die Sichtbarkeit der Variablen und Funktionen innerhalb der Klasse zu definieren.

Hier haben wir in unseren Beispielen schon public und private.

  • public >> Diese Funktion kann von überall aufgerufen werden.
  • private >> Diese Funktion kann nur innerhalb der eigenen Klasse genutzt werden.
  • protected >> Diese Funktion kann nur innerhalb der Klasse oder innerhalb vererbter Klassen aufgerufen werden.

Weitere Informationen dazu sind hier zu finden.

class myplugin{
    
    public function init(){
      $myrun = $this->intern();
      return $myrun;
    }
    
    private function intern(){
        return "Ich bin ein Plugin";
    }
    
}

$myplugin = new myplugin();
$myplugin->init();

Zu praktischen Inhalten, die auf unser Plugin und diese Boilerplate bezogen sind, kommen wir auch bald. Hier geht es noch um die Struktur, wofür ein grundsätzliches Verständnis wichtig ist.

Jetzt wo wir uns darüber klar geworden sind, dass Objektorientierung uns später einen viel größeren Gestaltungsspielraum bei der Ausgestaltung unseres Plugins lässt und diese Art der Programmierung auch ein gängiger Standard ist. Machen wir an diesen Punkt unserer Checkliste schonmal einen Haken.

Wir wollen einen objektorientierten Aufbau unseres Plugins.

Namespace oder nicht?

Nachdem wir uns für die Objektorientierung entschieden haben, müssen wir nun die nächste Entscheidung treffen. Denn in PHP haben wir die Möglichkeit Namespaces zu definieren.

Ähnlich wie es die Klassen bei einzelnen Funktionen ermöglichen, dass wir den Namen einer Funktion mehrfach verwenden können in verschiedenen Klassen, ermöglichen es uns Namespaces den Namen einer Klasse in verschiedenen Namespaces öfter zu vergeben. Nehmen wir unser oberes Beispiel mit der Klasse myplugin. Stellen wir uns vor wir haben diese Klasse nun an verschiedenen Stellen mit unterschiedlichen Ausprägungen. Aber damit wir einen besseren Überblick haben, soll die Klasse exakt gleich heißen.

namespace main;
class myplugin{
    
    public function init(){
      $myrun = $this->intern();
      return $myrun;
    }
    
    private function intern(){
        return "Ich bin die Hauptfunktion";
    }
    
}

$myplugin = new myplugin();
$myplugin->init();
namespace helping;
class myplugin{
    
    public function init(){
      $myrun = $this->intern();
      return $myrun;
    }
    
    private function intern(){
        return "Ich bin die Hilfe";
    }
    
}

$myplugin = new myplugin();
$myplugin->init();

Da unsere fast identischen Klassen aber in verschiedenen Namespaces liegen. Hier main und helping, haben wir kein Problem damit. Würden beide Klassen im selben Namespace liegen und geladen werden, würde es eine Fehlermeldung geben, dass diese Klasse schon existiert.

Ich finde, dass ist ein Konstrukt welches eine Vielzahl von Möglichkeiten bietet. Und da Namespaces ebenso zum gängigen Standard gehören, sollten wir diesen Aspekt in unsere Planung aufnehmen.

Unser Plugin soll einen eigenen Namespace und entsprechende Unterteilung haben.

Unsere Struktur

Nun aber genug der Theorie. Kommen wir zurück zu unserem Plugin, unserer Boilerplate.

Machen wir uns konkrete Gedanken über die Struktur, die unsere Vorlage für Plugins später haben soll:

  1. Wir haben den Plugin Updatechecker schon in unserer Hauptdatei als Optionalen Part eingefügt. Daher bringe ich diesen Part hier direkt mal auch an. Wir brauchen einen Bereich in unserem Plugin, in dem wir externe Libarys oder Scripte ablegen, die nicht von uns stammen. Die wir also nur benutzen, um uns die Arbeit an etwas eigenem zu ersparen. Und um das Rad nicht neu erfinden zu müssen. Daher lege ich direkt im Ordner boilerplate-master den Ordner lib an. In diesem Ordner werden dann alle externen Scripte abgelegt, damit wir sie von dort laden können.
  2. Der zweite Aspekt ist ebenso relevant. Wir haben ja im Web neben PHP auch andere Dateitypen. Da wären insbesondere CSS und Javascript – Dateien. Damit verbunden sind aber auch Bilder oder andere Abhängigkeiten, die noch auftauchen können. Damit wir nicht alles bunt miteinander vermischen und später im selben Ordner alle Dateitypen quer durcheinander vorliegen haben, schaffen wir uns hierfür einen Extraordner. Wir nennen diesen Ordner statics. Dieser Name kann aber auch beliebig anders heißen. dist ist auch noch ein häufiger Name.
  3. Der dritte Punkt ist am Ende das Prunkstück unseres Plugins. Nämlich die eigentliche Funktion. Hierfür erstellen wir ebenso auf der ersten Ebene den Ordner app in dem aber gleich noch eine weitere Unterteilung vorgenommen wird um unsere Anwendung sinnvoll zu strukturieren. Direkt in dem Ordner app erstellen wir einen weiteren Ordner den ich hier classes nennen werde. Dieser Ordner beinhaltet alle Klassen unserer Anwendung. Also jeden Aspekt, der Objektorientiert aufgebaut ist.
  4. Der Übersicht wegen wird in diesem Stichpunkt geschaut, welche Ordner wir noch innerhalb des classes Ordners benötigen werden. Hier nehmen wir erstmal eine dreiteilung vor. Wir erstellen die Ordner core, init und model. Der Ordner Core steht hierbei für alle essentiellen Funktionen unserer Anwendung. Der Ordner Init für die Bestandteile, die aktiv initalisiert werden müssen. Und der Ordner Model bildet den Bereich der Datenbank ab, auch wenn wir hier diverse Hilfen von WordPress haben. Zu diesen Hilfen aber später.

Der Autoloader

Nun haben wir uns eine Struktur erdacht. Jetzt müssen wir nur noch dafür sorgen, dass wir uns die Arbeit in Zukunft nicht zu schwer machen. Damit wir alle Klassen und Dateien benutzen können müssen diese in unser Hauptscript eingebunden sein.

Wir nutzen dafür das Autoloading. Wir wollen es mit Namespaces und Objektorientiert machen. Nun legen wir uns eine PHP-Datei mit dem Namen autoload.php an.  Wir haben diese ja schon in unserer Hauptdatei definiert. Die Datei wird dort abgelegt wo auch die boilerplate-master.php abgelegt wurde.

<?php
spl_autoload_register(function ($class) {
    $prefix = 'myboilerplate\\';
    $base_dir = __DIR__ . '/';

    // does the class use the namespace prefix?
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }
    $relative_class = substr($class, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    if (file_exists($file)) {
        require_once $file;
    }

    $file = $base_dir . str_replace('\\', '/', strtolower($relative_class)) . '.php';
    if (file_exists($file)) {
        require_once $file;
    }
});

Erklärung zu diesem Autoloader. Mit der Funktion spl_autoload_register wird der eigene Autoloader definiert und registriert. Als erster Parameter wird die Funktion übergeben, der Callback. Der Callback hat als Parameter den Klassennamen.

Gehen wir die Funktion mal Schritt für Schritt durch. Der Prefix definiert unseren Namespace. Mit der magischen Konstante __DIR__ bekommen wir den Ordner in dem sich diese Datei grade befindet. Da dass unser Grundpfad ist, sind wir hier auch schon genau richtig.

Im nächsten Abschnitt wird die Länge des Prefix gezählt und dann geschaut ob wir einen Namespace haben.

Dann nehmen wir den relativen Namen unserer Klasse und wandeln diesen Namen in eine Ordnerstruktur um. Diese Struktur sagt unserem Autoloader dann ganz automatisch, von wo er diese Datei abholen muss.

Ein kleines Beispiel:

  • Konkret: myboilerplate\app\classes\core\kekse
  • Theorie: namespace\unsere\order\struktur\klassenname

Damit würden wir die Klasse „Kekse“ aus dem Order app/classes/core/ laden innerhalb unseres Plugins laden. Sofern diese Klasse unseren Namespace hat.

Zu guter Letzt überprüfen wir noch, ob die Datei vorhanden ist im jeweiligen Ordner. Das ganze machen wir zweimal, da wir die selbe Datei auch nochmal suchen wenn sie komplett kleingeschrieben ist.

Zusammenfassung

Nun haben wir alles zusammen für den nächsten Schritt. Wir haben unsere Struktur zusammen. Wir haben dafür gesorgt, dass unsere Klassen unseres Plugins zukünftig nahtlos in unsere Struktur durch das Autoloading eingefügt werden können.

Wir haben Entscheidungen zum Konzept unseres Plugins getroffen und uns für die Varianten entschieden, die es uns ermöglichen möglichst Zukunftsorientiert zu entwickeln.

Im dritten Beitrag dieser Reihe werden wir uns darum kümmern dass wir unsere Updates in Zukunft ganz einfach durchführen können. Wir haben diese Funktion im ersten Part als Optional markiert. Im Rahmen dieser Reihe möchte ich diesen Abschnitt jedoch einmal klar thematisieren.

Quellen & Links

Und nun wieder alle relevanten und wichtigen Quellen zum Abschluss:

Jeder Beitrag unterliegt einem ständigen Verbesserungsprozess. Es wird versucht die enthaltenden Informationen stets auf dem neusten Stand zu erhalten. Über Hinweise zur Verbesserung bin ich dankbar.

Sollten Informationen fehlen oder veraltet sein, übernimmt der Autor dennoch keine Haftung dafür. Vielen Dank.

HIT Plugin Boilerplate
WP Plugins Part 1: Aufbau der Main Datei

Dieser Artikel ist der erste in der Reihe zum Aufbau einer eigenen Boilerplate. Mit dieser kann man dann eigene Plugins erstellen, die alle möglichen Funktionen enthalten.

Erfahre mehr