Package Management in WordPress: Einführung und Lösungen

Header Package Management in WordPress
Gutes Package Management ist ein wesentlicher Bestandteil des Dependency Managements und der stabilen Plugin-Entwicklung in WordPress.

Gutes Package Management ist ein wesentlicher Bestandteil des Dependency Managements und der stabilen Plugin-Entwicklung in WordPress. Dieser Artikel gibt eine Einführung in das Paketmanagement in WordPress, beschreibt typische Probleme und zeigt mögliche Lösungsansätze auf.

Package Management

Package Management oder Paketverwaltung bezieht sich auf die Fähigkeit, wiederverwendbare, als Paket verteilte Arbeit zu installieren und die Abhängigkeiten zwischen diesen Paketen bei unterschiedlichen Versionen beizubehalten. Bei Paketen, die für einzelne Projekte erforderlich sind, wird dies häufig als Dependency Management (Abhängigkeitsverwaltung) bezeichnet. Ein Paket wie ein Plugin oder eine Code-Bibliothek kann von mehreren anderen Paketen abhängig sein, die wiederum von anderen abhängig sind usw. Zusätzlich sind diese Abhängigkeiten oft auf bestimmte Versionsbereiche beschränkt.

Die Aufgabe eines Systems zur Abhängigkeitsverwaltung ist die rekursive Bestimmung und Installation der richtigen Version aller Abhängigkeiten eines Projekts. Eine weitere wichtige Funktion von Paketverwaltungssystemen ist zu ermitteln, ob eine Reihe rekursiver Sätze von Anforderungen in eine installierbare Liste von Paketen aufgelöst werden kann, also alle Anforderungen erfüllt werden können. Ist dies nicht der Fall, erhält der User eine Warnung und es werden hilfreiche Informationen zur Problembehebung bereitgestellt.

In PHP ist Composer das De-facto-Tool für die Abhängigkeitsverwaltung. Es gibt andere Tools für andere Plattformen wie npm für JavaScript. Diese sind hier aber nicht relevant, da sie eventuell nicht verwendet werden oder nicht die hier beschriebenen Probleme zeigen. Üblicherweise haben diese Tools eine Kommandozeilenschnittstelle (CLI) und sind in der Sprache geschrieben, für die sie die Paketverwaltung übernehmen. Das Konzept des Package Management selbst ist jedoch an keine bestimmte Plattform gekoppelt.

Pakete können so groß oder klein wie nötig sein, um eine logische Lösung zu enthalten. Dies sorgt für eine höhere Granularität bei Lösungen und ermöglicht den Fokus auf enger gefasste Probleme. Bei diesem Ansatz müssen weniger Annahmen getroffen und weniger Bedingungen erfüllt werden, wodurch die Pakete leichter und vielseitiger einsetzbar werden. Mit der Unterscheidung nach Belangen steigt auch die Interoperabilität.

Package Management Dependency Beispiel-Grafik
Beispiel für Paketabhängigkeiten

Modularität in WordPress

Auch, wenn in WordPress alle Erweiterungen dieselbe API nutzen, gibt es kein einheitliches Paketformat. Stattdessen gibt es WordPress-Erweiterungen entweder in Form von Plugins oder Themes. Außerdem können Plugins Must-UsePlugins sein, die anders als normale Plugins installiert werden; und Themes können Erweiterungen anderer Themes sein und somit Child Themes. Zum Schluss gibt es noch Dropins, die weder Plugins noch Themes sind, das Verhalten von WP jedoch auf andere Art beeinflussen, indem sie bestimmte Aspekte einfach vollständig ersetzen. Daher gibt es hier keinen einheitlichen Weg, wie Erweiterungen installiert oder verteilt werden. Das Konzept des „Pakets“ existiert nicht und daher auch kein Package Management.

