Skip to content

Instantly share code, notes, and snippets.

@vudaltsov
Created April 24, 2026 13:02
Show Gist options
  • Select an option

  • Save vudaltsov/c3ecf19b94081ea022f32a9767f802de to your computer and use it in GitHub Desktop.

Select an option

Save vudaltsov/c3ecf19b94081ea022f32a9767f802de to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
final readonly class Comparator
{
public static function compare(mixed $a, mixed $b): bool
{
/** @var \WeakMap<object, \WeakMap<object, bool>> */
$comparedObjects = new \WeakMap();
return self::doCompare($a, $b, $comparedObjects);
}
/**
* @param \WeakMap<object, \WeakMap<object, bool>> $comparedObjects
*/
private static function doCompare(mixed $a, mixed $b, \WeakMap $comparedObjects): bool
{
if ($a === $b) {
return true;
}
if ($a === null || \is_scalar($a) || \is_resource($a)) {
return false;
}
if (\is_array($a)) {
if (!\is_array($b)) {
return false;
}
if (\count($a) !== \count($b)) {
return false;
}
if (array_is_list($a) && !array_is_list($b)) {
return false;
}
foreach ($a as $key => $value) {
if (!\array_key_exists($key, $b)) {
return false;
}
if (!self::doCompare($value, $b[$key], $comparedObjects)) {
return false;
}
}
return true;
}
\assert(\is_object($a));
if (!\is_object($b)) {
return false;
}
// todo handle anonymous?
if ($a::class !== $b::class) {
return false;
}
$comparedToA = $comparedObjects[$a] ?? null;
if ($comparedToA !== null) {
if (isset($comparedToA[$b])) {
return $comparedToA[$b];
}
} else {
/** @var \WeakMap<object, bool> */
$comparedToA = new \WeakMap();
$comparedObjects[$a] = $comparedToA;
}
$comparedToA[$b] = true;
return $comparedToA[$b] = self::doCompare(
a: get_mangled_object_vars($a),
b: get_mangled_object_vars($b),
comparedObjects: $comparedObjects,
);
}
private function __construct() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment