PHP ofrece una manera sencilla de comparar objectos usando los operadores de comparación (==) e identidad (===), si usamos el operador de comparación entonces los objetos son comparados en forma simple o sea 2 objectos son iguales si son instancias de la misma clase y tienen los mismos atributos (propiedades) y valores, si usamos el operador de identidad entonces 2 objetos son iguales solo si hacen referencia (o apuntan) a la misma instancia de la misma clase.
En ocaciones no basta con saber si 2 objetos son iguales o differentes sino que en el caso de que sean differentes necesitamos saber las differencias, podemos lograrlo creando una función que reciba como parámetros los objetos a comparar, obtenga las propiedades de los objetos usando Reflection itere por las propiedades de los objetos y compare los valores de las propiedades y luego retorne un arreglo con las differencias.
Pongamos lo comentado anteriormente en la siguiente clase:
<?php
namespace Comparator;
use ReflectionObject;
use InvalidArgumentException;
class ObjectComparator
{
/**
* Compare 2 objects
*
* @param $o1
* @param $o2
* @param $strict Compare in simple (==) or in strict way (===)
* @return true objects are equals, false objects are differents
*/
public static function equal($o1, $o2, $strict = false)
{
return $strict ? $o1 === $o2 : $o1 == $o2;
}
/**
* Find the differences between 2 objects using Reflection.
*
* @param $o1
* @param $o2
* @return array Properties that have changed
* @throws InvalidArgumentException
*/
public static function diff($o1, $o2)
{
if (!is_object($o1) || !is_object($o2)) {
throw new InvalidArgumentException("Parameters should be of object type!");
}
$diff = [];
if (get_class($o1) == get_class($o2)) {
$o1Properties = (new ReflectionObject($o1))->getProperties();
$o2Reflected = new ReflectionObject($o2);
foreach ($o1Properties as $o1Property) {
$o2Property = $o2Reflected->getProperty($o1Property->getName());
// Mark private properties as accessible only for reflected class
$o1Property->setAccessible(true);
$o2Property->setAccessible(true);
if (($oldValue = $o1Property->getValue($o1)) != ($newValue = $o2Property->getValue($o2))) {
$diff[$o1Property->getName()] = [
'old_value' => $oldValue,
'new_value' => $newValue
];
}
}
}
return $diff;
}
}
Realizando algunas pruebas
<?php
include __DIR__ . '/ObjectComparator.php';
use Comparator\ObjectComparator as Comparator;
class Person
{
public $name = '';
private $age = 0;
public function setAge($age)
{
$this->age = $age;
}
}
function print_ln() {
return php_sapi_name() == 'cli' ? PHP_EOL : nl2br(PHP_EOL);
}
$p1 = new Person();
$p2 = new Person();
echo '1. p1 == p2 => ', Comparator::equal($p1, $p2) ? 'Yes' : 'No', print_ln(); // Print Yes
$p1->name = 'Juan';
echo '2. p1 == p2 => ', Comparator::equal($p1, $p2) ? 'Yes' : 'No', print_ln(); // Print No
$p2->name = 'Juan';
echo '3. p1 == p2 => ', Comparator::equal($p1, $p2) ? 'Yes' : 'No', print_ln(); // Print Yes
$p2->setAge(20);
echo '4. p1 == p2 => ', Comparator::equal($p1, $p2) ? 'Yes' : 'No', print_ln(); // Print No
$p1->setAge(20);
echo '5. p1 == p2 => ', Comparator::equal($p1, $p2) ? 'Yes' : 'No', print_ln(); // Print Yes
$p2 = clone $p1;
echo '6. p1 == p2 => ', Comparator::equal($p1, $p2, true) ? 'Yes' : 'No', print_ln(); // Print No
$p2 = $p1;
echo '7. p1 == p2 => ', Comparator::equal($p1, $p2, true) ? 'Yes' : 'No', print_ln(); // Print Yes
$p3 = new Person();
$p3->name = 'Pepe';
$p3->setAge(50);
echo '8. Get the differences for p1, p3', print_ln();
foreach (Comparator::diff($p1, $p3) as $property => $diff) {
echo $property, ' => ', 'old value: ', $diff['old_value'], ', new value: ', $diff['new_value'], print_ln();
}
// Print
// Get the differences for p1, p3
// name => old value: Juan, new value: Pepe
// age => old value: 20, new value: 50