Darüber hinaus liefert WordPress selbst keine Paket-Metadaten, ist stark abhängig von globalen Variablen und stellt viele Annahmen über die Umgebung an. Daher ist eine Verteilung als Paket schwierig und nicht verlässlich. Für die Installation von WordPress als Composer-Paket gibt es verschiedene Lösungen, z. B. johnpbloch/wordpress-core-installer.

Obwohl WordPress kein einheitliches Modul-Konzept hat, ist es dennoch möglich, plattformübergreifende Module zu erstellen und sie in WordPress-Plugins oder Themen zu verwenden. Dies ausführlich zu erklären, würde allerdings den Rahmen dieses Artikels sprengen.

Die Notwendigkeit der Abhängigkeitsverwaltung

Obwohl WordPress sein eigenes, wenn auch uneindeutiges Modul-System bietet, besteht die Notwendigkeit einer einheitlichen Abhängigkeitsverwaltung. Und zwar, weil bei Erweiterungen oft vorhandene Lösungen für allgemeine Probleme wiederverwendet werden müssen. Die Open-Source-Community bietet reichlich Bibliotheken und Standards, mit denen Entwicklungszeit und Komplexität drastisch reduziert werden könnten – entweder durch Bereitstellung von Algorithmen zur Lösung spezifischer Programmierprobleme oder dadurch, dass Software-Komponenten ausgetauscht werden können, ohne der Software zu schaden. In der überwältigenden Mehrheit der Fälle werden diese Lösungen in Form von Composer-Paketen bereitgestellt. Autoren von WordPress-Erweiterungen verlassen sich möglicherweise auf diese Pakete und konzentrieren sich lieber auf die spezifischen Aufgaben, die ihre Erweiterung bewältigen soll, als auf die Entwicklung einer eigenen Lösung. Die Autoren könnten im Open-Source-Bereich auch zusammen an Paketen arbeiten, was unzählige Vorteile für alle hätte: Entwickler würden weniger Zeit verschwenden und Benutzer würden solidere und fehlerfreie Produkte mit mehr Features erhalten.

Derzeit haben sich bereits viele Entwickler von Erweiterungen wie zum Beispiel Inpsyde und RebelCode  diese Philosophie zu eigen gemacht, und konnten so ihre Arbeit effektiver angehen, mehr Tests einführen, automatisieren und die Entwicklung aufteilen und beschleunigen. Mit Lösungen wie WP Starter und Bedrock können ganze WordPress-Umgebungen über Composer und andere Virtualisierungs-Tools verwaltet werden, was für Unternehmen und Agenturen ein wesentlicher Aspekt ist.

Probleme bei der Abhängigkeitsverwaltung in WordPress

  1. WordPress hat keinen Mechanismus zur Abhängigkeitsverwaltung.
  2. Entwickler von Erweiterungen möchten mit Abhängigkeitsverwaltung arbeiten.
  3. Entwickler von Erweiterungen müssen daher die Abhängigkeiten zusammen mit ihren Produkten liefern.
  4. Das bedeutet, dass keine Zentralisierung der Abhängigkeiten unterschiedlicher Erweiterungen stattfindet und Erweiterungen daher an Größe zunehmen.
  5. Zusätzlich bedeutet dies, dass bei Abhängigkeiten unterschiedlicher Erweiterungen ein erhebliches Konfliktpotential besteht.
  6. Gute Lösungen sind granular – jedoch werden mit zunehmender Granularität mehr Pakete benötigt, was Konflikte wahrscheinlicher macht.

Der Grund für Konflikte liegt darin, dass zwei verschiedene Erweiterungen mit zwei unterschiedlichen Versionen des Pakets, von dem sie abhängen, geliefert werden können. Aufgrund von Autoloading und den Eigenschaften von FQCN, wird von allen abhängigen Erweiterungen nur eine Version genutzt, wobei nicht steuerbar ist welche. Wenn die unterschiedlichen Versionen außerdem noch inkompatibel sind (entweder wegen unterschiedlicher API oder wesentlichen Unterschieden in der Implementierung), wird eine der Erweiterungen unbrauchbar – egal, welche Version verwendet wird. Dieses Problem wird ausführlich von Peter Suhm beschrieben, der eine Reihe von Tools zur Lösung des Problems entwickelt hat.

