13

Let's say I have an object - a User object in this case - and I'd like to be able to track changes to with a separate class. The User object should not have to change it's behavior in any way for this to happen.

Therefore, my separate class creates a "clean" copy of it, stores it somewhere locally, and then later can compare the User object to the original version to see if anything changed during its lifespan.

Is there a function, a pattern, or anything that can quickly compare the two versions of the User object?

Option 1 Maybe I could serialize each version, and directly compare, or hash them and compare?

Option 2 Maybe I should simply create a ReflectionClass, run through each of the properties of the class and see if the two versions have the same property values?

Option 3 Maybe there is a simple native function like objects_are_equal($object1,$object2);?

What's the fastest way to do this?

johnnietheblack
  • 12,290
  • 27
  • 86
  • 127
  • Well, I know I could serialize both objects, and compare hashes or something....it's not whether I CAN, it's more about how do I do it quickly? I'll adjsut the question to reflect that spirit... – johnnietheblack Mar 26 '12 at 05:18

4 Answers4

14

The only way to access the all (public, protected and private) properties of an object without modifying it is through reflection. You could, if you wanted to, clone the object before it's modified and then compare them by using the reflection functions to loop through the different properties of one of the two objects and comparing values. That would effectively tell you what properties have changed.

If all you care to see is whether or not the objects have changed, you can use the == or === operators. Have a look at the comparing objects section of PHP's documentation.

That said, wouldn't just be easier to keep track of what you've changed as you change them?

Francois Deschenes
  • 23,823
  • 3
  • 61
  • 58
  • Thanks for the answer :) I could, but I'm researching Doctrine2, and it seems as though they create a giant graph of objects, and upon calling flush(), they check to see what's changed, and update everything in a single swoop....which is what I'm trying to figure out. – johnnietheblack Mar 26 '12 at 05:22
  • @johnnietheblack - You could use Doctrine events but that would require that your objects be modified slightly. If you're interested in Doctrine events, have a look at http://stackoverflow.com/questions/2059399/doctrine-listener-run-action-only-if-a-field-has-changed. It's for Doctrine 1.2 but the principle is the same in the newer version. – Francois Deschenes Mar 26 '12 at 05:25
  • Thanks again - I'd like to not have to adjust the User class in any way, so that I could remove the "listener" and User wouldn't know the difference. Also, I'd like to learn how to do this w/o Doctrine...partly for fun, and partly if I ever need to know how it's done otherwise – johnnietheblack Mar 26 '12 at 05:28
  • @johnnietheblack - Well, don't serialize, it's slow and memory intensive, especially on larger objects. The reflection class is a great alternative but only if you need to compare all of the properties one by one. If you're only trying to see if an object has changed, use `==`. – Francois Deschenes Mar 26 '12 at 05:30
  • 1
    Sweet thanks :) So, if I created a clone of User, and changed the email address of one of them, then $user != $userClone, yes? – johnnietheblack Mar 26 '12 at 05:32
  • 1
    @johnnietheblack - In theory, that's right. I've never tried it with Doctrine but with regular classes (non managed by Doctrine) that would be the case indeed. – Francois Deschenes Mar 26 '12 at 05:35
  • FYI - Just found how Doctrine does it in their code....they use the ReflectionClass idea ... or, I mean it's rooted in that concept... – johnnietheblack Mar 26 '12 at 05:49
  • @johnnietheblack - That's awesome. Reflection is awesome but it can be a lot of work. :) – Francois Deschenes Mar 26 '12 at 05:50
2

Here is a piece of code that supports private properties:

$reflection_1 = new \ReflectionClass($object_1);
$reflection_2 = new \ReflectionClass($object_2);

$props_1 = $reflection_1->getProperties();

foreach ($props_1 as $prop_1) {

    $prop_1->setAccessible(true);
    $value_1 = $prop_1->getValue($object_1);

    $prop_2 = $reflection_2->getProperty($prop_1->getName());

    $prop_2->setAccessible(true);
    $value_2 = $prop_2->getValue($object_2);

    if ($value_1 === $value_2) {
        // ...
    } else {
        // ...
    }
}


Note that if $object_1 has properties that $object_2 doesn't have, the above code will produce errors when trying to access them.

For such a case, you would need first to intersect $reflection_1->getProperties() with $reflection_2->getProperties().

Gras Double
  • 14,028
  • 7
  • 52
  • 50
2

There is a whole site on php.net only dealing with comparing objects:

http://php.net/manual/en/language.oop5.object-comparison.php

Also a good read:

http://www.tuxradar.com/practicalphp/6/12/0

I would say you should use the "clone" method when copying your object and then later compare as said in the php.net article.

Also a fast way would be by declaring a static method in the class that just compares the relevant "properties" like lets say "username", "password", "cookie",...

    class User
    {

    public $username;
    public $password;
    public $cookie;

    # other code

    public static function compare(&$obj1, &$obj2)
    {
    if ($obj1->username != $obj2->username)
        return 0
    if ($obj1->password != $obj2->password)
        return 0
    if ($obj1->cookie != $obj2->cookie)
        return 0
    return 1
    }


    };       
  • The update is very good, like I said in the question...I'd like the User to not have to change ANYTHING to make this comparison possible... – johnnietheblack Mar 26 '12 at 05:30
  • thats a reason why static methods exist, to interact between objects of the same class :) with this method you could also determine fast which "property" has changed by simply return an assoc. array like $result["username" => "same", "password" => "same", "cookie" => "different"] –  Mar 26 '12 at 05:37
  • True! I just mainly don't want my User class worried about comparing itself to anything else, since it doesn't really concern the User. It only should concern the service that cares if User changed at all...you know? – johnnietheblack Mar 26 '12 at 05:48
  • Yeah I understand, I once did the same with a time registration for employees. There I used this method to compare the $time-property and I also cached it once a day via a file in json-format. So you didnt have to query the employee everytime and you could simply output how much time he worked. –  Mar 26 '12 at 05:54
  • Second link is broken – Lachezar Todorov Jun 18 '19 at 11:39
0

Simple example comparing dto class through reflection

class InvoiceDetails
{
    /** @var string */
    public $type;

    /** @var string */
    public $contractor;

    /** @var string */
    public $invoiceNumber;

    /** @var \DateTimeInterface|null */
    public $saleDate;

    /** @var \DateTimeInterface|null */
    public $issueDate;

    /** @var \DateTimeInterface|null */
    public $dueDate;

    /** @var string */
    public $payment;

    /** @var string */
    public $status;


    public function isEqual(InvoiceDetails $details): bool
    {
        $reflection = new \ReflectionClass(self::class);

        /** @var \ReflectionProperty $property */
        foreach ($reflection->getProperties() as $property) {
            $name = $property->getName();
            if ($this->$name !== $details->$name) {
                return false;
            }
        }

        return true;
    }

}
Jan Galtowski
  • 609
  • 5
  • 8