Eigenes WordPress Widget in nur ~30 Zeilen erstellen

Eigenes WordPress Widget in nur ~30 Zeilen erstellen

Es ist in WordPress recht einfach, eigene Inhalte in Form von Widgets einzubinden. Der Beitrag zeigt ein simples Beispiel mit ~30 Codezeilen. Darüber hinaus gehen wir auf erweiterte Themen wie das definieren von Einstellungen für die Admin-Oberfläche von WP ein. Außerdem einige Überlegungen aus eigener Erfahrung, die man sich vor dem produktiven Einbau stellen sollte – um Probleme zu verhindern, die nicht jeder direkt als absehbar erachtet.

Einfaches Widget erstellen

Um eigene Widgets zu entwickeln, stellt WordPress die Basisklasse WP_Widget bereit.1 Sie standardisiert den Aufbau durch das Vererben mehrere Methoden:

  • Der Konstruktor registriert das Widget mit Name, ID und Beschreibung
  • widget() wird aufgerufen, um den Inhalt für den Nutzer zu rendern
  • form() rendert die verfügbaren Einstellungen für den Admin-Bereich
  • update() bietet die Möglichkeit, neue Einstellungen vor dem Speichern anzupassen

Grundsätzlich lassen sich Widgets an zwei Stellen registrieren: Entweder als Teil des Designs, dann landet der Code in dessen functions.php. Oder zur besseren Übersicht in einer PHP-Datei, welche dort geladen wird. Das macht Sinn, wenn es sich um einen Teil dieses Themes handelt.

Möchte man unabhängig davon ein eigenes Widget nachrüsten, wird es als Erweiterung (Plugin) bereitgestellt. Hier sind zwei Besonderheiten zu beachten: Es muss mindestens ein Kommentar mit „Plugin Name:“ gefolgt von einer frei wählbaren Bezeichnung angegeben werden. Außerdem ist die einmalige Aktivierung unter den Plugins erforderlich – ansonsten fehlt es schlicht in der Liste verfügbarer Widgets.

Folgendes Beispiel zeigt ein minimalistisches Widget, welches lediglich eine Überschrift mit einem Mustertext ausgibt. Es verfügt über keinerlei Einstellungen & demonstriert den Aufbau in 31 Zeilen:

<?php
/**
 * Plugin Name: U-News Widget
 */
class UNews_Widget extends WP_Widget {
    public function __construct() {
        parent::__construct(
            'unews_widget',
            'U-News Widget',
            array('description' => 'Zeigt U-News Nachrichten an')
        );
    }
    public function widget($args, $settings) {
        echo $args['before_widget'];
        ?>
            <h4 class="widget-title">Neuste Nachrichten (U-News)</h4>
            <p>Dummy-Inhalt</p>
        <?php
        echo $args['after_widget'];
    }
    public function form($settings) {
        echo '<p>Kein Einstellungsfeld verfügbar.</p>';
    }
    public function update($new_settings, $old_settings) {
        return $new_settings;
    }
}

add_action('widgets_init', function() {
    register_widget('UNews_Widget');
});

Dies kann man später um die gewünschte Funktionalität erweitern. Da es sich um PHP-Code handelt, steht nahezu grenzenlose Freiheit zur Verfügung: Inhalte aus Datenbanken laden, Anfragen an APIs, usw.

Widget aktivieren & testen

  1. Unter wp-content/plugins einen einzigartigen Unterordner anlegen, der obigen Code in einer PHP-Datei enthält. WP lädt automatisch alle .php Dateien darin. Die Benennung ist daher frei wählbar. Um Konflikte zu vermeiden, empfehlen sich Projektnamen, ggf. sogar mit einem eigenen Präfix.
  2. Im Adminbereich unter Plugins die Erweiterung (hier U-News Widget) aktivieren
  3. Unter Design > Widgets kann man nun nach dem Name (zweiter Parameter im Konstruktor) suchen und das Widget in die Sidebar ziehen:

Nach dem Speichern erscheint das Widget im gewählten Bereich – hier etwa in der Sidebar auf der Startseite:

Eigene Einstellungen hinzufügen

Möchte man bestimmte Dinge abseits des Codes für WP-Nutzer anpassbar gestalten, kann dies in der form Methode umgesetzt werden. Sämtliches dort ausgegebenes HTML wird beim Klick auf das Widget im Admin-Bereich angezeigt. Dies lässt sich bereits mit dem einfachen Beispiel von oben Demonstrieren: Es zeigt hier lediglich einen Platzhalter mit der Info, dass keine Einstellungen zur Verfügung stehen.

Hier steht vollwertiges HTML zur Verfügung. Sinnvoll ist für mein Widget der Nachrichten von U-News eine Begrenzung der maximalen Anzahl. Dafür erzeugen wir in form ein Feld. Die Hilfsfunktionen get_field_id und get_field_name aus der Basisklasse nehmen uns Arbeit ab. WP setzt die Felder automatisch in einem Array nach dem Plugin- bzw. Widget-Name, um Konflikte mit anderen zu vermeiden.

public function form($settings) {
    ?>
        <p>
            <label for="<?=$this->get_field_id( 'max_news_count' )?>">
                <?php _e( 'Maximale Anzahl an Nachrichten' ); ?>
            </label>
            <input id="<?=$this->get_field_id( 'max_news_count' )?>" name="<?=$this->get_field_name( 'max_news_count' )?>" 
                value="<?=esc_attr( $settings['max_news_count'] )?>" type="text">
        </p>
    <?php
}

Damit erscheint beim Klick auf das Widget im Admin-Bereich eine Textbox für das Limit:

