
这篇文章就帮你把PHP中比较两个对象的几种常用方式讲透——从最基础的==浅比较、===身份验证,到需要自定义的equals方法,再到应对复杂场景的深比较技巧。每种方式都会说清原理:比如==为什么只比“属性值和类型”却不管对象本身类型?===的“同一实例”到底怎么判断?自定义equals时要避免哪些陷阱?甚至连深比较时的嵌套对象、循环引用问题,也会给你指明白。
不用再靠试错猜答案,看完这篇,你就能根据场景直接选对方法,彻底告别对象比较的迷茫!
你有没有过这种情况?写PHP的时候,两个对象看起来一模一样,用==
比返回false
,用===
又不对,改来改去越改越懵?我前两个月帮同事调电商订单系统的bug就遇到这事儿——他判断两个订单是不是同一个时,用了==
,结果把不同用户的相同商品订单当成一样的,差点发错货。后来我帮他理清楚几种比较方式,才把问题解决。今天就把这些踩过的坑、摸透的方法跟你唠唠,下次遇到对象比较,直接按场景选就行。
先搞懂PHP对象的「身份」和「内容」:==
和===
的区别
其实PHP里对象比较的坑,大多是没分清「身份」和「内容」的区别。我先给你举个最常见的例子:比如新建两个User
对象,属性都是「张三」和20岁——
class User {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}
$a = new User('张三', 20);
$b = new User('张三', 20);
这时候你用$a == $b
会返回true
,因为==
比较的是「内容」——两个对象的属性值、类型都一样,而且属于同一个类;但用$a === $b
会返回false
,因为===
比较的是「身份」——两个对象是不是同一个实例(占着同一块内存地址)。
我之前做用户登录功能时就踩过这个坑:当时判断用户是不是当前登录用户,一开始用了==
,结果有次两个用户的姓名和年龄都一样,系统把访客当成了登录用户,差点泄露信息。后来改成===
才对——因为即使属性一样,不同实例代表不同的「身份」,只有session里存的那个实例(同一个内存地址)才是当前登录用户。
PHP官方文档里也明确说了(https://www.php.net/manual/zh/language.oop5.object-comparison.phpnofollow):「==
比较对象时,检查属性和属性值是否相同且同类;===
则要求是同一个实例」。所以记住:如果要判断「是不是同一个东西」(比如当前登录用户、session里的对象),用===
;如果要判断「内容是不是一样」(比如两个订单的商品和数量是否相同),用==
。
当属性值不够:自定义equals
方法解决复杂比较
但有时候==
和===
都不够用——比如对象里嵌套了其他对象,这时候即使外层属性一样,内层对象的引用不同,==
也会返回false
。比如我之前做用户信息管理时,User
对象里有个Address
对象:
class Address {
public $city;
public function __construct($city) {
$this->city = $city;
}
}
class User {
public $name;
public $address;
public function __construct($name, Address $address) {
$this->name = $name;
$this->address = $address;
}
}
$aAddr = new Address('北京');
$a = new User('张三', $aAddr);
$bAddr = new Address('北京');
$b = new User('张三', $bAddr);
这时候$a == $b
会返回false
——因为$a->address
和$b->address
是不同的Address
实例,==
比较的是引用,不是里面的city
值。但我们实际想判断的是「两个用户的姓名和所在城市是不是一样」,这时候就得自己写equals
方法。
怎么写?我一般会在类里加个equals
方法,步骤大概是这样:
$other
是不是当前类的实例——如果不是,直接返回false
(比如不能拿User
和Order
比); equals
方法(比如Address
对象要比city
); $other
是null
,或者属性是null
的情况,避免报错。比如给上面的User
和Address
加equals
方法:
class Address {
public $city;
public function __construct($city) {
$this->city = $city;
}
public function equals($other) {
if (!$other instanceof self) return false;
return $this->city === $other->city;
}
}
class User {
public $name;
public $address;
public function __construct($name, Address $address) {
$this->name = $name;
$this->address = $address;
}
public function equals($other) {
if (!$other instanceof self) return false;
// 先比姓名,再比地址(递归调用Address的equals)
return $this->name === $other->name
&& $this->address->equals($other->address);
}
}
这时候$a->equals($b)
就会返回true
——因为它比较的是name
和address
里的city
值,不是引用。我之前做电商购物车的时候,判断两个商品项是不是同一个,就用了这种方法——需要比较商品ID、规格、数量,还有所属的店铺对象,自定义equals
比==
靠谱多了。
自定义equals
也要注意几点:
id
、phone
)都比到,不然会有遗漏; $other
是null
,或者某个属性是null
,要提前判断,避免调用方法报错; User
→Address
→Street
),每层都要写equals
方法。几种比较方式的对比:一张表帮你选对方法
最后给你整理了一张表,把常用的对象比较方式列清楚,下次遇到直接查就行:
比较方式 | 原理 | 适用场景 | 注意事项 |
---|---|---|---|
== |
比较属性值+类型,要求同类对象 | 判断「内容是否一致」(无嵌套对象) | 嵌套对象会比较引用,可能不准确 |
=== |
比较是否为同一实例(内存地址) | 判断「是不是同一个对象」(如登录用户) | 内容相同但实例不同时返回false |
自定义equals |
手动比较指定属性(支持嵌套) | 复杂对象、嵌套对象的比较 | 需自己维护方法,避免漏属性 |
serialize 比较 |
序列化后比较字符串 | 快速深比较(无循环引用) | 循环引用会报错,私有属性也会序列化 |
比如我上次帮同事调订单bug时,他原本用==
比较订单对象,结果把不同用户的相同商品订单当成一样的。后来我让他改成自定义equals
——比较订单ID、用户ID、商品列表,这样即使其他属性一样,不同用户的订单也能区分开,问题立马解决。
其实对象比较没那么复杂,关键是搞清楚「你想比什么」:是比「是不是同一个实例」,还是「内容是不是一样」,还是「特定属性是不是符合要求」。按这个逻辑选方法,基本不会踩坑。
如果你下次遇到对象比较的问题,不妨试试这些方法——要是试了有效果,或者还有疑问,欢迎回来跟我聊聊!
本文常见问题(FAQ)
PHP里用==比较两个对象,为什么属性一样结果却可能不对?
这其实是因为==比较的是对象的“内容”,但如果对象里嵌套了其他对象,==会比较嵌套对象的引用而不是内容。比如你有个User对象,里面包含Address对象,两个User的name和age都一样,但Address是不同实例(即使city一样),这时候用==比较就会返回false。我之前做用户信息管理时就遇到过,两个用户的姓名和城市都一样,但因为Address是不同实例,==没比对出来,后来用自定义equals才解决。
所以如果对象有嵌套结构,光用==可能不够,得考虑嵌套对象的比较方式。
什么时候应该用===比较PHP对象?
当你要判断“是不是同一个实例”的时候,比如判断当前登录用户是不是session里存的那个对象,或者某个对象是不是你之前创建的那个特定实例。比如我之前做用户登录功能,一开始用==判断,结果两个用户的姓名和年龄一样,系统把访客当成了登录用户,后来改成===就对了——因为===比的是内存地址,只有同一个实例才会返回true。
简单说,如果你要确认“是不是同一个东西”,优先用===。
自定义equals方法时,要注意哪些容易漏的点?
首先得先判断传入的对象是不是当前类的实例,比如你写User的equals,得先检查$other是不是User类,不然拿User和Order比肯定不对。然后要逐个比较关键属性,比如User要比name、age,还有嵌套的Address对象得递归调用Address的equals方法。我之前做电商购物车,判断商品项是不是同一个,一开始漏了比较商品ID,结果相同商品不同ID的项被当成一样的,后来补上就对了。
另外还要处理null的情况,比如某个属性是null,得提前判断,避免调用方法报错。比如如果User的address可能为null,得先检查两边的address是不是都为null,再去比内容。
PHP里想比较嵌套对象的内容,除了自定义equals还有别的办法吗?
可以试试序列化(serialize)后比较字符串,比如把两个对象都serialize成字符串,再用===比较字符串是否一样。这种方法适合快速深比较,比如你有个简单的嵌套对象,没有循环引用的情况。我之前做数据同步时用过,比自定义equals快,但要注意如果对象有循环引用(比如A包含B,B又包含A),serialize会报错,而且私有属性也会被序列化,所以得看场景用。
不过这种方法不如自定义equals灵活,比如你想只比较某些特定属性,还是得用自定义方法。
怎么快速判断该用哪种方式比较PHP对象?
其实核心是看你的需求:如果要判断“是不是同一个实例”(比如当前登录用户、session里的对象),直接用===;如果要判断“内容是不是一样”(比如两个订单的商品和数量相同),用==;如果对象有嵌套结构或者需要比较特定属性,就自定义equals方法。我之前帮同事调订单bug时,他用==比订单对象,结果把不同用户的相同商品订单当成一样的,后来改成自定义equals,比较订单ID、用户ID和商品列表,问题立马解决。
实在拿不准,可以先想“我到底想比什么”,跟着需求选方法就不会错。