PHP: Wie wandelt man ein Objekt in ein Array um?

Der Cast (array) $objekt ist die Antwort, die man überall findet. Bei einem stdClass funktioniert er auch. Bei einer eigenen Klasse bekommst du damit Schlüssel, die du nicht mehr ansprechen kannst.

Der einfache Fall

Ein stdClass, wie ihn json_decode() liefert, lässt sich direkt casten:

<?php
  $obj = new stdClass();
  $obj->host = 'localhost';
  $obj->port = 3306;

  print_r((array) $obj);
?>
Array
(
    [host] => localhost
    [port] => 3306
)

Solange alle Eigenschaften public sind, ist die Sache erledigt.

Bei eigenen Klassen wird es hässlich

Jetzt dieselbe Sache mit einer richtigen Klasse:

<?php
class Server {
  public string    $host     = 'localhost';
  protected int    $port     = 3306;
  private string   $passwort = 'geheim';
}

$arr = (array) new Server();
?>

Das Array hat drei Einträge. Nur heißen sie nicht so, wie man denkt:

host                (Länge 4)
\0*\0port           (Länge 7)
\0Server\0passwort  (Länge 16)

Da stehen NUL-Bytes in den Schlüsseln. Das Schema ist:

  • protected\0 + * + \0 + Name
  • private\0 + Klassenname + \0 + Name

Die praktische Folge:

isset($arr['port'])      : false
isset($arr["\0*\0port"]) : true

Du kommst an den Wert also nur heran, wenn du den NUL-Byte-Schlüssel exakt nachbaust. Und dafür musst du wissen, in welcher Klasse die Eigenschaft deklariert wurde — bei Vererbung ist das nicht unbedingt die Klasse des Objekts.

Besonders ärgerlich ist die Anzeige. print_r() stellt den Schlüssel so dar:

[port:protected] => 3306

Das sieht aus, als hieße der Schlüssel port, mit einer Zusatzinformation dahinter. Tut er nicht. print_r() interpretiert das NUL-Byte-Schema für dich und macht daraus etwas Lesbares. Wer daraufhin nach $arr['port'] greift, sucht lange.

Nebenbei ist der Cast nicht der einzige Ort, an dem die Sichtbarkeit hineinregiert. json_encode() nimmt ausschließlich die öffentlichen Eigenschaften mit:

json_encode($server) : {"host":"localhost"}

serialize() nimmt zwar alle mit, verwendet dafür aber dieselben NUL-Byte-Namen:

O:6:"Server":3:{s:4:"host";...s:7:"\0*\0port";...s:16:"\0Server\0passwort";...}

Die s:7: bzw. s:16: sind die Längenangaben — dieselben Zahlen wie oben.

get_object_vars() — und wo du es aufrufst

get_object_vars() liefert die Eigenschaften mit sauberen Schlüsseln. Welche du siehst, hängt allerdings davon ab, von wo aus du die Funktion aufrufst:

von außen  : ["host"]
von innen  : ["host","port","passwort"]

Von außerhalb der Klasse siehst du nur public. Innerhalb der Klasse siehst du alles — und bekommst normale Schlüssel ohne NUL-Bytes.

Damit ist der saubere Weg vorgezeichnet: Man schreibt die Umwandlung in die Klasse hinein.

<?php
class Server {
  public string  $host     = 'localhost';
  protected int  $port     = 3306;
  private string $passwort = 'geheim';

  public function toArray(): array {
    return get_object_vars($this);
  }
}
?>

Das hat außerdem den Vorteil, dass du entscheidest, was nach außen geht. Das Passwort willst du in dem Array vermutlich gar nicht haben.

Der Cast ist nicht rekursiv

Der nächste Stolperstein. Bei verschachtelten Objekten wandelt der Cast nur die oberste Ebene um:

(array) $aussen : ['innen'] ist stdClass  <- immer noch ein Objekt
über JSON       : ['innen'] ist array

Deshalb findet man als Lösung überall diesen Umweg:

<?php
  $arr = json_decode(json_encode($obj), true);
?>

Der arbeitet tatsächlich rekursiv. json_decode() mit true als zweitem Parameter liefert überall Arrays statt stdClass:

json_decode($json)        : stdClass
->db ist                  : stdClass
json_decode($json, true)  : array
['db'] ist                : array

Aber der JSON-Umweg kostet dich etwas

Und zwar Typinformationen. Der Round-Trip durch JSON kennt nur die Typen, die JSON kennt:

1.0 wird zu          : 1 (int)
PHP_INT_MAX bleibt   : true
DateTimeImmutable    : {"date":"2026-08-28 00:00:00.000000","timezone_type":3,"timezone":"UTC"}

Die erste Zeile ist die, die man in der Praxis trifft. Ein Preis 19.0 kommt als int 19 zurück, weil JSON zwischen 19 und 19.0 nicht unterscheidet. Solange du damit nur rechnest, geht das gut. Bei einem ===-Vergleich oder einer Funktionssignatur mit float fällt es auf.

Und jedes Objekt, das nicht bloß ein Datencontainer ist, verliert seinen Typ. Aus dem DateTimeImmutable wird ein Array mit drei Feldern. Das lässt sich nicht zurückverwandeln.

Was übrigens funktioniert: PHP_INT_MAX überlebt den Umweg korrekt. Das wird gelegentlich anders behauptet.

Was ich empfehle

  • stdClass aus json_decode(): Gib gleich true als zweiten Parameter mit, dann brauchst du gar nicht umzuwandeln.
  • Eigene Klasse: Schreib eine toArray()-Methode. Zehn Zeilen, dafür vorhersagbar.
  • Fremde Klasse, die du nicht ändern kannst: get_object_vars(), wenn dir die öffentlichen Eigenschaften reichen. Sonst die Reflection-API.
  • Den JSON-Umweg nur, wenn dir Typen egal sind.

Zusammenfassung

  • (array) funktioniert nur bei stdClass bzw. rein öffentlichen Klassen problemlos.
  • protected wird zu "\0*\0name", private zu "\0Klasse\0name".
  • print_r() zeigt [port:protected] an — das ist nicht der Schlüsselname.
  • get_object_vars() sieht von innen alles, von außen nur public.
  • Der Cast ist nicht rekursiv, json_decode(..., true) schon.
  • Der JSON-Umweg macht aus 1.0 ein int und aus Objekten Arrays.

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, ...).