PHP: Was ist stdClass?

stdClass ist eine leere Klasse. Keine Eigenschaften, keine Methoden, kein Konstruktor. Man begegnet ihr meistens, ohne sie angefordert zu haben — als Rückgabewert von json_decode(). Und dann hat man ein paar Fragen.

Was es ist

new stdClass()  : stdClass
Methoden        : []
Elternklasse    : false

Das war’s. Eine Klasse ohne Inhalt, die man mit Eigenschaften befüllen kann.

Und gleich das häufigste Missverständnis aus dem Weg geräumt: stdClass ist nicht die Basisklasse aller PHP-Objekte. Der Name legt das nahe — “standard class” —, aber es stimmt nicht:

stdClass ist Basis von ArrayObject? false

PHP hat, anders als Java oder C#, keine gemeinsame Wurzelklasse. Wenn du prüfen willst, ob etwas ein Objekt ist, nimm is_object() und nicht instanceof stdClass.

Woher man sie bekommt

Drei Wege, und den ersten hat man selten selbst gewählt:

<?php
  $a = json_decode('{"host":"localhost"}');   // stdClass
  $b = (object) ['host' => 'localhost'];      // stdClass
  $c = new stdClass();
  $c->host = 'localhost';
?>

Bei json_decode() ist das der Standard. Mit true als zweitem Parameter bekommst du stattdessen Arrays — und das ist meistens die bessere Wahl, dazu gleich mehr.

Die Sache mit den dynamischen Eigenschaften

Seit PHP 8.2 ist es deprecated, Eigenschaften auf ein Objekt zu schreiben, die nicht in der Klasse deklariert sind. stdClass ist davon aber ausgenommen:

eigene Klasse : Deprecated: Creation of dynamic property Eigene::$neu is deprecated
stdClass      : keine Meldung

Das ist der Grund, warum die Änderung Code mit json_decode()-Ergebnissen nicht betrifft, deine selbstgeschriebenen Datencontainer-Klassen aber schon. Für die brauchst du dann entweder das Attribut #[AllowDynamicProperties] oder — besser — deklarierst die Eigenschaften einfach.

In PHP 9 wird aus der Deprecation ein Error. stdClass bleibt ausgenommen.

Die Falle mit den Eigenschaftsnamen

Und jetzt der Punkt, den ich für den wichtigsten in diesem Artikel halte. Ein Array kann Schlüssel haben, die als Eigenschaftsname nicht funktionieren:

<?php
  $obj = (object) ['0' => 'null', 'mit leer' => 'x', 'ok' => 'y'];
?>

Die Eigenschaften sind alle da:

Array-Schlüssel : [0,"mit leer","ok"]

$obj->ok           : 'y'
$obj->{0}          : 'null'
$obj->{"0"}        : 'null'
$obj->{"mit leer"} : 'x'

Aber $obj->0 ist ein Syntaxfehler. $obj->mit leer auch. Du kommst nur mit geschweiften Klammern heran — und darauf muss man erst einmal kommen.

Das ist der Grund, json_decode($json, true) zu nehmen, wann immer die Schlüssel aus fremder Hand kommen. Bei einem Array ist jeder Schlüssel erreichbar, ganz gleich wie er aussieht:

<?php
  $daten = json_decode($json, true);
  echo $daten['mit leer'];   // unproblematisch
?>

Bis PHP 7.2 waren diese Eigenschaften übrigens gar nicht erreichbar. Seither gehen sie wenigstens mit den geschweiften Klammern.

Hin und zurück

(array) $obj keys    : [0,"mit leer","ok"]
get_object_vars keys : [0,"mit leer","ok"]

Bei stdClass liefern beide dasselbe, weil es keine Sichtbarkeit gibt. Bei einer eigenen Klasse mit private oder protected sieht das anders aus — dazu gibt es einen eigenen Artikel.

Was man beachten muss: Der Cast ist nicht rekursiv, json_decode() schon.

(object) auf verschachteltes Array : ->innen ist array
json_decode                        : ->innen ist stdClass

Fehlende Eigenschaften

$k->fehlt          : NULL   (Warning: Undefined property)
$k->fehlt ?? 'std' : 'std'
isset($k->fehlt)   : false
property_exists    : false

