Eigene phpBB Erweiterungen erstellen (Fortgeschrittene Themen)

Beschreibung: In diesem Artikel werden einige fortgeschrittene Themen bei der Erstellung einer Erweiterung vorgestellt.

Kategorie: Extensions

Link zu diesem Artikel: Alles auswählen

[url=https://www.phpbb.de/kb/viewarticle?a=18&sid=f55a848d720228fcd70232edbb0ec107]Knowledge Base - Eigene phpBB Erweiterungen erstellen (Fortgeschrittene Themen)[/url]

In diesem Artikel werden einige fortgeschrittene Themen bei der Erstellung einer Erweiterung vorgestellt. Für eine grundlegende Einführung und grundlegende Themen siehe Eigene phpBB Erweiterungen erstellen

Datenbankänderungen bei der Installation

Sehr viele auch komplexe Änderungen lassen sich ohne Datenbankänderungen realisieren. In einigen Fällen werden aber neben einfachen Verhaltensänderungen auch zusätzlich Änderungen an der Datenbank benötigt. Neben Strukturänderungen, also z.B. komplett neuen Tabellen, neuen Tabellenspalten oder ähnlichem können das auch einfach zusätzliche Konfigurationsdaten, geänderte Konfigurationsdaten, neue Berechtigungen oder etwas ähnliches sein.

Neben solchen üblichen Installationsschritten könnte man vielleicht auch speziellen Code lediglich bei der Installation ausführen wollen, also z.B. neue Benutzer anlegen, ganze Foren erstellen, benutzerdefinierte Profilfelder anlegen oder seine Erweiterung abhängig von der bestehenden Forumkonfiguration einstellen.

Für alle diese Zwecke sind die so genannten Migrations gedacht. Diese php Dateien liegen alle im migrations/ Unterordner der Erweiterung (oder einem beliebigen Unterordner dieses Ordners). Mit einer Reihe spezieller Funktionen, die jeweils ein Array mit Anweisungen zurückliefern führt phpBB dann die gewünschten Anpassungen durch und kann diese zu großen Teilen auch wieder Rückgängig machen um die Erweiterung beispielsweise wieder zu deinstallieren. Auch phpBB selbst setzt bei Versionsupdates auf Migrations um Datenänderungen jeder Art durchzuführen.

Um den Aufbau zu verdeutlichen schauen wir uns mal eine einfache Migration an, die gekürzt und leicht modifiziert aus der Hookup Erweiterung kopiert ist:

Code: Alles auswählen

// Namensraumangabe. Muss dem korrekten Pfad der Migration entsprechen
namespace gn36\hookup\migrations;

// Name der Klasse = Name der Datei
// Abgesehen von der Standarderweiterung kann man auch von einigen speziellen anderen Erweiterungen erben, um z.B. benutzerdefinierte Profilfelder anzulegen
class v_1_0_0_compat30x extends \phpbb\db\migration\migration
{
    // Mit dieser Funktion wird phpBB mitgeteilt, welche anderen Migrationen vorher installiert werden müssen, damit diese funktioniert
    // Das können beliebige andere Migrationen sein, in diesem Fall sind es phpBB eigene Migrationen
    // Hierdurch wird z.B. bei einer automatischen Installation verhindert, dass die Migration installiert wird bevor phpBB selbst zumindest auf dem Niveau von phpBB 3.1.1 installiert ist.
    static public function depends_on()
    {
        return array(
            '\phpbb\db\migration\data\v30x\release_3_0_0',
            '\phpbb\db\migration\data\v31x\v311',
            '\phpbb\db\migration\data\v310\dev'
        );
    }

    // Diese Funktion kann verwendet werden um die Installation der Migration komplett zu überspringen und diese als installiert zu markieren
    // das ist z.B. nützlich, wenn man einen alten Mod für phpBB 3.0 portiert und die Daten übernehmen will.
    public function effectively_installed()
    {
        return isset($this->config['hookup_last_run']);
    }

    // Diese Funktion wird für Änderungen an dem Datenbankschema verwendet, also z.B. für das hinzufügen von Tabellen oder Spalten
    public function update_schema()
    {
        return array(
            'add_tables' => array(
                $this->table_prefix . 'hookup_available' => array(
                    'COLUMNS' => array(
                        'date_id'         => array('UINT:11', 0),
                        'topic_id'        => array('UINT:11', 0),
                        'user_id'        => array('UINT:11', 0),
                        'available'        => array('UINT:6', 1),
                    ),
                    'PRIMARY_KEY' => array('date_id', 'user_id'),
                    'KEYS' => array(
                        'date_id'         => array('INDEX', 'date_id'),
                        'topic_id'         => array('INDEX', 'topic_id'),
                        'tu_id' => array('INDEX', array('topic_id', 'user_id')),
                    ),
                ),
            ),
            'add_columns' => array(
                $this->table_prefix . 'topics' => array(
                    'hookup_enabled'         => array('UINT:1', 0),
                    'hookup_active_date'     => array('UINT:11', null),
                    'hookup_self_invite'     => array('UINT:1', 0),
                    'hookup_autoreset'         => array('UINT:1', 0),
                ),
            ),
        );
    }

    // Mit dieser Funktion werden Datenbankänderungen bei der Deinstallation wieder rückgängig gemacht
    public function revert_schema()
    {
        return array(
            'drop_tables' => array(
                $this->table_prefix . 'hookup_available',
            ),
            'drop_columns' => array(
                $this->table_prefix. 'topics' => array(
                    'hookup_enabled',
                    'hookup_active_date',
                    'hookup_self_invite',
                    'hookup_autoreset',
                ),
            ),
        );
    }

    // Mit dieser Funktion können Daten in der DB geändert werden.
    // Für einige häufig benötigte Dinge existieren Standardfunktionen, z.B. Konfigurationsvariablen oder Berechtigungen
    public function update_data()
    {
        return array(
                array('permission.add', array('f_hookup', false, 'f_read')),
                array('config.add', array('hookup_last_run', '0')),
                array('config.add', array('hookup_interval', '86400')),
        );
    }
} 
Der Aufbau dieser Dateien ist eigentlich weitgehend selbsterklärend, man muss für die meisten häufig benötigten Dinge lediglich die entsprechenden Bezeichnungen kennen, die in die Arrays eingetragen werden müssen und die Reihenfolge der Parameter. Das Beispiel oben hängt ab von Migrationen, die bei der Installation von phpBB selbst installiert werden. Es fügt eine Tabelle "hookup_available" sowie vier Tabellenspalten in der User Tabelle in die Datenbank ein und macht diese Änderungen bei der deinstallation wieder rückgängig. In der Konfiguration werden eine lokale Foren-Berechtigung "f_hookup" sowie zwei Variablen mit Standardwerten angelegt. Die Rechteeinstellungen für "f_hookup" werden dabei von "f_read" kopiert. Diese Änderungen können automatisch rückgängig gemacht werden, deshalb existiert keine Deinstallationsroutine für die Daten. Diese lässt sich jedoch auch anlegen - die betreffende Methode muss in diesem Fall revert_data heißen.

Für die meisten Erweiterungen dürfte das schon die wichtigsten Beispiele abdecken, die benötigt werden. Daneben gibt es noch diverse weitere Möglichkeiten mit Migrationen Änderungen an der DB durchzuführen. Da auch die englischsprachigen Beispiele hier mit relativ wenig Begleittext auskommen und weitgehend selbsterklärend sind verzichte ich an dieser Stelle auf weitere Details.
Beim Erstellen von Migrationen sollte man darauf achten, dass man bei einem Update keine bestehenden Migrationsdateien verändert. Sind Migrationen ein mal veröffentlicht und im Umlauf sollten sie unverändert so bleiben wie sie sind, ansonsten hat man später möglicherweise Probleme bei Updates oder der Deinstallation, weil die Datenbankstruktur anders ist, als die neuen Migrationen das vorsehen. Stattdessen sollte man einfach mit neuen Migrationen auf der Basis der alten Migrationen neue Änderungen machen und ggf. wenn nötig eben die Änderungen anderer Migrationen in den neuen Migrationen anpassen oder sogar rückgängig machen. Diese Vorgehensweise erlaubt auch beim Update das überspringen von Zwischenversionen
Oft kann man viele Datenbankänderungen in eine gemeinsame Datei verpacken. Das ist aber nicht immer sinnvoll: Wird beispielsweise ein phpBB 3.0 Mod auf 3.1 und höher portiert und dabei verändert sollte man zunächst mit einer oder mehreren Migrationen die Datenbank so herrichten, dass sie dem Stand von phpBB 3.0 entspricht. Anschließend verwendet man weitere Migrationen um basierend auf diesem Stand weitere Änderungen an der Datenbank vorzunehmen. Das hat den Vorteil, dass man in den zunächst angelegten Migrationen prüfen kann, ob die Änderungen vielleicht bereits (im von phpBB 3.0 hochgerüsteten Forum) vorhanden sind und kann die Änderungen dann einfach überspringen, das Forum aber trotzdem mit den weiteren Migrationen auf den aktuellen Stand bringen.

Auch die Deinstallation der Migrationen sollte man nicht vernachlässigen. Sie sollten jeweils die in der Migration vorgenommenen Schritte rückgängig machen, nicht mehr. Auf die Weise ermöglicht man ab phpBB 3.2 bei Problemen mit einem Update eine Rückkehr zu einem älteren Softwarestand ohne langwierig ein Backup einspielen zu müssen. Der Autor dieses Artikels hat beispielsweise bereits ein versehentliches Datenbankupdate von phpBB 3.1.x auf eine Betaversion von phpBB 3.2 komplett rückgängig gemacht.

Zuletzt sei noch auf die Möglichkeit hingewiesen, von anderen Migrationen zu erben als der Standardmigration im obigen Beispiel. Eine weitere nützliche Basisklasse ist beispielsweise die container_aware_migration, welche mit Hilfe des phpbb_container in der Migration alle in phpBB oder in Erweiterungen verfügbaren Services bereitstellt (also alle PHP Klassen, die in irgend einer services.yml Datei definiert sind). Will man beispielsweise bei der Installation einige Benachrichtigungen in die Datenbank legen, so kann man sich über die bereitgestellte Methode $this->container->get('notification_manager') eine Instanz des notification_manager Objekts liefern lassen. Daneben stellt phpBB noch eine Basisklasse zum Bereitstellen von benutzerdefinierten Profilfeldern zur Verfügung: profilefield_base_migration. In dieser müssen weitgehend lediglich die Klassenvariablen mit Inhalt gefüllt werden sowie die update_data() Funktion überschrieben um ein Profilfeld anzulegen. Weitergehende Infos finden sich beispielsweise in diesem Thema. Bei wiederkehrenden Aktionen können natürlich auch eigene Basisklassen angelegt werden.

Spezielle Klassen: Cronjobs, Benachrichtigungen

Dieser Abschnitt ist noch nicht ganz fertiggestellt
phpBB arbeitet an einigen Stellen mit dynamischen Listen von Klassen, um ähnliche Dinge zu tun. Ähnlich wie die obigen Migrationen, die verschiedenste Installationen und Updates in einer standardisierten Weise durchführen arbeitet phpBB einige Klassenlisten durch, die andere ähnliche Dinge tun. Das bekannteste Beispiel hierfür sind die Benachrichtigungen, die phpBB an die Benutzer verschickt. Jede Art von Benachrichtigung wird von einer speziellen Klasse realisiert. Es gibt beispielsweise eine speziell für Themenbenachrichtigungen und eine für PNs. Eine weitere wichtige Liste enthält alle Cronjobs, die phpBB in definierten Zeitabständen, die jede der Klassen selbst bestimmen kann automatisch ausführt.

Diese Listen lassen sich - genau wie weitere Listen, die es noch gibt - auch in Erweiterungen problemlos erweitern. Hierzu sind zwei Dinge notwendig: Die neue Klasse, die die gewünschte Funktionalität bereitstellt und ein dazu passender Eintrag in der config/services.yml der Erweiterung, der die Klasse in die Liste einträgt. Die Klasse ist aufgebaut wie jede andere Klasse der Erweiterung, muss allerdings je nach Liste ein paar Rahmenbedingungen erfüllen (also ein paar Methoden mit definierten Parametern und Namen bereitstellen). Der Serviceeintrag ist ebenfalls aufgebaut wie jeder andere Serviceeintrag mit allen Parametern für den Konstruktor. Zusätzlich erhält er jedoch noch einen "Tag", der den Eintrag als Teil der Liste markiert. Für Benachrichtigungen, die beim Freischalten von Beiträgen verschickt werden sieht der Eintrag für phpBB 3.1 beispielsweise wie folgt aus:

Code: Alles auswählen

      notification.type.approve_post:
          class: phpbb\notification\type\approve_post
          shared: false
          arguments:
              - '@user_loader'
              - '@dbal.conn'
              - '@cache.driver'
              - '@user'
              - '@auth'
              - '@config'
              - '%core.root_path%'
              - '%core.php_ext%'
              - '%tables.notification_types%'
              - '%tables.notifications%'
              - '%tables.user_notifications%'
          tags:
              - { name: notification.type } 
Der für phpBB 3.2 hat sich geringfügig verändert, weil die Parameter des Konstruktors sich verändert haben, gemeinsam ist aber beiden, dass sie einen Tag name mit dem Inhalt notification.type verwenden. Jede Klasse, die in der services.yml mit diesem Tag versehen wird wird von phpBB als Benachrichtigungsklasse erkannt und entsprechend verwendet. Analog dazu sieht ein Eintrag für einen Cronjob beispielsweise wie folgt aus:

Code: Alles auswählen

      cron.task.core.prune_all_forums:
          class: phpbb\cron\task\core\prune_all_forums
          arguments:
              - '%core.root_path%'
              - '%core.php_ext%'
              - '@config'
              - '@dbal.conn'
          calls:
              - [set_name, [cron.task.core.prune_all_forums]]
          tags:
              - { name: cron.task } 
In diesem Fall ist der verwendete Name cron.task.

Beim genaueren Betrachten beider Serviceeinträge fallen an den Einträgen noch weitere Besonderheiten auf: Die Benachrichtigungen enthalten einen Eintrag shared: false, Cronjobs haben einen zusätzlichen Eintrag calls: in dem ein Unterpunkt - [set_name, ... enthalten ist. Ersteres sorgt dafür, dass für jede Benachrichtigung eine neue Instanz der Klasse verwendet wird (das genauer auszuführen würden den Rahmen hier sprengen), zweiteres legt den Namen des Cronjobs bei der Bedienung von der Konsole aus fest.

Die Argumente, die deine Klasse verwendet sind natürlich nicht auf die obigen Beispiele beschränkt - wenn deine Klassen andere Parameter benötigen, beispielsweise weil dein Cronjob die Berechtigungen von Benutzern prüfen muss oder einige eigene Objekte verwendet, dann können sie selbstverständlich auch andere Parameter erhalten.

Die Klassen selbst kannst du anhand von Beispielen aus phpBB selbst erstellen. Diese implementieren das benötigte Interface bereits, in der Regel gibt es sogar eine Basisklasse, von der du deine eigene Klasse ableiten kannst. Diese stellt dir die benötigten Grundfunktionen dann bereits zur Verfügung. Für einen neuen Cronjob kannst du deine Klasse beispielsweise von der Klasse phpbb/cron/task/base ableiten, die du unter phpbb/cron/task/base.php findest. Im einfachsten Fall musst du dann in deinem abgeleiteten Objekt noch lediglich eine Methode namens run zur Verfügung stellen, diese wird dann bei jeder Gelegenheit, bei der ein Cronjob ausgeführt wird ebenfalls ausgeführt:

Code: Alles auswählen

<?php
namespace gn36\infobar\cron;

class sample_cronjob extends phpbb\cron\task\base
{
    function run()
    {
        // Do something useful here
        return;
    }
} 
Im Normalfall wird man vermutlich die Funktion nicht bei jeder Gelegenheit ausführen wollen, um das zu prüfen gibt es zwei weitere Methoden: is_runnable() und should_run(). Wenn erstere true zurückliefert ist der Cronjob ausführbar (wurde also z.B. nicht in der Konfiguration deaktiviert), wenn die zweite Methode true zurückliefert sollte der Cronjob zum aktuellen Zeitpunkt ausgeführt werden, in der Regel weil seit dem letzten Lauf genügend Zeit verstrichen ist. Üblicherweise erstellt man hierfür mit einer Migration zwei Konfigurationsvariablen (eine für das Intervall, eine für den Zeitpunkt der letzten Ausführung) und prüft in should_run() die Zeitdifferenz. Nach dem Ausführen setzt man in run() dann einfach den Zeitstempel der letzten Ausführung neu und schon ist der Cronjob fertig.

Fortsetzung folgt...