我正在寻找一种方法来为PHP对象生成某种哈希值(通用解决方案,与所有分类,内置和自定义,如果可能的话)。
SplObjectStorage::getHash不是我要找的,因为它会为给定类的每个实例生成不同的哈希。为了描述这个问题,让我们考虑一个简单的类:
class A() {
public $field; //public only for simplicity
}
和该类的两个实例:
$a = new A(); $a->field = 'b';
$b = new A(); $b->field = 'b';
我尝试过的每个内置函数都会为这些对象返回不同的哈希值,而我想要一些具有属性f($a) == f($b) => $a == $b
的function f($x)
。
我知道我可以写一个函数递归地遍历所有对象的属性,直到我找到一个可以被转换为字符串的属性,用奇特的方式连接这些字符串和哈希,但是这种解决方案的性能会很糟糕。
是否有有效的方法来做到这一点?
假设我理解正确,您可以序列化对象,然后md5序列化对象。由于如果所有属性都相同,那么序列化将创建相同的字符串,因此每次都应该得到相同的哈希值。除非你的对象有某种时间戳属性。例子:
class A {
public $field;
}
$a = new A;
$b = new A;
$a->field = 'test';
$b->field = 'test';
echo md5(serialize($a)) . "'n";
echo md5(serialize($b)) . "'n";
输出:0a0a68371e44a55cfdeabb04e61b70f7
0a0a68371e44a55cfdeabb04e61b70f7
的结果就不一样了因为php内存中的对象是用编号的id来存储每个实例的
object(A)#1 (1) {...
object(A)#2 (1) {...
你似乎在谈论一个值对象。在这种模式中,每个这样的对象不是根据对象标识进行比较,而是根据组成对象的全部或部分属性的内容进行比较。
我在一个项目中使用了它们中的一些:
public function equals(EmailAddress $address)
{
return strtolower($this->address) === strtolower((string) $address);
}
更复杂的对象可以简单地向比较函数中添加更多项。
return ($this->one === $address->getOne() &&
$this->two === $address->getTwo());
因为这样的条件(都用'&&'连接)一旦有任何项不匹配,就会简化为false。
这个问题实际上是在询问两件可能彼此不一致的事情。
- 一种以一致(和高性能)的方式对任何对象进行散列的方法。
- 使用这种哈希方法对对象进行有效的比较。
首先是散列。其他人出于性能原因建议使用serialize()
,但它确实引入了一个限制:PHP对象可以在外部添加字段,也可以在类中声明字段。因此有可能(虽然不太可能,而且肯定表明有问题的编码实践),您的对象可能具有相同的字段,但以不同的顺序声明。这将产生不同的序列化,根据您的问题的措辞,这是您不希望看到的。
要防止这种情况,需要将对象强制转换为数组并对其成员排序。如果任何字段本身是对象或数组,可能有相同的问题,您应该递归地工作。
function sortObject($obj) {
$arr = (array) $obj;
ksort($arr);
foreach($arr as $k => $v) {
if(is_array($v) || is_object($v)) {
$arr[$k] = sortObject($v);
}
}
return $arr;
}
这提供了可以序列化和散列的对象的一致表示。或者,您可以在函数本身中构建哈希:
function hashObject($obj) {
$arr = (array) $obj;
ksort($arr);
$hash = '';
foreach($arr as $k => $v) {
if(is_array($v)) {
$hash .= '['.hashObject($v).']';
elseif(is_object($v)) {
$hash .= '{'.hashObject($v).'}';
} else {
$hash .= var_export($v);
}
}
return $hash;
}
//The brackets are added to preserve structure.
json_encode()
可以用来代替var_export()
,但我选择后者来保证PHP值的忠实表示(JSON中可能发生冲突,我不知道),我怀疑它可能会执行得更好。
那么,如果一个对象包含循环引用,例如,它有一个对象或数组字段包含一个值,这是对它的引用?serialize()
可以处理;以上函数不能。
:比较。最好的方法是由决定答案最有可能是。也就是说:
如果您期望所比较的大多数对象对是不同的,那么逐个比较它们会更有效,这样您就可以尽快确定差异。
function matchObj($a,$b) {
if(gettype($a) !== gettype($b)) {
return false;
}
$arrA = (array) $a;
$arrB = (array) $b;
if(count($arrA) <> count($arrB)) {
return false;
}
ksort($arrA);
ksort($arrB);
foreach($arrA as $k => $v) {
if($k !== key($arrB) || gettype($v) !== gettype($arrB[$k]) {
return false;
}
if(is_array($v) || is_object($v)) {
matchObj($v,$arrB[$k]) || return false;
} elseif($v !== $arrB[$k]) {
return false;
}
next($arrB);
}
return true;
}
如果您希望大多数对匹配,那么您可以将每个对象全部散列并比较它们(如果使用serialize()
,可能比使用您自己的递归函数(如上面)更有效),因为无论如何您不会在每个实例中都有太多的快捷方式来获得答案。