Ein Array in PHP kann viel, aber es kann nichts von allein. Es prüft keine Werte, es kennt keine Regeln, und wer eine Liste kapseln will, landet schnell bei einer Klasse mit einem öffentlichen Array darin, in das dann doch wieder jeder hineinschreibt. Genau an dieser Stelle setzt PHP ArrayObject an: eine Klasse aus der Standardbibliothek, die sich nach aussen wie ein Array anfühlt und innen eigene Logik mitbringt.
Bevor der erste Code kommt, lohnt die ehrliche Frage, wann sich der Umweg überhaupt rechnet.
Wozu PHP ArrayObject, wenn es Arrays gibt?
Die kurze Antwort: meistens brauchst du es nicht. Ein normales Array ist schneller, sparsamer und jedem Entwickler sofort vertraut. Wer eine Liste von Namen durchläuft, hat mit ArrayObject nichts gewonnen ausser einem zusätzlichen Objekt im Speicher.
Interessant wird die Klasse an dem Punkt, an dem Verhalten mit den Daten mitreisen soll. Ein Warenkorb, der ungültige Positionen gar nicht erst annimmt. Eine Konfiguration, die einen fehlenden Schlüssel mit einer verständlichen Meldung quittiert statt mit einer Warning. Eine Sammlung, die beim Iterieren immer sortiert ausgibt, ohne dass der Aufrufer daran denken muss. In all diesen Fällen willst du den bequemen Klammerzugriff behalten und trotzdem eine Stelle haben, an der Regeln greifen.
Der zweite Grund ist die Übergabesemantik. Ein Array wird in PHP kopiert, sobald es geschrieben wird. Ein PHP ArrayObject wird als Objekt weitergereicht, also per Handle. Wer eine Sammlung durch mehrere Funktionen schleust und Änderungen behalten will, spart sich damit die Referenz-Kaufmanns-Und im Funktionskopf.
Ein PHP ArrayObject erzeugen und benutzen
Der Konstruktor nimmt ein vorhandenes Array entgegen. Danach verhält sich ein PHP ArrayObject bei den drei wichtigsten Zugriffsarten wie ein Array: Klammerzugriff, count() und foreach.
<?php
$korb = new ArrayObject(['Brot', 'Milch']);
$korb[] = 'Kaffee';
echo $korb[0]; /* Brot */
echo count($korb); /* 3 */
foreach ($korb as $position) {
echo $position . "\n";
}
Dass das funktioniert, liegt an vier Interfaces, die die Klasse mitbringt. ArrayAccess erlaubt den Klammerzugriff, Countable macht count() möglich, IteratorAggregate liefert den Iterator für foreach, und Serializable regelt das Speichern. Wer wissen will, wie solche Verträge in PHP funktionieren, findet das im Tutorial zu PHP Interfaces ausführlich erklärt.
Was nicht funktioniert, ist der Punkt, an dem die meisten zum ersten Mal stolpern.
Zurück zum Array mit getArrayCopy()
Die Array-Funktionen von PHP nehmen kein Objekt entgegen. array_map(), array_filter(), sort() und in_array() erwarten ein echtes Array und quittieren ein PHP ArrayObject mit einem TypeError. Der Ausweg heisst getArrayCopy().
<?php
$korb = new ArrayObject(['Brot', 'Milch']);
/* Falsch: array_map erwartet ein Array */
/* $gross = array_map('strtoupper', $korb); */
$gross = array_map('strtoupper', $korb->getArrayCopy());
print_r($gross); /* Array ( [0] => BROT [1] => MILCH ) */
Der Name der Methode ist wörtlich zu nehmen. Du bekommst eine Kopie, keine Referenz. Änderungen an dem zurückgegebenen Array wirken nicht auf das Objekt zurück. Wer das übersieht, sucht später lange nach Werten, die scheinbar verschwunden sind.
Eigene Logik einbauen: offsetSet() überschreiben
Jetzt kommt der eigentliche Grund, ein PHP ArrayObject einzusetzen. Beim Erben lassen sich die vier Zugriffsmethoden ersetzen, und offsetSet() ist dabei die wichtigste: sie läuft bei jedem schreibenden Klammerzugriff. Damit landet die Validierung an genau einer Stelle, egal von wo geschrieben wird.
<?php
class Warenkorb extends ArrayObject
{
public function offsetSet(mixed $schluessel, mixed $wert): void
{
if (!is_array($wert) || !isset($wert['menge'])) {
throw new InvalidArgumentException('Position braucht eine Menge.');
}
if ($wert['menge'] < 1) {
throw new InvalidArgumentException('Menge muss mindestens 1 sein.');
}
parent::offsetSet($schluessel, $wert);
}
public function gesamtmenge(): int
{
$summe = 0;
foreach ($this as $position) {
$summe += $position['menge'];
}
return $summe;
}
}
$korb = new Warenkorb();
$korb[] = ['artikel' => 'Kaffee', 'menge' => 2];
$korb[] = ['artikel' => 'Tee', 'menge' => 1];
echo $korb->gesamtmenge(); /* 3 */
/* Diese Zeile wirft eine Exception, bevor etwas gespeichert wird: */
/* $korb[] = ['artikel' => 'Zucker', 'menge' => 0]; */
Der Aufruf von parent::offsetSet() am Ende ist Pflicht. Ohne ihn prüft die Methode zwar brav, speichert aber nie etwas. Neben offsetSet() stehen offsetGet(), offsetExists() und offsetUnset() zur Verfügung, mit denen sich Lesezugriffe, Existenzprüfungen und Löschvorgänge genauso abfangen lassen. Wer sich mit dem Erben von Basisklassen noch schwertut, findet die Grundlagen unter Abstrakte Klassen in PHP.
Die Konstruktor-Flags STD_PROP_LIST und ARRAY_AS_PROPS
Der zweite Konstruktor-Parameter nimmt Flags entgegen. Praktisch relevant ist vor allem ARRAY_AS_PROPS, weil es die Elemente zusätzlich als Eigenschaften zugänglich macht.
<?php
$standard = new ArrayObject(['host' => 'localhost']);
echo $standard['host']; /* localhost */
/* echo $standard->host; Fehler: Property existiert nicht */
$alsProps = new ArrayObject(
['host' => 'localhost'],
ArrayObject::ARRAY_AS_PROPS
);
echo $alsProps['host']; /* localhost */
echo $alsProps->host; /* localhost, beides funktioniert */
Das zweite Flag, STD_PROP_LIST, betrifft einen Sonderfall: es sorgt dafür, dass echte Objekteigenschaften bei var_dump() und beim Iterieren getrennt von den Array-Elementen behandelt werden. In der Praxis brauchst du es selten. Ein häufiger Irrtum ist, an dieser Stelle die Iterator-Klasse angeben zu wollen. Die gehört an die dritte Position, nicht an die zweite.
Iterieren und sortieren
Sortierfunktionen gibt es als Methoden direkt am Objekt. Das ist bequemer, als jedes Mal über getArrayCopy() zu gehen, und es ändert die Reihenfolge im Objekt selbst.
<?php
$preise = new ArrayObject([
'Kaffee' => 8.90,
'Brot' => 2.40,
'Milch' => 1.15,
]);
$preise->uasort(fn(float $a, float $b): int => $a <=> $b);
foreach ($preise as $artikel => $preis) {
echo $artikel . ': ' . number_format($preis, 2, ',', '.') . " Euro\n";
}
/*
Milch: 1,15 Euro
Brot: 2,40 Euro
Kaffee: 8,90 Euro
*/
Zur Verfügung stehen asort(), ksort(), uasort(), uksort() und natsort(). Wer noch mehr Kontrolle braucht, tauscht über setIteratorClass() den Iterator aus, den getIterator() zurückliefert. Standardmässig ist das ein ArrayIterator.
Drei Stolperfallen, die jeden treffen
Diese drei Punkte kosten beim ersten Kontakt mit der Klasse regelmässig Zeit. Sie sind kein Fehler in PHP, sondern eine Folge davon, dass ein Objekt eben doch kein Array ist.
json_encode() liefert ein leeres Objekt Ohne JsonSerializable serialisiert PHP nur öffentliche Eigenschaften, und die hat ein ArrayObject nicht. Heraus kommt {}. Entweder du übergibst getArrayCopy(), oder deine Klasse implementiert jsonSerialize().
Verschachtelte Schreibzugriffe greifen nicht Ein Ausdruck wie $obj['liste'][] = 'x' läuft ins Leere, weil ein überladenes Element nicht per Referenz zurückgegeben wird. Der Weg führt über eine Zwischenvariable: auslesen, ändern, zurückschreiben.
Das Objekt wird nicht kopiert Wer ein PHP ArrayObject an eine Funktion übergibt und dort ändert, ändert auch das Original. Bei einem Array wäre das nicht passiert. Eine echte Kopie entsteht mit clone, und selbst die ist flach.
Der erste Punkt lässt sich in wenigen Zeilen dauerhaft beheben.
<?php
$daten = new ArrayObject(['a' => 1, 'b' => 2]);
echo json_encode($daten); /* {} */
echo json_encode($daten->getArrayCopy()); /* {"a":1,"b":2} */
class Nutzdaten extends ArrayObject implements JsonSerializable
{
public function jsonSerialize(): array
{
return $this->getArrayCopy();
}
}
echo json_encode(new Nutzdaten(['a' => 1])); /* {"a":1} */
Damit sind die Grundlagen abgedeckt. Bleibt die Frage, ob die Klasse für dein Vorhaben überhaupt der richtige Ausgangspunkt ist.
PHP ArrayObject erben oder ArrayAccess selbst implementieren?
Beide Wege führen zu einer Klasse, die sich wie ein Array benutzen lässt. Der Unterschied liegt darin, wie viel du geschenkt bekommst und wie viel Ballast du mitschleppst.
| Kriterium | ArrayObject erweitern | Interfaces selbst umsetzen |
| Aufwand am Anfang | Gering, eine Methode genügt | Höher, vier bis sechs Methoden |
| Kontrolle über die API | Eingeschränkt, alle Basismethoden sind öffentlich | Vollständig, nur was du freigibst |
| Typsicherheit der Elemente | Nur über eigene Prüfung in offsetSet() | Frei wählbar, auch getypte Methoden |
| Passt gut für | Schnelle Kapselung, Konfiguration, kleine Sammlungen | Fachliche Collections mit engem Vertrag |
Die Faustregel: solange du dem Aufrufer ruhig alle Array-Operationen erlauben darfst, spart das Erben Arbeit. Sobald die Klasse nur ganz bestimmte Objekte aufnehmen und nur ganz bestimmte Operationen zulassen soll, ist die eigene Implementierung ehrlicher. Ein PHP ArrayObject mit fünf überschriebenen Methoden, die alle nur Exceptions werfen, ist ein Zeichen dafür, dass die Vererbung der falsche Weg war.
Der folgende Ablauf fasst die Entscheidung und den typischen Arbeitsweg zusammen.
flowchart TD
A[Array vorhanden] --> B{Eigene Logik noetig?}
B -->|Nein| C[Normales Array behalten]
B -->|Ja| D[ArrayObject erweitern]
D --> E[offsetSet ueberschreiben]
E --> F[Zugriff bleibt wie Array]
F --> G{Array-Funktion noetig?}
G -->|Ja| H[getArrayCopy aufrufen]
G -->|Nein| I[Direkt weiterarbeiten]
Ob ein Wert überhaupt ein Objekt ist, lässt sich vorher prüfen. Der Schnelltest dafür steht im Tutorial zu PHP is_object().
Fazit
PHP ArrayObject ist kein besseres Array, sondern ein Werkzeug für einen bestimmten Fall: wenn Regeln an den Daten hängen sollen und der Klammerzugriff trotzdem bleiben darf. Für reine Datenhaltung bleibt das normale Array die richtige Wahl.
Drei Dinge reichen für den Einstieg. Erzeugen mit dem Konstruktor, zurück ins Array mit getArrayCopy(), und eigene Logik durch das Überschreiben von offsetSet(). Wer dazu die drei Stolperfallen kennt, spart sich die Fehlersuche, die sonst bei json_encode() oder beim ersten array_map() beginnt.