Ein Redaktionssystem lädt Erweiterungen aus einem Verzeichnis. Nach dem Einbinden einer Datei steht die Klasse zur Verfügung, nur weiß niemand, wie sie heißt. Bisher half eine Namenskonvention, an die sich kein Erweiterungsautor gehalten hat.
Im Bild läuft eine lange Liste durch mehrere Filter, unten bleiben wenige Treffer übrig. Genau so wird die Funktion in der Praxis benutzt.
Was PHP get_declared_classes() liefert
PHP get_declared_classes() gibt ein Array mit den Namen aller Klassen zurück, die zum Zeitpunkt des Aufrufs geladen sind. Die Funktion nimmt keine Parameter entgegen. Die Reihenfolge folgt der Ladereihenfolge, deshalb stehen die eingebauten Klassen von PHP am Anfang und die eigenen am Ende.
<?php
$klassen = get_declared_classes();
echo count($klassen); // z.B. 214
print_r(array_slice($klassen, -3));
/* Array (
[0] => App\Model\Rechnung
[1] => App\Export\CsvSchreiber
[2] => App\Export\PdfSchreiber
) */
Wichtig ist die Schreibweise der Namen: Sie kommen ohne führenden Trenner zurück. Ein Filter, der nach \App\Model sucht, findet deshalb nichts, obwohl die Klassen geladen sind. Das ist der häufigste Stolperstein bei der ersten Benutzung.
Die Signatur im Original
Die Funktion nimmt keine Parameter, ihre Rückgabe verdient trotzdem eine Anmerkung:
get_declared_classes(): array
/* return - an array with the names of all classes declared in the
current script, in the order they were declared
The number of entries differs between versions: PHP 7 declares fewer
built-in classes than PHP 8. A test that compares against a fixed
count will therefore break on an upgrade. */
Damit ist auch gesagt, wofür sich die Liste nicht eignet: als Grundlage für einen Vergleich mit einer festen Zahl.
Die Liste zeigt den Zustand, nicht das Projekt
PHP get_declared_classes() kennt nur, was tatsächlich eingebunden wurde. Mit einem Autoloader wird eine Klasse erst geladen, wenn sie zum ersten Mal gebraucht wird, und bis dahin taucht sie in der Liste nicht auf. Wer alle Klassen eines Projekts sucht, braucht einen Scan des Dateisystems oder die Klassenkarte von Composer, nicht diese Funktion.
<?php
$vorher = count(get_declared_classes());
$rechnung = new App\Model\Rechnung(); /* Autoloader springt an */
echo count(get_declared_classes()) - $vorher; /* 1 oder mehr */
Dieser Zustandscharakter ist keine Schwäche, sondern die Grundlage für das interessanteste Muster weiter unten: Wenn sich die Liste durch das Laden verändert, lässt sich der Unterschied auswerten.
Eigene Klassen von eingebauten trennen
Für eine Diagnoseseite interessieren die 150 eingebauten Klassen selten. Wer einen eigenen Namensraum benutzt, filtert einfach danach.
<?php
$eigene = array_filter(
get_declared_classes(),
static fn (string $name): bool => str_starts_with($name, 'App\\')
);
sort($eigene);
print_r(array_values($eigene));
Der doppelte Trenner in der Zeichenkette ist die übliche Maskierung innerhalb doppelter Anführungszeichen; in einfachen Anführungszeichen genügt einer. Wer keinen eigenen Namensraum benutzt, kann die Liste vor dem Einbinden eigener Dateien einmal sichern und später mit array_diff() vergleichen.
Alle Kindklassen einer Basisklasse finden
Ein häufiger Fall: Eine Anwendung hat mehrere Exportformate, jedes als eigene Klasse mit gemeinsamer Basis. Statt eine Liste von Hand zu pflegen, findet PHP get_declared_classes() sie selbst.
<?php
abstract class Schreiber {}
class CsvSchreiber extends Schreiber {}
class PdfSchreiber extends Schreiber {}
$kinder = array_filter(
get_declared_classes(),
static fn (string $k): bool => is_subclass_of($k, Schreiber::class)
);
print_r(array_values($kinder));
/* Array ( [0] => CsvSchreiber [1] => PdfSchreiber ) */
is_subclass_of() liefert bewusst false, wenn geprüfte und erwartete Klasse identisch sind. Die Basisklasse steht deshalb nicht im Ergebnis, was hier genau richtig ist. Die Einzelheiten dazu behandelt das Tutorial zu PHP is_subclass_of() und dem sicheren Prüfen von Vererbung.
Alle Umsetzer eines Interfaces finden
Dasselbe Muster funktioniert mit Schnittstellen. class_implements() liefert alle Interfaces einer Klasse, auch die geerbten.
<?php
interface Versendbar {}
$umsetzer = array_filter(
get_declared_classes(),
static fn (string $k): bool => in_array(
Versendbar::class,
class_implements($k) ?: [],
true
)
);
Der Ausdruck mit dem Fragezeichen-Doppelpunkt fängt den Fall ab, dass class_implements() ausnahmsweise false liefert. Ohne diese Absicherung bricht in_array() mit einem Typfehler ab.
Neue Klassen nach einem require erkennen
Damit ist der Fall aus der Einleitung lösbar. Die Liste wird vor und nach dem Einbinden geholt, der Unterschied ist die gesuchte Klasse.
<?php
function erweiterungLaden(string $datei, string $basis): ?object
{
$vorher = get_declared_classes();
require_once $datei;
$neu = array_diff(get_declared_classes(), $vorher);
foreach ($neu as $name) {
if (is_subclass_of($name, $basis)) {
return new $name();
}
}
return null; /* Datei brachte keine passende Klasse mit */
}
$plugin = erweiterungLaden('/plugins/newsletter.php', Erweiterung::class);
Die Prüfung mit is_subclass_of() vor dem Erzeugen ist kein Beiwerk. Ohne sie würde die erste beliebige Klasse aus der Datei erzeugt, auch eine Hilfsklasse ohne passende Schnittstelle. Zu beachten ist außerdem, dass require_once beim zweiten Aufruf nichts mehr lädt und die Differenz dann leer bleibt.
Der folgende Überblick zeigt, welcher Filter zu welcher Frage gehört.
flowchart TD
A[get_declared_classes] --> B{Wonach filtern}
B -->|Namensraum| C[str_starts_with]
B -->|Vererbung| D[is_subclass_of]
B -->|Interface| E[class_implements]
B -->|neu geladen| F[array_diff vorher nachher]
C --> G[Trefferliste]
D --> G
E --> G
F --> G
In allen vier Fällen liefert die Funktion nur das Rohmaterial, die Auswahl trifft der Filter.
PHP get_declared_classes() oder class_exists()?
Beide Funktionen wirken austauschbar und sind es nicht. class_exists() hat einen zweiten Parameter, der in der Vorgabe auf true steht und den Autoloader anstoßen darf. Die Funktion beantwortet damit nicht die Frage "ist die Klasse geladen", sondern "lässt sie sich laden", und sie verändert dabei den Zustand.
<?php
$geladen = in_array('App\\Model\\Rechnung', get_declared_classes(), true);
var_dump($geladen); // bool(false)
var_dump(class_exists('App\\Model\\Rechnung', false)); // bool(false), nur pruefen
var_dump(class_exists('App\\Model\\Rechnung')); // bool(true), laedt nach
/* jetzt steht sie auch in der Liste */
var_dump(in_array('App\\Model\\Rechnung', get_declared_classes(), true));
Für eine Diagnose ist deshalb entweder PHP get_declared_classes() oder class_exists() mit false als zweitem Parameter die richtige Wahl. Wer den Parameter weglässt, misst hinterher einen Zustand, den er selbst erzeugt hat.
Interfaces und Traits: die Geschwisterfunktionen
Klassen sind nur ein Drittel des Bildes. Es gibt zwei weitere Funktionen nach demselben Muster.
| Funktion | Liefert | Verfügbar seit |
get_declared_classes() | geladene Klassen | PHP 4 |
get_declared_interfaces() | geladene Schnittstellen | PHP 5 |
get_declared_traits() | geladene Traits | PHP 5.4 |
Traits sind keine Typen, sondern Kopiervorlagen für Code. Sie tauchen deshalb nicht in der Klassenliste auf und lassen sich auch nicht mit is_subclass_of() prüfen. Was sie stattdessen leisten, beschreibt das Tutorial zu PHP Traits und der Wiederverwendung von Code zwischen Klassen.
Mit Reflection zur Quelldatei
Bei gleichnamigen Klassen aus verschiedenen Paketen stellt sich die Frage, welche gewonnen hat. Die Antwort liefert die Reflection-API.
<?php
foreach (get_declared_classes() as $name) {
if (!str_starts_with($name, 'App\\')) {
continue;
}
$datei = (new ReflectionClass($name))->getFileName();
printf("%-40s %s\n", $name, $datei ?: '(eingebaut)');
}
Bei eingebauten Klassen liefert getFileName() den Wert false, weil sie nicht aus einer PHP-Datei stammen. Diese Kombination ist das übliche Werkzeug, wenn ein Autoloader eine unerwartete Datei lädt.
Wann die Funktion nicht passt
Bei jedem Aufruf entsteht ein Array mit allen geladenen Klassennamen, bei großen Anwendungen also mehrere hundert Einträge. In der Startphase, in einer Diagnoseseite oder in Testhilfen ist das völlig unerheblich. In einer Schleife, die pro Anfrage tausendfach durchlaufen wird, ist es Verschwendung. Dort wird die Liste einmal ermittelt und das Ergebnis gemerkt.
Ebenso wenig taugt PHP get_declared_classes() als Projektübersicht. Sie beschreibt einen Moment im Ablauf, und mit Autoloading ist dieser Moment bei jeder Anfrage ein anderer. Wer die aufrufende Klasse zur Laufzeit sucht, findet die passende Antwort im Tutorial zu PHP get_called_class().
Fazit
PHP get_declared_classes() liefert eine Liste, die allein selten nützt und mit einem Filter sofort wertvoll wird. Vier Filter decken fast alle Fälle ab: nach Namensraum, nach Basisklasse, nach Interface und über die Differenz vor und nach einem require.
Zwei Dinge sollte man dabei im Kopf behalten. Die Namen kommen ohne führenden Trenner zurück, und die Funktion zeigt nur den aktuellen Ladezustand. Wer stattdessen class_exists() zur Diagnose benutzt, verändert genau das, was er messen wollte.