Comparing Objects
How PHP Compares Objects
In simple terms: "Are these two objects the same?" can mean two different things. Are they the same object (one thing referred to twice)? Or are they equal — two separate objects with the same contents, like two ₹500 notes? PHP has a separate operator for each question.
Technically:
| Operator | Name | True When |
|---|---|---|
=== | Identity | Both operands refer to the same instance |
== | Equality | Both are instances of the same class and all properties are equal (compared with ==) |
!== / != | Negations | The opposite of the above |
Identity vs Equality
<?php
final class Money
{
public function __construct(private int $paise, private string $currency) {}
}
$a = new Money(50000, 'INR');
$b = new Money(50000, 'INR');
$c = $a;
$d = new Money(50000, 'USD');
var_dump($a == $b); // equal contents
var_dump($a === $b); // different instances
var_dump($a === $c); // same instance
var_dump($a == $d); // different currency
Output
bool(true)
bool(false)
bool(true)
bool(false)
How It Works
$aand$bare separate objects with identical property values: equal (==) but not identical (===).$c = $acopies the object handle, so$aand$care the same instance: identical.$aand$ddiffer in one property, so they are not equal.==compares all properties, including private ones.
Different Classes Are Never Equal
<?php
class Point
{
public function __construct(public int $x, public int $y) {}
}
class Vector
{
public function __construct(public int $x, public int $y) {}
}
var_dump(new Point(1, 2) == new Point(1, 2));
var_dump(new Point(1, 2) == new Vector(1, 2));
Output
bool(true)
bool(false)
Even with identical properties, objects of different classes are not equal with ==.
Object IDs
Every object alive at a given moment has a unique integer ID:
<?php
$first = new stdClass();
$second = $first;
$third = new stdClass();
var_dump(spl_object_id($first) === spl_object_id($second));
var_dump(spl_object_id($first) === spl_object_id($third));
Output
bool(true)
bool(false)
spl_object_id() (and the string version spl_object_hash()) identify instances — useful as array keys when tracking objects. IDs can be reused after an object is destroyed, so they are only unique among objects that exist at the same time. SplObjectStorage and WeakMap provide ready-made object-keyed collections.
The Limits of ==
== is convenient but often not what a domain means by "equal":
| Problem | Example |
|---|---|
| Loose comparison of properties | Untyped properties '500' and 500 count as equal |
| All properties count | Two User objects with the same ID but different "last login" are not equal |
| Business rules are ignored | Email addresses differing only in letter case are not equal |
| Deep or circular structures | Comparison recurses through nested objects; circular references cause a fatal "Nesting level too deep" error |
A Dedicated equals() Method
For value objects, define equality explicitly:
<?php
final class EmailAddress
{
private string $value;
public function __construct(string $email)
{
$this->value = trim($email);
}
public function equals(EmailAddress $other): bool
{
return strtolower($this->value) === strtolower($other->value);
}
}
$one = new EmailAddress('Asha@Example.com');
$two = new EmailAddress('asha@example.com');
var_dump($one == $two);
var_dump($one->equals($two));
Output
bool(false)
bool(true)
== compares the stored strings exactly, but for email addresses the case does not matter. equals() encodes the real rule, documents it, and uses strict comparisons internally.
Entities vs Value Objects
| Kind | Equal When | Example |
|---|---|---|
| Value object | All relevant values are equal | Money(500, 'INR'), EmailAddress, DateRange |
| Entity | The identity (usually an ID) is the same | Customer #7 remains the same customer after changing the address |
public function sameAs(Customer $other): bool
{
return $this->id === $other->id;
}
Comparing Dates
DateTimeImmutable and DateTime objects are an exception: comparison operators compare the moment in time, regardless of time zone:
<?php
$india = new DateTimeImmutable('2026-10-15 10:00', new DateTimeZone('Asia/Kolkata'));
$utc = new DateTimeImmutable('2026-10-15 04:30', new DateTimeZone('UTC'));
$later = new DateTimeImmutable('2026-10-15 11:00', new DateTimeZone('Asia/Kolkata'));
var_dump($india == $utc);
var_dump($india < $later);
Output
bool(true)
bool(true)
10:00 in India and 04:30 UTC are the same moment, so the objects are equal even though their time zones differ. For other classes, avoid < and >: PHP compares objects property by property, which rarely has a meaningful result. Implement methods such as isGreaterThan() instead.
Searching Arrays of Objects
in_array() compares loosely by default, which uses == for objects:
<?php
final class Money
{
public function __construct(public readonly int $paise) {}
public function equals(Money $other): bool
{
return $this->paise === $other->paise;
}
}
$payments = [new Money(10000), new Money(25000)];
$target = new Money(25000);
var_dump(in_array($target, $payments)); // loose: equal contents
var_dump(in_array($target, $payments, true)); // strict: same instance?
var_dump(array_any($payments, fn(Money $m): bool => $m->equals($target))); // PHP 8.4+
Output
bool(true)
bool(false)
bool(true)
- Loose
in_array()finds an equal object. - Strict
in_array()looks for the same instance, which$targetis not. array_any()withequals()expresses the intended rule explicitly (in older PHP versions, use a loop orarray_filter()).
Practical Example: Merging Duplicate Cart Lines
<?php
final class CartLine
{
public function __construct(
public readonly string $sku,
public readonly string $size,
public int $quantity,
) {}
public function isSameProductAs(CartLine $other): bool
{
return $this->sku === $other->sku && $this->size === $other->size;
}
}
function addToCart(array $lines, CartLine $new): array
{
foreach ($lines as $line) {
if ($line->isSameProductAs($new)) {
$line->quantity += $new->quantity;
return $lines;
}
}
$lines[] = $new;
return $lines;
}
$cart = [];
$cart = addToCart($cart, new CartLine('TSHIRT-01', 'M', 1));
$cart = addToCart($cart, new CartLine('TSHIRT-01', 'L', 1));
$cart = addToCart($cart, new CartLine('TSHIRT-01', 'M', 2));
foreach ($cart as $line) {
echo "{$line->sku} ({$line->size}) × {$line->quantity}\n";
}
Output
TSHIRT-01 (M) × 3
TSHIRT-01 (L) × 1
== would treat the first and third lines as different (their quantities differ), and === would never match a newly created line. The domain rule — "same SKU and same size" — is expressed in isSameProductAs().
Common Mistakes
Using === to Compare Values
Effect: Two equal value objects are reported as different.
Fix: Use == for simple cases, or better, an equals() method.
Using == for Domain Equality
Effect: Irrelevant properties (timestamps, caches) make equal things unequal, or loose comparison makes different things equal.
Fix: Implement explicit equality methods.
Comparing Objects With < and >
Effect: Meaningless results for most classes.
Fix: Write comparison methods (or a comparator for usort()).
Comparing Objects With Circular References Using ==
Effect: Fatal "Nesting level too deep" error.
Fix: Compare identities or IDs.
Best Practices
- Use
===to check whether two variables refer to the same instance. - Give value objects an
equals()method encoding the real equality rule. - Compare entities by identifier.
- Use strict searching or callbacks with explicit comparison methods for arrays of objects.
- Rely on built-in comparison operators only for types that define them meaningfully, such as dates.
Official reference: https://www.php.net/manual/en/language.oop5.object-comparison.php