PHP -哈希对象的方式不同的对象具有相同的字段值有相同的哈希


PHP - hash objects in a way distint object with same fields values have same hash

我正在寻找一种方法来为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 == $bfunction 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。

这个问题实际上是在询问两件可能彼此不一致的事情。

  1. 一种以一致(和高性能)的方式对任何对象进行散列的方法。
  2. 使用这种哈希方法对对象进行有效的比较。

首先是散列。其他人出于性能原因建议使用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(),可能比使用您自己的递归函数(如上面)更有效),因为无论如何您不会在每个实例中都有太多的快捷方式来获得答案。