== vergleicht Werte, === vergleicht Identität: was das für den Code bedeutet
Bei Skalaren ist der Unterschied zwischen == und === schon anspruchsvoll genug, bei Objekten kommt eine ganz eigene Dimension hinzu, die viele Entwickler unterschätzen. == vergleicht bei Objekten Klasse und sämtliche Properties rekursiv, während === ausschließlich prüft, ob es sich um dieselbe Instanz im Speicher handelt. PHP kennt dabei keine Magic Method wie __equals, mit der sich dieses Verhalten anpassen liesse. Dieser Artikel erklärt beide Operatoren präzise und zeigt, wie sich eigene, nachvollziehbare Vergleichslogik für Value Objects umsetzen lässt.
Inhaltsverzeichnis
- 1. Zwei grundverschiedene Vergleichsarten bei Objekten
- 2. Wie == bei Objekten wirklich funktioniert
- 3. Wie === echte Identität prüft
- 4. Rekursion bei verschachtelten Objekten und ihre Fallstricke
- 5. Warum PHP kein __equals hat
- 6. Eigene Vergleichslogik ohne Operator-Overloading
- 7. Praktisches Beispiel: Value-Object-Vergleich
- 8. Identität nachweisen mit spl_object_id
- 9. Faustregeln für die tagliche Praxis
- 10. Zusammenfassung
- 11. FAQ
1. Zwei grundverschiedene Vergleichsarten bei Objekten
Bei Objekten unterscheiden sich == und === in PHP fundamentaler als bei jedem anderen Typ. Während beide Operatoren bei Skalaren lediglich verschieden streng mit Typkonvertierung umgehen, beantworten sie bei Objekten grundsätzlich verschiedene Fragen: == fragt, ob zwei Objekte inhaltlich gleichwertig sind, === fragt, ob es sich um exakt dieselbe Instanz handelt. Wer diesen Unterschied nicht kennt, produziert Vergleiche, die sich fachlich vollkommen anders verhalten als beabsichtigt.
Diese Unterscheidung ist keine PHP-Eigenheit ohne Vorbild, sie spiegelt ein bekanntes Konzept aus anderen Sprachen wider: inhaltliche Gleichheit gegenüber Referenzidentität. Der entscheidende Unterschied zu Sprachen wie Java oder C Sharp ist jedoch, dass PHP für die inhaltliche Gleichheit keine überschreibbare Methode anbietet, das Verhalten von == bei Objekten ist fest in der Sprache verdrahtet und lässt sich nicht individüll anpassen.
2. Wie == bei Objekten wirklich funktioniert
Der == Operator liefert bei zwei Objekten genau dann true, wenn beide Objekte Instanzen derselben Klasse sind und alle ihre Properties, verglichen mit ==, ebenfalls gleich sind. Der Vergleich erfolgt rekursiv: Enthält eine Property selbst ein Objekt, wird dieses wiederum nach denselben Regeln verglichen. Zwei Objekte unterschiedlicher Klassen sind mit == niemals gleich, selbst wenn sie identische Properties mit identischen Werten besitzen.
Diese Regel hat eine wichtige Konsequenz: Zwei separat erzeugte Instanzen einer Klasse mit denselben Konstruktorwerten sind mit == gleich, obwohl es sich um zwei vollkommen unabhängige Objekte im Speicher handelt. Für einfache, unveränderliche Datencontainer ist das oft genau das gewünschte Verhalten, bei komplexeren Objekten mit Ressourcen-Handles, Zeitstempeln oder zirkulären Referenzen kann der rekursive Vergleich dagegen unerwartet teuer oder sogar in Randfällen fehlerhaft werden.
<?php
declare(strict_types=1);
final class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {
}
}
$a = new Point(1.0, 2.0);
$b = new Point(1.0, 2.0);
var_dump($a == $b); // true, gleiche Klasse und gleiche Property-Werte
var_dump($a === $b); // false, zwei unterschiedliche Instanzen
3. Wie === echte Identität prüft
Der === Operator prüft bei Objekten, ob beide Operanden auf exakt dasselbe interne Objekt-Handle zeigen. Intern verwaltet PHP jedes Objekt über eine eindeutige Handle-Referenz, unabhängig von einer möglicherweise noch vorhandenen Variable, über die darauf zugegriffen wird. Weist man ein Objekt einer neuen Variable per einfacher Zuweisung zu, wird keine Kopie erzeugt, sondern lediglich eine weitere Referenz auf dasselbe Handle, weshalb === in diesem Fall true liefert.
Genau dieses Verhalten macht === zum richtigen Werkzeug, wenn tatsächlich Identität und nicht Gleichwertigkeit gefragt ist, etwa beim Prüfen, ob ein Event-Listener bereits registriert wurde, oder beim Entfernen eines spezifischen Objekts aus einer Sammlung. In beiden Fällen ist entscheidend, ob es sich um genau dasselbe Objekt handelt, nicht ob ein inhaltlich gleichwertiges Objekt existiert.
4. Rekursion bei verschachtelten Objekten und ihre Fallstricke
Der rekursive Charakter von == wird bei tief verschachtelten Objektstrukturen schnell zur Falle. Enthält ein Objekt eine Property mit einer Sammlung weiterer Objekte, etwa eine Bestellung mit mehreren Positionen, vergleicht == jede einzelne Position rekursiv mit der entsprechenden Position des anderen Objekts. Bei großen Objektgraphen kann das schnell teuer werden und ist in vielen Fällen auch fachlich nicht das gewünschte Verhalten.
Noch problematischer wird es bei zirkulären Referenzen, wenn also zwei Objekte gegenseitig aufeinander verweisen. Ein solcher Vergleich mit == kann in einer Endlosschleife enden oder zumindest zu einem schwer nachvollziehbaren Fehlerbild führen. In der Praxis sollte == bei Objekten deshalb nur bewusst und bei überschaubaren, flachen Strukturen eingesetzt werden, nicht als generischer Standard für beliebige Objektvergleiche.
5. Warum PHP kein __equals hat
Anders als etwa Java, wo jede Klasse implizit von Object erbt und equals überschreiben kann, bietet PHP keine vergleichbare Magic Method. Es gibt keine Möglichkeit, das Verhalten von == für eine eigene Klasse gezielt zu definieren. Die einzige Ausnahme betrifft SPL-Klassen, die eine ArrayAccess- oder Comparable-ähnliche Schnittstelle implementieren, aber selbst diese ändern nichts am grundlegenden Verhalten von == oder ===.
Diese Sprachentscheidung ist bewusst: Sie verhindert versteckte, überraschende Seiteneffekte beim Vergleichsoperator, wie sie in anderen Sprachen gelegentlich vorkommen, wenn eine schlecht implementierte equals-Methode inkonsistent zu hashCode ist. Der Preis dafür ist, dass jede Klasse mit fachlich relevanter Gleichheitslogik diese explizit als eigene Methode anbieten muss, anstatt sich auf die Operatoren zu verlassen.
6. Eigene Vergleichslogik ohne Operator-Overloading
Die etablierte Konvention in der PHP-Community ist eine explizite equals-Methode, die genau definiert, welche Properties für die Gleichheit relevant sind und wie sie verglichen werden. Damit wird die Vergleichslogik selbstdokumentierend und lesbar, statt sich auf das implizite, rekursive Verhalten von == zu verlassen, das bei komplexeren Klassen schnell unvorhersehbar wird.
Ein zusätzlicher Vorteil einer eigenen equals-Methode ist die Möglichkeit, gezielt nur fachlich relevante Properties einzubeziehen. Ein Objekt kann interne, technische Properties besitzen, etwa einen Cache-Wert oder einen Erstellungszeitstempel, die für die fachliche Gleichheit irrelevant sind, aber den == Vergleich trotzdem verfälschen würden, weil dieser ausnahmslos alle Properties einbezieht.
7. Praktisches Beispiel: Value-Object-Vergleich
Am Beispiel eines Geldbetrags lässt sich der Unterschied gut zeigen. Ein Money-Value-Object soll zwei Beträge genau dann als gleich betrachten, wenn Betrag und Währung übereinstimmen, unabhängig davon, ob es sich um dieselbe Instanz handelt. Eine explizite equals-Methode macht diese Regel klar und testbar, während == zwar im einfachen Fall dasselbe Ergebnis liefert, bei zusätzlichen, fachlich irrelevanten Properties aber leicht falsche Ergebnisse produzieren kann.
Diese Methode kann anschließend in Collections, Sortierungen oder Duplikatsprüfungen genutzt werden, ohne dass sich jemand auf das implizite Verhalten von == verlassen muss. Gerade in grösseren Codebasen mit vielen Value Objects zahlt sich diese Konsistenz aus, weil das Vergleichsverhalten an einer einzigen, klar erkennbaren Stelle definiert ist und nicht implizit aus der Property-Struktur der Klasse abgeleitet werden muss.
<?php
declare(strict_types=1);
final class Money
{
public function __construct(
private readonly int $amountInCents,
private readonly string $currency,
private readonly DateTimeImmutable $createdAt,
) {
}
/**
* Vergleicht ausschließlich fachlich relevante Werte, ignoriert createdAt.
*/
public function equals(self $other): bool
{
return $this->amountInCents === $other->amountInCents
&& $this->currency === $other->currency;
}
}
$a = new Money(1999, 'EUR', new DateTimeImmutable('2026-01-01'));
$b = new Money(1999, 'EUR', new DateTimeImmutable('2026-08-08'));
var_dump($a->equals($b)); // true, fachlich gleich
var_dump($a == $b); // false, createdAt unterscheidet sich
8. Identität nachweisen mit spl_object_id
Manchmal muss Objektidentität nicht nur geprüft, sondern auch als eindeutiger Schlüssel verwendet werden, etwa in einer Map, deren Schlüssel Objektinstanzen sind. Da Arrays in PHP keine Objekte als Schlüssel akzeptieren, hilft spl_object_id, eine eindeutige, während der Lebensdauer des Objekts stabile Ganzzahl zu erzeugen. Vor PHP 7.2 war spl_object_hash die gängige Alternative, spl_object_id ist heute performanter und vorzuziehen.
Wichtig ist dabei eine Einschränkung: Die von spl_object_id zurückgegebene ID kann nach der Zerstörung eines Objekts durch den Garbage Collector für ein neues Objekt wiederverwendet werden. Solange eine Referenz auf das ursprüngliche Objekt aktiv gehalten wird, bleibt die ID eindeutig, ohne diese Garantie sollte man sich nicht allein auf die ID verlassen, sondern zusätzlich das Objekt selbst als Schlüssel in einer SplObjectStorage verwenden, die genau für diesen Zweck entworfen wurde.
<?php
declare(strict_types=1);
$storage = new SplObjectStorage();
$order = new stdClass();
$storage[$order] = 'verarbeitet';
var_dump($storage->contains($order)); // true
var_dump(spl_object_id($order)); // z. B. int(1), stabil solange $order lebt
9. Faustregeln für die tagliche Praxis
Als Grundregel gilt: === für Identitätsprüfungen, etwa beim Entfernen eines spezifischen Objekts aus einer Liste oder beim Prüfen, ob ein Callback bereits registriert wurde. Eine eigene equals-Methode für fachliche Gleichheit, insbesondere bei Value Objects, bei denen zwei unabhängige Instanzen mit denselben Werten als gleichwertig gelten sollen. == bei Objekten sollte dagegen bewusst vermieden werden, außer bei einfachen, flachen Datencontainern ohne irrelevante Properties.
Diese klare Trennung erspart eine ganze Klasse subtiler Bugs, die typischerweise erst bei zusätzlichen Properties oder tiefer Verschachtelung sichtbar werden. Wer von Anfang an konsequent zwischen Identität, fachlicher Gleichheit und der impliziten, rekursiven == Semantik unterscheidet, vermeidet Überraschungen, die sich sonst erst durch fehlschlagende Tests in späteren Projektphasen zeigen.
| Operator/Technik | Was wird verglichen | Rekursiv? | Typischer Einsatz |
|---|---|---|---|
| == | Klasse plus alle Properties | Ja | Einfache, flache Datencontainer |
| === | Identisches Objekt-Handle | Nein | Identitätsprüfung, Duplikat-Entfernung |
| Eigene equals()-Methode | Frei definierbare Properties | Nach Bedarf | Value Objects mit fachlicher Gleichheit |
| spl_object_id() | Eindeutige Instanz-ID | Nein | Objekte als Map-Schlüssel-Ersatz |
| SplObjectStorage | Objektidentität als echter Schlüssel | Nein | Objekt-zu-Wert-Zuordnungen |
Mironsoft
PHP-Modernisierung, Code-Qualität und Legacy-Refactoring
Gewachsener PHP-Code, der niemand mehr gern anfasst?
Wir modernisieren PHP-Codebasen auf aktuelle Sprachstandards, führen statische Analyse und Coding Standards ein und refactorn Legacy-Code Schritt für Schritt, ohne den laufenden Betrieb zu gefährden.
Legacy-Refactoring
Gewachsenen PHP-Code strukturiert und risikoarm modernisieren.
Code-Qualität etablieren
PHPStan, Coding Standards und CI-Checks nachhaltig im Team verankern.
Versions-Upgrade
PHP-Major-Version-Upgrades sicher planen und ohne Ausfallzeit umsetzen.
10. Zusammenfassung
Objektvergleich == vs. ===
== bei Objekten
Vergleicht Klasse und alle Properties rekursiv, keine Identitätsprüfung.
=== bei Objekten
Prüft ausschließlich, ob es sich um dieselbe Instanz im Speicher handelt.
Kein __equals
PHP bietet keine Magic Method zur Anpassung des Vergleichsverhaltens.
Eigene Logik
equals()-Methoden machen fachliche Gleichheit explizit und testbar.