Der dort gesetzte Wert steht in der widget() Funktion im zweiten übergebenen Parameter $settings zur Verfügung:

$max_news_count = $settings['max_news_count'];

Standardwerte für Einstellungen

Während das beim Testen funktioniert, sollten produktiv Standardwerte vorhanden sein. Schließlich werden Einstellungen wie $settings['max_news_count'] erst gesetzt, nachdem der Nutzer entsprechende Werte im Admin-Bereich einträgt. Das wird an mehreren Stellen problematisch. Simples Beispiel: Das Limit soll die geladenen Datensätze aus einer Datenbank begrenzen.

$query = $db->prepare('
    select ...
    limit ' . (int)$max_news_count
);

Dies wird im beschriebenen Szenario einer frischen Installation ohne gefüllte Einstellungen fehlschlagen, weil $settings['max_news_count'] nicht gesetzt ist – dadurch entsteht eine fehlerhafte SQL-Abfrage. Daher lege ich einen Array mit Standard-Werten an:

class UNews_Widget extends WP_Widget {
    private $default_settings = array(
        'max_news_count' => 10
    );
    // ...
}

Dazu eine Hilfsfunktion, welche prüft, ob eine gewünschte Einstellung bereits gesetzt ist – und diese entweder daraus zurückliefert, oder alternativ aus den Standardwerten.

private function get_setting($settings, $key) {
    if(isset($settings[$key])) {
        return $settings[$key];
    }
    return $this->default_settings[$key];
}

In der widget() Funktion zum Rendern des Inhalts wird sie wie folgt genutzt:

$max_news_count = $this->get_setting($settings, 'max_news_count');

Eben so beim Rendern der Einstellungen:

<input id="<?=$this->get_field_id( 'max_news_count' )?>" name="<?=$this->get_field_name( 'max_news_count' )?>" 
     value="<?=esc_attr( $this->get_setting($settings, 'max_news_count') )?>" type="text" type="number" min="1" max="100">

Zu beachten

Mit großer Macht geht bekanntlich auch große Verantwortung einher. Gerade weil sich nahezu alles über Widgets integrieren lässt, sollte man sich vorher ein paar Gedanken darüber machen, was technisch gemacht werden soll. Und ob bzw. in wie weit das skaliert. Immerhin wird das Widget an der jeweiligen Stelle bei jedem Aufruf komplett neu gerendert. In der Seitenleiste geschieht das auf der Startseite sowie in den Artikeln. Abhängig von der Anzahl an Besuchern also recht häufig.

Wer etwa eine HTTP-API aufruft, erzeugt jedes mal eine Anfrage an den Zielserver. Da es sich um serverseitigen Code handelt, im Namen des eigenen Servers. Das kann schnell problematisch werden: Abfragenlimits sorgen für temporäre oder dauerhafte Probleme beim Laden der Daten. Oder man wird gar wegen eines ungewollten DoS-Angriffs komplett ausgesperrt. Selbst wenn es bei internen Ressourcen bleibt – eine umfangreiche Datenbankabfrage oder andere rechenintensive Vorgänge verlangsamen die Ladezeit beim Nutzer, während parallel die Serverlast steigt.

Im „KI“ Zeitalter müssen neben menschlichen Besuchern auch Bots zunehmend berücksichtigt werden. Zwar gab es die bereits vorher. Doch insbesondere „KI“ Crawler gehen zunehmend aggressiv vor, wodurch selbst auf kleineren Blogs zehntausende Anfragen in kürzester Zeit entstehen können. Ebenfalls möglich sind unerwartet hohe Zahlen echter Besucher, weil Links in Sozialen Netzwerken geteilt werden. Es gibt verschiedene Gründe, warum man auf höhere Last vorbereitet sein sollte.

Optimiertes Laden von Daten

Grundsätzlich sollten nur jene Daten geladen werden, die man tatsächlich benötigt. Der Klassiker bei Datenabfragen ist SELECT * von denen in aller Regel nicht sämtliche Spalten Verwendung finden. Insbesondere in Kombination mit JOINs. Hier empfehlen sich die grundlegenden Optimierungen: Indizes verwenden bzw. wenn nötig anlegen, Abfragen mit EXPLAIN prüfen, Volltextsuche vermeiden usw.

Lässt sich Caching sinnvoll nutzen? Ich habe zur PHP 5-Zeit auf XCache gesetzt. Es speichert den kompilierten Bytecode zwischen, sodass der PHP-Interpreter nicht für jede Anfrage sämtlichen Code neu interpretieren muss (OPcache). Dies erfolgt im Arbeitsspeicher – damit entfällt die I/O Last. Außerdem stellt XCache PHP-Funktionen wie xcache_set oder xcache_get bereit. So kann man Objekte im RAM ablegen, die über verschiedene Anfragen hinweg zur Verfügung stehen.

Ab PHP 5.5 hat die Sprache endlich mit Zend OPcache einen eigenen OPcache integriert2 und mit dem JIT-Compiler in PHP 8.0 ausgebaut. Leider weiterhin „nur“ als Bytecode-Cache. Um selbst Daten ablegen zu können, sind Alternativen wie APCu notwendig. Das Prinzip ist identisch, nur die Funktionen dafür heißen anders: apcu_store statt xcache_set und apcu_fetch anstelle von xcache_get. Ein simpler Weg kann darin bestehen, die Daten XX Minuten zu speichern, bevor man sie neu lädt. Welcher Wert sinnvoll ist, hängt von der Art der Datenquelle sowie dem erwarteten Besuchsvolumen ab.

Quellen

  1. https://developer.wordpress.org/reference/classes/wp_widget/ ↩︎
  2. https://www.php.net/releases/5_5_0.php ↩︎

Leave a Reply