Objektvergleich in PHP: == vs. === und was wirklich verglichen wird
AI generated
8.4
PHP
Objektvergleich in PHP
== 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.

12 Min. Lesezeit Objektvergleich Value Objects

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.

11. FAQ: Objektvergleich == vs. ===

1Was vergleicht == bei zwei PHP-Objekten genau?
Ob beide Objekte Instanzen derselben Klasse sind und alle Properties, rekursiv verglichen, übereinstimmen.
2Was prüft === bei Objekten?
Ausschließlich, ob beide Operanden auf exakt dasselbe interne Objekt-Handle zeigen, also dieselbe Instanz im Speicher sind.
3Hat PHP eine Magic Method wie __equals?
Nein, PHP bietet keine Möglichkeit, das Verhalten von == oder === für eigene Klassen anzupassen.
4Warum sind zwei separat erzeugte Objekte mit == gleich?
Weil == nur Klasse und Property-Werte vergleicht, nicht die Speicheradresse. Bei identischen Werten liefert == daher true, auch bei zwei unabhängigen Instanzen.
5Was passiert bei == mit Objekten unterschiedlicher Klassen?
Sie sind immer ungleich, selbst wenn beide Klassen identische Properties mit identischen Werten besitzen.
6Wie sollte ich fachliche Gleichheit bei Value Objects prüfen?
Am besten mit einer expliziten equals()-Methode, die genau festlegt, welche Properties relevant sind, statt sich auf == zu verlassen.
7Wofür ist spl_object_id nützlich?
Um eine eindeutige, während der Objektlebensdauer stabile Ganzzahl-ID zu erzeugen, etwa wenn Objekte als Schlüssel in einer Datenstruktur dienen sollen.
8Kann spl_object_id-Werte wiederverwendet werden?
Ja, nachdem der Garbage Collector ein Objekt entfernt hat, kann dieselbe ID einem späteren, neuen Objekt zugewiesen werden.
9Was ist der Vorteil von SplObjectStorage gegenüber spl_object_id?
SplObjectStorage hält eine echte Referenz auf das Objekt als Schlüssel, wodurch das Wiederverwendungsproblem von IDs entfällt.
10Kann der rekursive == Vergleich bei Objekten zum Problem werden?
Ja, bei tief verschachtelten Strukturen oder zirkulären Referenzen kann der Vergleich teuer werden oder sogar in einer Endlosschleife enden.