First, I generally think a $modified or $dirty property is the best solution here.
But the reason I'm writing was your comment that it would be
tedious if there are a lot of setters
I'm wondering if you're not using PHP5?
If you are, this is a perfect use of the __set() magic method. You could even use it as-is, calling your existing setters from the __set() method.
For example:
class some_concept
{
private $x = 1;
private $y = 2;
private $dirty = false;
// This represents your existing setter methods
function set_y($i)
{
$this->y = $i;
}
function __set($name, $value)
{
if ($value != $this->{$name}) $this->dirty = true;
return $this->{'set_' . $name}($value);
}
function __get($name)
{
return $this->{$name};
}
}
However, there is some merit to your idea of comparing serialized strings. Serialization can be expensive. It's true.
But it is the most accurate solution on your list. Imagine loading the object above, setting $y to 0, then setting it back to 2, then saving. Is it dirty? It will say so, but in reality, it's in the same state it was when it was loaded.
The test I would use here is how expensive is your save(). If saving is a very expensive API call, DB transaction, etc, then you might find that it's worth the expense to serialize the object on-load and save an md5 hash of it.
If that op, which will take a fraction of a second, can save a multi-second transaction, then it could really be worth it.
Finally, I want to point out a contrarian opinion on this subject from Damien Katz. Katz is a talented developer who created CouchDb, and currently works for MySQL.
His Error Codes v Exceptions post is long but a very good read on this topic.
While it begins talking about the merits of returning an Error Code of throwing an Exception, he really ends up talking about how to write solid software.
Primarily, he talks about how to create classes that are atomic in the same way that SQL Transactions are. The general idea being that you make a copy of an object, modify it its state, and, only on the last step, if successful, do you swap that copy out for the primary object. That allows meaningful undo features. A pattern like this, while difficult to shim into an existing app, also provides a solution to this is_modified problem.