Mögliche Lösungen für inkompatible Erweiterungen

Das Konfliktproblem kann auf mehreren Wegen gelöst werden. Zu beachten ist, dass selbst unter idealen Bedingungen die Kompatibilität zwischen allen Produkten nicht immer gewährleistet werden kann. Einige Programme sind einfach aufgrund widersprüchlicher Zwecke oder anderer Gründe nicht kompatibel. Und das ist eine Tatsache, an der nichts geändert werden kann.

Höchstwahrscheinlich ist die ultimative Lösung eine Kombination mehrerer oder aller nachfolgend beschriebenen Ansätze.

Einführung von Abhängigkeitsverwaltung in WordPress

Dies wäre die ideale Lösung. Leider sind wir davon weit entfernt. Es gibt Tools, die Aktualisierung und CI/CD erleichtern, wie zum Beispiel WP Pusher oder Branch. Es gibt aber kein Tool, das Composer in WordPress integriert. Als sich die Vorteile von Composer abzeichneten, hatte Rarst vorgeschlagen, dem Core eine maschinenlesbare Paketbeschreibung hinzuzufügen. Das liegt zum Zeitpunkt der Erstellung dieses Artikels mittlerweile sechs Jahre zurück. Somit ist dies zurzeit keine gangbare Möglichkeit und wird es möglicherweise auch nie.

Nutzung von WordPress unter Composer

Prinzipiell bedeutet das, WordPress in einer mit Composer verwalteten Umgebung als eine Abhängigkeit dieser Umgebung zu nutzen. WordPress-Erweiterungen können in gleicher Weise als Abhängigkeiten deklariert werden, und der oomphinc/composer-installers-extender bietet eine Methode, jeden Erweiterungstyp im richtigen Ordner abzulegen, während der johnpbloch/wordpress-core-installer oder ähnliche Tools dafür sorgen, dass der WordPress-Core dort ist, wo er sein soll.

Das Problem bei diesem Ansatz ist, dass die WordPress-Umgebung bereits von Composer verwaltet sein muss. Das stellt aufgrund der benötigten installierten Software eine Belastung für den Server dar und ist nicht ohne entsprechende Kenntnisse durchführbar. Nichtsdestotrotz ist dies eine gute und akzeptable Lösung für Webseiten, die komplett von einer Agentur verwaltet werden.

Simultane Aktualisierung von Plugin-Abhängigkeiten

Dies bedeutet einfach, darauf zu achten, dass alle Plugins, die kompatibel bleiben sollen, kompatible Abhängigkeiten haben. Dazu sind simultane Pflege und Freigabe dieser Plugins erforderlich, was äußerst schwierig zu bewerkstelligen ist. Wenn für ein Plugin eine neuere, nicht mehr abwärtskompatible Version einer Abhängigkeit erforderlich ist, muss das andere Plugin in irgendeiner Form nachziehen. Dies ist selten möglich. Und es wird immer schwieriger, je mehr Plugins miteinander kompatibel bleiben müssen.

Zentralisierte Abhängigkeiten in einem Framework-Plugin

Der entgegengesetzte Ansatz zum vorherigen. Hierzu muss sehr häufig verwendeter Code ausgewählt und als separates Plugin geliefert werden, von dem andere Plugins dann abhängig wären. WordPress verfügt über keinen systemeigenen Mechanismus zur Deklarierung und Signalisierung von Abhängigkeiten zwischen Plugins. Es gibt aber Lösungen von Drittanbietern wie TGMPA, die Usern eine Oberfläche bieten, über die sie entsprechende Informationen abrufen und diese Abhängigkeiten verwalten können.

