Drupal - Is it possible to hook to variable_set() event?

It seems that it's impossible using only Drupal, which means:

variable_set() itself doesn't invoke any hooks, but it uses db_merge(). That function is using the MergeQuery class. Now, it would be nice to hook with hook_query_alter(), but it only works for query classes that implements the QueryAlterableInterface interface. Sadly, this interface is now implemented only by the SelectQuery and the SelectQueryExtender classes, not by the MergeQuery class.

Note that even if you will find a way to create a child class of MergeQuery, that will implement QueryAlterableInterface, and make Drupal use it. hook_query_alter() only works on queries that have tags, and variable_set() doesn't tag its query, so the hook would not be used anyway, unless you are willing to hack core. But if you are, you don't need all that, you could simply hack in a hook call.

If you feel hardcore you can use a more indirect PHP approach: $conf is a global array of configuration variables; you can write a module that will replace it with object acting like an array, as described on Stack Overflow. To make it a good substitute you need to implement ArrayAccess. Pull all values from original $conf into your object. Then, in ArrayAccess::offsetSet() implement your logging logic.

You could use a database trigger, which would be faster than code.

Here is the MySQL doc.

  1. create a table to store old values

    CREATE TABLE variable_backup
        name varchar(128) not null,
        value longblob,
        updated datetime not null,
        primary key (name, updated)
  2. create your triggers, one for insert and one for update:

    CREATE TRIGGER backup_variable_update BEFORE UPDATE ON variable
        FOR EACH ROW
            INSERT INTO variable_backup (name, value, type, updated) VALUES (OLD.name, OLD.value, "update", NOW());
    CREATE TRIGGER backup_variable_insert BEFORE INSERT ON variable
        FOR EACH ROW
            INSERT INTO variable_backup (name, value, type, updated) VALUES (NEW.name, NEW.value, "insert", NOW());

Now all of your updates and inserts will record old values in variable_backup.

As you can see in the source code, variable_set() makes no request for hooks or alterations, e.g no module_invoke_all() or drupal_alter() calls there.

function variable_set($name, $value) {
  global $conf;

  db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute();

  cache_clear_all('variables', 'cache_bootstrap');

  $conf[$name] = $value;

However, you might be able to listen to the db_merge() query with a specially placed hook_query_alter() and do some additional processing there but, as pointed out by Molot, hook_query_alter() looks unlikely to be able to target the db_merge() query.

Alternatively, you could perhaps cron snapshot the variable table to diff it against previous revisions of that table, or else implement some other form of variable revision storage to compare against.