Wieso ihr Theme-Code nicht in Plugins packen solltet

I warte regelmäßig etwa 40 WordPress Websites. Beim Aktualisieren von Plugins und Themes kann es manchmal zu Problemen kommen, das Aktualisieren des Core ist in der Regel kein Problem. Nicht so diese Woche bei einer Seite, die ich zu ersten Mal gewartet habe.

Nach dem Core-Update erhielt ich den „Es gab einen kritischen Fehler auf deiner Website“ Dialog und das Backend war defekt. Glücklicherweise hatte ich kurz zuvor ein Backup angelegt. Da die Seite aber nicht auf unseren Servern gehostet war und ich per FTP einige Dateien nicht sehen konnte, musste ich die Web-FTP Funktion des Hosters nutzen, um mir das Backup runterladen zu können. Mit einer lokalen Kopie der Seite konnte ich dann mit dem Debuggen beginnen.

Anpassen von Theme-Icons

Schauen wir uns erst einmal den Code an, der zu dem Problem geführt hat. Nach dem Aktivieren des Debug-Modus konnte ich folgende Fehlermeldung finden:

PHP Fatal error:  Uncaught Error: Class 'TwentyTwenty_SVG_Icons' not found in ...

Dieser Fehler wurde von den folgenden Zeilen verursacht (der Code ist für dieses Beispiel vereinfacht):

function add_custom_twentytwenty_icons() {
	TwentyTwenty_SVG_Icons::$ui_icons['hamburger'] = '<svg><!-- ... --></svg>';
}
add_action( 'after_setup_theme', 'add_custom_twentytwenty_icons' );

Ein kleiner Code zum Hinzufügen eines eigenen Icons in die „ui“ Gruppe der SVG-Icons von TwentyTwenty. Dies wurde in der after_setup_theme Action gemacht, was erst einmal sehr sinnvoll aussieht. Aber eine wichtige Sache hat gefehlt. Die Überprüfung, ob die Klasse überhaupt existiert. Wie geht es also besser?

Den Fehler beheben

Es gibt mehrere Wege um diesen spezifischen Fehler zu beheben. Schauen wir sie uns an, um eine gute Lösung zu finden.

Auf Existenz der Klasse prüfen

Wie der Fehler schon aussagt schlägt der Code fehl, weil versucht wird auf einer Klasse eine Property zu erweitern, die aber gar nicht existiert. Also warum prüfen wir nicht einfach zuvor, ob die existiert:

function add_custom_twentytwenty_icons() {
	if ( class_exists( 'TwentyTwenty_SVG_Icons' ) ) {
		TwentyTwenty_SVG_Icons::$ui_icons['hamburger'] = '<svg><!-- ... --></svg>';
	}
}
add_action( 'after_setup_theme', 'add_custom_twentytwenty_icons' );

Das würde so funktionieren. Jetzt können wir sicher gehen, dass wir nur dann die Property der Klasse ändern, wenn diese auch existiert. Es gibt mit dieser Lösung aber ein paar Probleme. Was passiert, wenn sich der Klassenname ändert? Was, wenn sich der Name der Property ändert? In diesen Fällen bekommen wir entweder erneut einen Fatal Error, oder zumindest eine Warnung und der Code funktioniert nicht mehr.

Überschreiben der „pluggable“ Klasse

Wisst ihr was? Diese spezifische Klasse von TwentyTwenty ist pluggable. Wenn ihr also das Verhalten der Klasse ändern wollt, dann könnt ihr sie einfach in eurem eigenen Code deklarieren, bevor sie selbst von TwentyTwenty geladen wird.

Der einzige Nachteil daran: Wenn ihr nur ein Icon hinzufügen wollt, dann ist es vielleicht etwas zu viel des Guten, die gesamte Klasse zu duplizieren. Ihr müsstet in diesem Fall bei jedem Update von TwentyTwenty sicherstellen, dass eure Klasse noch immer kompatibel ist.

Einen Filter verwenden!

Ja, ihr lest richt, es gibt einen Filter um die Liste der verfügbaren SVG-Icons in TwentyTwenty zu erweitern:

/**
 * Filters Twenty Twenty's array of icons.
 *
 * The dynamic portion of the hook name, `$group`, refers to
 * the name of the group of icons, either "ui" or "social".
 *
 * @since Twenty Twenty 1.5
 *
 * @param array $arr Array of icons.
 */
$arr = apply_filters( "twentytwenty_svg_icons_{$group}", $arr );

In unserem Beispiel wollen wir ein Icon in die „ui“ Gruppe hinzufügen, die neue Funktion könnte also im einfachsten Fall wie folgt aussehen:

function add_custom_twentytwenty_icons( $icons ) {
	$icons['hamburger'] = '<svg><!-- ... --></svg>';

	return $icons;
}
add_action( 'twentytwenty_svg_icons_ui', 'add_custom_twentytwenty_icons' );

Das ist die beste Lösung. Der einzige Nachteil hierbei ist, dass diese Funktion jedes Mal aufgerufen wird, wenn ein Icons auf der Seite ausgegeben wird. Vielleicht war genau das der Grund für die „clevere Lösung“, die zu dem Fehler geführt hat, da hier versucht wurde, das Icons nur einmal der Property hinzuzufügen.

Wieso hat die Originallösung zu einem Fehler geführt?

Meine Motivation zum Schreiben dieses Blogbeitrags war es aber nicht, dieses spezifische Problem und die Lösung hierfür zu präsentieren. Es gebt mir darum zu zeigen, wieso es überhaupt zu einem Fehler gekommen ist. Der Code war nämlich nicht in einem Child-Theme gespeichert, sondern in einem MU-Plugin!

Das Projekt verwendete ein „vendor-wp-base-theme“ als Child-Theme für TwentyTwenty. Innerhalb dieses Child-Themes wurde nicht wirklich viel gemacht. Zusätzlich gab es noch ein MU-Plugin „vendor-wp-base-theme-plugin“, welches in einer PHP-Klasse unter anderem Widgets hinzugefügt, Styles und Skripte eingebunden, ACF-Filter gesetzt und eben auch das SVG-Icon hinzugefügt hat.

Aber warum ist das nun ein Problem? Wieso würde die after_setup_theme Action feuern, aber die Dateien des Themes niemals geladen worden sein? Das Problem hierfür steckt in der wp-settings.php Datei. Diese Datei lädt zuerst alle MU-Plugin-Dateien, anschließend alle Plugin-Dateien und zu guter Letzt alle Dateien von „aktiven und gültigen“ Themes, also sowohl vom Parent-, als auch vom Child-Theme, bevor dann die Action gefeuert wird:

foreach ( wp_get_active_and_valid_themes() as $theme ) {
	if ( file_exists( $theme . '/functions.php' ) ) {
		include $theme . '/functions.php';
	}
}
unset( $theme );

/**
 * Fires after the theme is loaded.
 *
 * @since 3.0.0
 */
do_action( 'after_setup_theme' );

Aber wieso können wir noch immer das Problem bekommen? Schauen wir uns hierzu die Funktion wp_get_active_and_valid_themes einmal genauer an und was sie (nicht) tut:

function wp_get_active_and_valid_themes() {
	global $pagenow;

	$themes = array();

	if ( wp_installing() && 'wp-activate.php' !== $pagenow ) {
		return $themes;
	}
	// ...
}

Diese Funktion prüft, ob WordPress gerade „installiert“ wird. Das ist auch wahr, wenn der Core eine Aktualisierung der Datenbank braucht. Hierzu wird die Datei wp-admin/upgrade.php verwendet. In diesem Fall würde die Funktion das leere Themes-Array zurückgeben und somit würden weder vom Parent-, noch vom Child-Theme die functions.php Dateien geladen. Im Anschluss wird aber dennoch die after_setup_theme gefeuert, was dann zu diesem Fehler führt.

Fazit: niemals Theme-Dinge in Plugins machen!

Es gibt einen guten Grund dafür, weshalb man Code zur Anpassung von Themes in ein Child-Theme und solchen für die Anpassung von Plugin in ein weiteres (MU-)Plugin packen sollte. Das verhindert Fehler wie den hier gezeigten. Wenn man Theme-Code in Plugins ausführt (und umgekehrt), kann es sehr schnell zu solchen Fehler kommen, die man vielleicht sehr schwer debuggen kann, vor allem dann, wenn sie nur sehr selten auftreten (wie etwa in einem Datenbank-Update).

Falls ihr wirklich einmal Code an eine Stelle packen müsst, an die er typischerweise nicht gehört, dann stellt zumindest sicher, dass Dinge existieren, bevor ihr versucht sie anzupassen. Und falls der Original-Code euch Hooks zur Verfügung stellt, dann verwendet sie auch bitte. Genau aus diesem Grund wurden sie zum Code hinzugefügt. Selbst wenn ihr denkt, dass eure clevere Lösung ein paar Funktionsaufrufe einsparen kann ist es oft keine gute Idee Properties von PHP-Klassen (oder ähnliches) direkt zu manipulieren. Andere werden euch sehr dankbar sein, wenn euer Code nicht bei einem so eher unkritischen Schritt wie einem WordPress Major-Update kaputt geht. 😉

Veröffentlicht von

Bernhard ist fest angestellter Webentwickler, entwickelt in seiner Freizeit Plugin, schreibt in seinem Blog über WordPress und andere Themen, treibt sich gerne bei den WP Meetups in Berlin und Potsdam herum und läuft nach Feierabend den ein oder anderen Halbmarathon.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.