Wie bei Arrays: ?? für den Standardwert. property_exists() ist das Gegenstück zu array_key_exists() und der richtige Test, wenn null als Wert eine eigene Bedeutung hat.

Vergleichen und Kopieren

Zwei Dinge, die sich bei Objekten anders verhalten als bei Arrays.

Erstens der Vergleich:

$p == $q  : true    (gleicher Inhalt)
$p === $q : false   (nicht dieselbe Instanz)
$p === $r : true

Bei Objekten prüft === auf Identität — sind das zwei Namen für dasselbe Objekt? Bei Arrays vergleicht === dagegen den Inhalt. Wer das verwechselt, schreibt einen Vergleich, der immer false liefert.

Zweitens die Übergabe:

nach aendere($z)                     : a = 99   <- verändert, ohne &
wieder auf 1 gesetzt, dann ersetze() : a = 1    <- unverändert
<?php
  function aendere(stdClass $o): void { $o->a = 99; }   // wirkt nach außen
  function ersetze(stdClass $o): void { $o = (object) ['a' => 42]; } // nicht
?>

Das wird oft als “Objekte werden per Referenz übergeben” beschrieben, und das ist nicht ganz richtig. Die Variable hält einen Verweis auf das Objekt, ist aber selbst keine Referenz. Deshalb wirkt eine Änderung an einer Eigenschaft nach außen, eine Neuzuweisung des Parameters aber nicht.

Und drittens das Kopieren:

nach clone und Änderung : Original n = 99   <- mitgeändert
über serialize          : Original n = 1

clone ist flach. Verschachtelte Objekte werden nicht mitkopiert, sondern geteilt. Wenn du wirklich eine tiefe Kopie brauchst, geht das über unserialize(serialize($obj)) — oder du schreibst ein __clone(), das die inneren Objekte selbst klont.

Wann man es nimmt und wann nicht

stdClass ist gut für das, wofür sie gedacht ist: ein Datencontainer ohne Verhalten, dessen Struktur man vorher nicht kennt. Eine dekodierte API-Antwort etwa.

Sobald die Struktur feststeht, ist eine eigene Klasse die bessere Wahl:

<?php
class Konfig {
  public function __construct(
    public readonly string $host,
    public readonly int $port,
  ) {}
}
?>

Was das bringt, habe ich nachgemessen — und dabei etwas gelernt. Beim Lesen eines Tippfehlers verhalten sich beide gleich:

Konfig::$tipfehler   : Warning: Undefined property
stdClass::$tipfehler : Warning: Undefined property

Die typisierte Klasse hilft hier also nicht. Der Unterschied zeigt sich erst beim Schreiben:

stdClass : stillschweigend angelegt
Konfig   : Deprecated: Creation of dynamic property

Das ist eine genauere Aussage als das übliche “typisierte Klassen fangen Tippfehler ab”. Sie fangen sie beim Zuweisen ab, nicht beim Lesen. In PHP 9 wird aus der Deprecation ein Error, dann ist der Unterschied deutlicher.

Was eine eigene Klasse darüber hinaus bringt: Typen, die tatsächlich geprüft werden, ein Konstruktor, der Pflichtfelder erzwingt, readonly, sowie eine Stelle, an der man nachlesen kann, welche Felder es überhaupt gibt. Das letzte ist im Alltag der größte Gewinn.

Zusammenfassung

  • stdClass ist eine leere Klasse — und nicht die Basisklasse aller Objekte.
  • Man bekommt sie von json_decode() ohne true und vom Cast (object).
  • Sie ist von der 8.2er-Deprecation für dynamische Eigenschaften ausgenommen, eigene Klassen sind es nicht.
  • Schlüssel wie 0 oder 'mit leer' werden zu Eigenschaften, die nur mit $obj->{...} erreichbar sind — bei fremden Daten lieber json_decode($j, true).
  • Bei Objekten prüft === die Identität, nicht den Inhalt.
  • clone ist flach.
  • Eine eigene Klasse fängt Tippfehler beim Schreiben ab, beim Lesen nicht.

Hinweis zu Netcup (Werbung)

Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).