Dieser Ansatz verlangt ernsthaftes Engagement und Gewissenhaftigkeit, ist aber umsetzbar. Ein Nachteil für abhängige Plugins kann eine in verschiedenem Maße verringerte Flexibilität bei den benötigten Abhängigkeiten und deren Versionen sein. Die Einhaltung von Einheitlichkeit kann sich aber auch als vorteilhaft erweisen. Hinsichtlich der Notwendigkeit zusätzliche Plugins zu installieren gibt auch Marketingbedenken. Während Benutzer mit der Installation von Plugins wohlvertraut sind und damit meistens kein Problem haben, stellt die Abhängigkeit von anderen Plugins keinen üblichen Ansatz dar und kann etwas irritierend wirken.

Automatisches Refactoring beim Build

Bei diesem Ansatz wird der gesamte in einem Projekt verwendete Code mittels Monkey Patching so verändert, dass nur FQNs genutzt werden, die spezifisch für das Projekt sind. So werden FQN-Konflikte vermieden, da die Namensräume der Abhängigkeiten geändert werden. Dieser Ansatz zieht unvorhersehbare Konsequenzen nach sich und stellt ein enormes Interoperabilitätsproblem dar, da er die Standardisierung oder Wiederverwendung von anderem standardisierten Code unmöglich macht.  In Fällen, in denen der gesamte Code von den Autoren des Plugin gepflegt wird und Interoperabilität nicht von Belang ist, mag dies eine brauchbare Lösung sein. Für die Automatisierung dieser Aufgabe gibt es Tools wie Mozart und PHP-Scoper, da diese ohne Automatisierung nicht zu bewältigen ist.

Die Interoperabilitätsprobleme werden weniger schwerwiegend, wenn es beim gewählten Tool möglich ist, bestimmte Namensräume von der Verwendung von Präfixen auszuschließen. In diesem Fall könnte es möglich sein, auf das Präfix bei Interop-Schnittstellen zu verzichten und dennoch die übrigen projektspezifischen Implementierungen zu berücksichtigen. Dadurch wird die Umsetzbarkeit dieses Ansatzes marginal erhöht. Zum Zeitpunkt dieses Artikels hat Mozart keine Funktion implementiert, die es ermöglichen würde, ausgewählte FQNs vom Patching auszuschließen. Aus diesem Grund sollte in den meisten Fällen PHP-Scoper verwendet werden, das außerdem umfassender, gepflegter, funktionsreicher und weit verbreitet ist.

Es ist auch erwähnenswert, dass ein solches Patching kein Teil des Entwicklungsprozesses ist und nicht mit dem Code des Projekts zusammenhängt. Die Quelldateien des Projekts sollten Original-Symbole und FQNs verwenden. Das Patching sollte in der Build Time mit einem Build Tool wie Phing durchgeführt werden. Diese Art von Build Tool kommt weder in der Entwicklung, noch im Produkt selbst zum Einsatz, und deshalb sollte es mit etwas wie Phive installiert werden, um viele unnötige und verwirrende Schritte und Erklärungen zu vermeiden.

Daumen drücken und aufs Beste hoffen

Je nach beteiligten Abhängigkeiten und Anzahl der Ziel-Plugins treten die Probleme eventuell niemals auf – speziell bei Abhängigkeit von äußerst stabilen APIs. Von daher besteht bei Abhängigkeiten mit Standardschnittstellen wie PSR und Ähnlichen ein sehr geringe Chance, dass Probleme auftreten, weil sie sich, obwohl sie weit verbreitet sind (und auch sein sollten), äußerst selten ändern. Bei Plugins, die selten zusammen mit anderen möglicherweise kollidierenden Plugins installiert werden, die auf Unternehmensumgebungen zugeschnitten sind und bei kurzfristigen Lösungen wie Migrations-Tools besteht ebenfalls ein geringeres Risiko von Kompatibilitätsproblemen mit Abhängigkeiten.