get_class, instanceof und is_callable im Detail
Nicht jede Frage zur Struktur eines Objekts braucht ReflectionClass. PHP bringt eine Reihe leichtgewichtiger Introspektions-Funktionen mit, von get_class über instanceof bis is_callable, die ohne den Overhead der Reflection API auskommen und für einen Großteil der alltäglichen Typprüfungen völlig ausreichen.
Inhaltsverzeichnis
- 1. Warum es neben der Reflection API einfache Introspektion gibt
- 2. get_class, get_parent_class und class_exists im Detail
- 3. instanceof, is_a und is_subclass_of: Typprüfung zur Laufzeit
- 4. get_object_vars und get_class_methods ohne Reflection-Objekte
- 5. is_callable und method_exists: Aufrufbarkeit sicher prüfen
- 6. class_implements und class_uses: Interfaces und Traits auflisten
- 7. Praxisbeispiel: ein leichtgewichtiger Type-Checker
- 8. Grenzen der einfachen Introspektion: wann Reflection nötig ist
- 9. Introspektions-Funktionen im Vergleich zur Reflection API
- 10. Zusammenfassung
- 11. FAQ
1. Warum es neben der Reflection API einfache Introspektion gibt
Lange bevor PHP eine vollständige objektorientierte Reflection API besaß, gab es bereits eine Reihe prozeduraler Funktionen zur Introspektion: get_class(), instanceof, method_exists() und ähnliche Werkzeuge existieren teils seit PHP 4 und wurden nie durch die Reflection API ersetzt, weil sie ein anderes Problem lösen. Wo ReflectionClass ein vollständiges, in sich konsistentes Objektmodell für beliebig komplexe Analysen bietet, beantworten die eingebauten Introspektions-Funktionen jeweils eine einzelne, häufig gestellte Frage direkt und ohne Umweg über ein zusätzliches Objekt.
Der praktische Vorteil dieser Funktionen liegt in ihrer Einfachheit: Eine Prüfung wie $object instanceof SomeInterface ist für Leser sofort verständlich, erzeugt keine temporären Reflection-Objekte und ist dabei durch die PHP-Engine selbst optimiert. Für viele alltägliche Aufgaben, etwa das Prüfen, ob ein Objekt ein bestimmtes Interface implementiert oder ob eine Methode aufrufbar ist, ist diese Art der Introspektion nicht nur ausreichend, sondern die klar bessere Wahl gegenüber der vollen Reflection API.
Dieser Artikel ordnet die wichtigsten dieser Funktionen ein, zeigt, wofür sie jeweils gedacht sind, und markiert die Grenze, ab der ein Wechsel zur vollen Reflection API tatsächlich nötig wird, etwa wenn Parameter-Typen, Docblocks oder Vererbungshierarchien programmatisch ausgewertet werden müssen.
2. get_class, get_parent_class und class_exists im Detail
get_class() liefert für eine gegebene Objektinstanz den vollqualifizierten Klassennamen als String, ohne dass dafür ein Reflection-Objekt konstruiert werden muss. Seit PHP 8.0 kann die Funktion ohne Argument auch innerhalb einer Methode aufgerufen werden und liefert dann den Namen der aktuellen Klasse, was self::class als Konstante ergänzt, aber zur Laufzeit den tatsächlichen Klassennamen einer Instanz liefert, was bei Vererbung wichtig ist, da self::class immer die deklarierende Klasse liefert, get_class($this) hingegen die tatsächliche Laufzeitklasse.
get_parent_class() liefert die direkte Elternklasse oder false, wenn keine existiert, und class_exists() prüft, ob ein Klassenname überhaupt bekannt ist, bevor man versucht, ihn zu instanziieren oder zu reflektieren. Diese drei Funktionen zusammen decken bereits einen großen Teil der Fälle ab, in denen Code lediglich wissen muss, welche Klasse und welche Elternklasse vorliegen, ohne tiefer in Methoden oder Properties einsteigen zu müssen.
<?php
declare(strict_types=1);
class BaseNotification
{
public function describe(): string
{
// get_class($this) returns the actual runtime class, not the declaring one
return sprintf('%s (parent: %s)', get_class($this), get_parent_class($this) ?: 'none');
}
}
final class OrderShippedNotification extends BaseNotification
{
}
$notification = new OrderShippedNotification();
echo $notification->describe() . PHP_EOL; // OrderShippedNotification (parent: BaseNotification)
// Guard before dynamic instantiation from a config value
$className = 'OrderShippedNotification';
if (class_exists($className)) {
$instance = new $className();
}
3. instanceof, is_a und is_subclass_of: Typprüfung zur Laufzeit
Der Operator instanceof ist die idiomatische, von der PHP-Engine direkt optimierte Art, zur Laufzeit zu prüfen, ob ein Objekt eine bestimmte Klasse implementiert, von ihr erbt oder ein bestimmtes Interface umsetzt. Er funktioniert allerdings nur, wenn der Klassenname als Literal oder als bereits geladene Variable vorliegt. Muss der Zielklassenname hingegen als String zur Laufzeit ausgewertet werden, etwa aus einer Konfigurationsdatei, kommt is_a() ins Spiel, das denselben Zweck erfüllt, aber einen String als zweites Argument akzeptiert.
is_subclass_of() unterscheidet sich von is_a() in einem wichtigen Detail: Es liefert false, wenn die geprüfte Klasse exakt der übergebenen Klasse entspricht, prüft also ausschließlich echte Unterklassen, während is_a() auch bei Übereinstimmung mit der eigenen Klasse true zurückgibt. Dieser Unterschied ist bei generischen Frameworks relevant, die zwischen exakter Typübereinstimmung und tatsächlicher Vererbung unterscheiden müssen.
<?php
declare(strict_types=1);
interface Shippable
{
}
class Order implements Shippable
{
}
class ExpressOrder extends Order
{
}
$order = new ExpressOrder();
var_dump($order instanceof Shippable); // bool(true) — literal class name
var_dump(is_a($order, 'Order')); // bool(true) — string, includes exact match
var_dump(is_subclass_of($order, 'Order')); // bool(true) — true subclass
var_dump(is_subclass_of(new Order(), 'Order')); // bool(false) — exact match, no subclass
// Dynamic class name from configuration, resolved at runtime
$allowedBaseClass = 'Order';
if (is_a($order, $allowedBaseClass)) {
echo 'Order is processable' . PHP_EOL;
}
4. get_object_vars und get_class_methods ohne Reflection-Objekte
get_object_vars() liefert ein assoziatives Array aller sichtbaren Properties einer Objektinstanz, wobei die Sichtbarkeit vom Kontext des Aufrufers abhängt: Wird die Funktion von außerhalb der Klasse aufgerufen, liefert sie nur öffentliche Properties, wird sie von innerhalb einer Methode der Klasse selbst aufgerufen, liefert sie zusätzlich private und protected Properties. Dieses kontextabhängige Verhalten unterscheidet sich fundamental von ReflectionProperty, das unabhängig vom Aufrufkontext funktioniert.
get_class_methods() liefert analog die Namen aller öffentlichen Methoden einer Klasse als einfaches Array von Strings, ohne Informationen über Parameter oder Rückgabetypen. Für Aufgaben, die lediglich eine Liste von Namen benötigen, etwa das Generieren einer einfachen API-Dokumentation oder das Prüfen, ob eine bestimmte Methode überhaupt existiert, sind diese beiden Funktionen schneller geschrieben und leichter lesbar als der entsprechende Code mit ReflectionClass::getProperties() und getMethods().
<?php
declare(strict_types=1);
final class InvoiceLine
{
public string $sku = 'SKU-1';
public int $quantity = 2;
private float $unitPrice = 19.99;
public function total(): float
{
return $this->quantity * $this->unitPrice;
}
private function auditTrail(): void
{
}
}
$line = new InvoiceLine();
// Called from outside the class: only public properties are visible
print_r(get_object_vars($line)); // ['sku' => 'SKU-1', 'quantity' => 2]
// Public methods only, no visibility or type information
print_r(get_class_methods($line)); // ['total']
5. is_callable und method_exists: Aufrufbarkeit sicher prüfen
is_callable() prüft, ob ein Wert, sei es ein String, ein Array in der Form [$objekt, 'methode'] oder eine Closure, tatsächlich als Funktion aufgerufen werden kann, bevor der eigentliche Aufruf über call_user_func() oder direkte Syntax erfolgt. Diese Prüfung ist essenziell für generische Dispatcher, die Callables aus externen Quellen wie Konfigurationsdateien oder Routing-Tabellen entgegennehmen und vor der Ausführung validieren müssen, ohne blind einen möglicherweise nicht existierenden Aufruf zu riskieren.
method_exists() und property_exists() ergänzen diese Prüfung auf einer feingranulareren Ebene: Sie beantworten die einfache Frage, ob eine bestimmte Methode oder Property auf einer Klasse oder Instanz überhaupt deklariert ist, unabhängig von deren Sichtbarkeit. Für Bibliotheken, die optional vorhandene Hooks aufrufen wollen, etwa ein onBeforeSave(), das nur manche Klassen implementieren, ist diese Kombination der pragmatischste Weg, ohne die volle Reflection API zu bemühen.
<?php
declare(strict_types=1);
final class HookDispatcher
{
public function dispatchOptionalHook(object $subject, string $hookName): void
{
// Only invoke the hook if it actually exists and is callable
if (method_exists($subject, $hookName) && is_callable([$subject, $hookName])) {
$subject->{$hookName}();
}
}
}
final class ProductImporter
{
public function onBeforeSave(): void
{
echo 'Running pre-save validation' . PHP_EOL;
}
}
$dispatcher = new HookDispatcher();
$dispatcher->dispatchOptionalHook(new ProductImporter(), 'onBeforeSave');
$dispatcher->dispatchOptionalHook(new ProductImporter(), 'onAfterSave'); // silently skipped
6. class_implements und class_uses: Interfaces und Traits auflisten
class_implements() liefert ein Array aller Interfaces, die eine Klasse direkt oder über Vererbung implementiert, indiziert und wertgleich mit dem jeweiligen Interface-Namen, was die Prüfung mit isset() statt einer Schleife erlaubt. class_parents() liefert entsprechend die komplette Vererbungskette als Array, und class_uses() listet alle Traits auf, die eine Klasse direkt einbindet, wobei verschachtelte Traits, also Traits innerhalb von Traits, nicht automatisch aufgelöst werden.
Diese Funktionen sind besonders nützlich für generische Registrierungsmechanismen, etwa ein Plugin-System, das prüfen muss, ob eine übergebene Klasse zu einer bestimmten Kategorie gehört, ohne jede mögliche Klasse einzeln kennen zu müssen. Ein Event-Dispatcher, der nur an Objekte liefert, die ein bestimmtes Marker-Interface implementieren, kommt mit class_implements() und einem einfachen isset()-Check aus, statt für jede Prüfung ein ReflectionClass-Objekt zu erzeugen.
<?php
declare(strict_types=1);
interface Auditable
{
}
interface Cacheable
{
}
trait TimestampableTrait
{
public ?string $updatedAt = null;
}
final class ProductEntity implements Auditable, Cacheable
{
use TimestampableTrait;
}
$interfaces = class_implements(ProductEntity::class);
var_dump(isset($interfaces[Auditable::class])); // bool(true)
$traits = class_uses(ProductEntity::class);
var_dump(isset($traits[TimestampableTrait::class])); // bool(true)
7. Praxisbeispiel: ein leichtgewichtiger Type-Checker
Die vorgestellten Funktionen lassen sich zu einem kompakten, gut lesbaren Type-Checker kombinieren, der ohne ein einziges Reflection-Objekt auskommt und dennoch eine Reihe strukturierter Prüfungen über eine Klasse durchführt. Ein solches Werkzeug eignet sich beispielsweise für Validierungsschichten, die vor der eigentlichen fachlichen Verarbeitung prüfen wollen, ob eine übergebene Instanz die erwarteten Interfaces implementiert und bestimmte Methoden bereitstellt.
Der Vorteil gegenüber einer Reflection-basierten Lösung liegt in der Übersichtlichkeit: Jede einzelne Prüfung ist eine einfache, klar benannte Funktion, deren Bedeutung sich sofort erschließt, ohne dass der Leser wissen muss, welche Methoden auf ReflectionClass welche Information liefern.
<?php
declare(strict_types=1);
final class LightweightTypeChecker
{
/** @param class-string $requiredInterface */
public function implementsAll(object $subject, array $requiredInterfaces): bool
{
$implemented = class_implements($subject);
foreach ($requiredInterfaces as $interface) {
if (!isset($implemented[$interface])) {
return false;
}
}
return true;
}
public function hasCallableMethod(object $subject, string $methodName): bool
{
return method_exists($subject, $methodName)
&& is_callable([$subject, $methodName]);
}
public function usesTrait(object $subject, string $traitName): bool
{
return isset(class_uses($subject)[$traitName]);
}
}
$checker = new LightweightTypeChecker();
$product = new ProductEntity();
var_dump($checker->implementsAll($product, [Auditable::class, Cacheable::class])); // true
var_dump($checker->usesTrait($product, TimestampableTrait::class)); // true
8. Grenzen der einfachen Introspektion: wann Reflection nötig ist
Die vorgestellten Funktionen erreichen dort ihre Grenze, wo detaillierte Informationen über Methodensignaturen gefragt sind: Parametertypen, Standardwerte, Nullability oder Rückgabetypen sind über keine der einfachen Introspektions-Funktionen zugänglich. Sobald ein Autowiring-Container die Konstruktor-Parameter einer Klasse auflösen muss, führt kein Weg an ReflectionMethod und ReflectionParameter vorbei, weil diese Informationen schlicht nicht Teil der prozeduralen API sind.
Ebenso stößt einfache Introspektion an ihre Grenze, wenn Attribute ausgelesen, private Properties direkt gesetzt oder Objekte ohne Ausführung des Konstruktors erzeugt werden müssen. In all diesen Fällen ist die volle Reflection API das richtige Werkzeug, während die in diesem Artikel behandelten Funktionen ihre Stärke gerade in den häufigen, einfachen Fällen ausspielen, in denen der zusätzliche Aufwand eines Reflection-Objekts unnötigen Ballast bedeuten würde.
9. Introspektions-Funktionen im Vergleich zur Reflection API
Die folgende Tabelle ordnet die wichtigsten Aufgaben den jeweils passenden Werkzeugen zu und macht die Entscheidung im konkreten Projekt einfacher.
| Aufgabe | Einfache Introspektion | Reflection API nötig? | Grund |
|---|---|---|---|
| Klassenname einer Instanz ermitteln | get_class() |
Nein | Einfacher String-Rückgabewert genügt |
| Interface-Implementierung prüfen | instanceof / class_implements() |
Nein | Direkter Boolean-Check ausreichend |
| Konstruktor-Parameter für Autowiring auflösen | Nicht möglich | Ja | ReflectionParameter::getType() nötig |
| Private Property von außen setzen | Nicht möglich | Ja | ReflectionProperty::setValue() nötig |
| Prüfen, ob eine Methode aufrufbar ist | is_callable() |
Nein | Eingebaute Funktion deckt genau diesen Fall ab |
Die Tabelle zeigt das durchgängige Muster: Sobald die Frage mit einem einfachen Boolean oder String beantwortbar ist, reicht eine der eingebauten Introspektions-Funktionen. Sobald jedoch strukturierte Informationen über Typen, Signaturen oder Attribute gebraucht werden, ist die volle Reflection API unumgänglich.
Mironsoft
PHP-Performance-Reviews und schlanke Architektur-Entscheidungen
Unnötigen Reflection-Overhead im Code finden?
Wir prüfen bestehende PHP-Codebasen auf überflüssige ReflectionClass-Aufrufe und ersetzen sie dort, wo eingebaute Introspektions-Funktionen völlig ausreichen, für messbar schnelleren Code.
Performance-Audit
Reflection-Aufrufe in heißen Codepfaden identifizieren
Code-Vereinfachung
Überflüssige Reflection-Objekte durch schlanke Funktionen ersetzen
Architektur-Beratung
Wann Reflection wirklich nötig ist, entscheiden wir gemeinsam
10. Zusammenfassung
Introspektion jenseits der Reflection API deckt mit Funktionen wie get_class(), instanceof, is_a(), get_object_vars() und is_callable() einen großen Teil der alltäglichen Fragen zur Struktur eines Objekts ab, ohne den Overhead eines Reflection-Objekts zu erzeugen. Diese Funktionen sind seit langem Teil der Sprache, werden von der Engine direkt optimiert und liefern klar lesbaren Code für Typprüfungen, Interface-Checks und Aufrufbarkeits-Tests.
Die Grenze zur vollen Reflection API verläuft dort, wo strukturierte Informationen über Methodensignaturen, Attribute oder private Zustände gebraucht werden, etwa bei Autowiring-Containern oder Serializern. Wer beide Werkzeuge kennt und gezielt einsetzt, schreibt Code, der in den häufigen Fällen schlank bleibt und nur dort auf die volle Reflection API zurückgreift, wo sie tatsächlich unumgänglich ist.
Introspektion jenseits der Reflection API — Das Wichtigste auf einen Blick
Klasse und Typ
get_class(), instanceof, is_a() und is_subclass_of() für Typprüfungen ohne Reflection-Objekt.
Struktur
get_object_vars() und get_class_methods() liefern Namen ohne Typ- oder Sichtbarkeitsdetails.
Aufrufbarkeit
is_callable() und method_exists() prüfen sicher, bevor ein dynamischer Aufruf erfolgt.
Grenze
Parametertypen, Attribute und private Zustände brauchen die volle Reflection API.