8

I would like to track the system changes event, to make them revertable. When checking variable_set(), I see there is no hook provided for that event. Is there any way for me to do this?

I can alter to hook to settings forms, but there's a lot of settings form to track, if I can hook to variable_set() directly, code becomes much simpler.

I can also track the variable changes with features + strongarm modules, but it's better if Drupal admin can browse the variable history without code touching.

1
  • I opened a Feature Request ticket on Drupal.org to create hooks for intercepting the setting and deleting of system variables, and I submitted for review a core patch for this! Please see: drupal.org/project/drupal/issues/2934718 Jan 8, 2018 at 22:20

3 Answers 3

11

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.

3
  • I forgot about this old answer of mine and I already found how to do pretty much anything with $conf back then :D Hope my updated answer will help somebody.
    – Mołot
    Sep 30, 2013 at 13:55
  • I'm impressed! :D
    – Letharion
    Sep 30, 2013 at 14:26
  • @Letharion Thank you :) Once upon a time I liked "object acting as ..." magic in PHP, and I still remember a bit about it ;)
    – Mołot
    Sep 30, 2013 at 14:28
6

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.

4
  • I like it, neat and simple. Probably faster than my method, too. More limited, but enough to do what OP needs, if he has sufficient MySQL access.
    – Mołot
    Sep 30, 2013 at 14:56
  • I like this way. But I think we need to track user agent information too Oct 1, 2013 at 7:15
  • I doubt that information would be available to the database. Oct 1, 2013 at 14:25
  • @AndyTruong that info will not be in the database, sorry. I tried to look around but it's impossible to guess it with data in session table, and only select queries are alterable so no way to add it. So my "hardcore" method is there for you. Hope you had success at implementing it, if not - ask new question linking to it, and comment that you did so.
    – Mołot
    Oct 2, 2013 at 9:46
5

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.

2
  • 1
    If hook_query_alter can be used on this query, I would love to see how. In 8 QueryAlterableInterface is indeed implemented by Query itself. But in 8 configuration management is rebuilt anyway. And in 7 only tagged select queries are alterable as far as I see. But maybe I'm missing something?
    – Mołot
    Sep 30, 2013 at 9:23
  • 1
    No, you are correct, hook_query_alter was a guess, but not viable in D7 in this case, thanks. Sep 30, 2013 at 9:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.