WordPress settings API is PITA

The WordPress settings API is there to “help authors manage their plugin custom options“, but does it? Many lengthy tutorials pointed to from the codex hints that the answer is probably “not really”. To quote Olly Benson from yet another settings API tutorial/example (emphasize mine)

WordPress does make a habit of creating mountains out of molehills sometimes, and the Settings API seems to be a fantastic example of this.  Trying to follow the instructions on the initial page got me hopelessly lost, and it was only when I went through Otto’s WordPress Settings API tutorial that I begin to understand how to implement it.

And the problem is not with the codex, the problem is in the structure of the API itself. Instead of having a simple fast and dirty code like

add_action('admin_init','my_init');

function my_init() {
add_page('title','title',10,'my_options_page');
}

function my_options_page() {
if (isset($_POST['my_value'])) {
validate_value();
update_option('my_option',$_POST['my_value']);
}
<form>
Enter value <input type="text" name="my_value" value="<?echo get_option('my_option')?>
</form>
}

Where all the logic of handling the change of values is placed in the same place as the presentation, the my_options_page function, which makes it much easier to understand and debug.

The settings API basically moves your options handling away from your presentation code. To use it you need to call at least 3 initialization functions to which you have to supply 3 callback functions, and all the handling is done in a “black box” that doesn’t give you any hint for misconfiguration and it is hard to debug.

When trying to use the API I end spending more time to make myself feel good about following coding best practices then needed to code the same functionality in an equally accessible and secure way.

Caching with transient options and API in wordpress

from the transiants api codex page:

… offers a simple and standardized way of storing cached data in the database temporarily by giving it a custom name and a timeframe after which it will expire and be deleted

One usage pattern for the transient API is to cache values you retrieve from a remote server. The overhead of establishing a connection to the remote server, sending a query and waiting for a reply is too big so we make a concession and instead of been totally up to date with our info, but with a site that take ages to load, we better be 5 minutes late with the info, but with usable site, and we will do it by caching the last result for 5 minutes in a transient option.

So instead of having

echo get_my_latest_video_from_youtube();

We can use

$video = get_transient('latestvideo'); // we might have the value already in our cache, lets retrieve it
if ($video) { // it is there
echo $video;
} else { // nothing in the cache, or the cache had expired
$video =  get_my_latest_video_from_youtube(); // get the video code
set_transient('latestvideo',$video,5*60); // set the cache with expiry set to 5 minutes in the future
echo $video;
}

The nice thing about this code is its robustness as it will recover from any event that hurt the cache and regenerate the info.

Important to note that this solution does not eliminate entirely the delay in site load caused by accessing the remote data, it just make it less frequent. For a site which has only 1 visitor every 5 minutes or more we basically haven’t changed anything as the cache will expire before the next visitor will come. Setting longer expiration interval gives you more performance value, so you should set it as long as possible without making the displayed data to be stupidly out of date.

But what can be done if we want that all of our users will have great experience, not only 99% of the time, but 100% of the time? If our interval is long enough we can pre populate the cache with a scheduled backend operation.

The next code assumes your interval is 5 hours

wp_schedule_event(time(),'hourly','regenerate_video'); // since we want to the avoid the situation in which the cache expires we have to use a schedule which is smaller then 5 hours. This should really be done only on plugin or theme activation

add_action('regenerate_video',regenerate_video);

function regenerate_video() {
$video =  get_my_latest_video_from_youtube(); // get the video code
set_transient('latestvideo',$video,5*60*60);
}

This way we prime the cache every hour and therefor every user gets a current enough info. But then the cache practically never expires couldn’t we get the same results by using the usual options api aand store the cached value as an option? Our code will then look like

// on front end
$video = get_option('latestvideo');
if (!$video) {
regenerat_video();
$video = get_option('latestvideo');
}
echo $video;

// on the backend
wp_schedule_event(time(),'hourly','regenerate_video'); // since we want to the avoid the situation in which the cache expires we have to use a schedule which is smaller then 5 hours. This should really be done only on plugin or theme activation

add_action('regenerate_video',regenerate_video);

function regenerate_video() {
$video =  get_my_latest_video_from_youtube(); // get the video code
update_option('latestvideo',$video);
}

What we have done here is to practically change the expiry mechanism. Now we can control better when the data expires.

So which pattern is better, transients or straight options? There is another factor you need to take into account before deciding about that – the existence of object catching in the site.
Unlike options, transients do not autoload into memory when WordPress starts up. This means that if there is no active object cache on the site, get_transient actually performs an extra SB query, and there is nothing worse for performance then a DB query that can be avoided, especially when this query is happening on the front end.

On the other hand, when object caching is active, transients are not stored to the DB at all, but only at the cache. This eliminates the cost of using get_transient and make the options table smaller and therefor each operation (add,change,delete,query) on it faster.

 

The subtle differences between get_alloptions, wp_load_alloptions and get_option

WordPress code had been very bad and made me waste several hours because of lack of proper documentation :(. All I was trying to do was to write a script that will do a simple search and replace on the text part of a text widget and each time I  would run the script the widget will stop being displayed.

My code was very simple

$opts = get_alloptions();
foreach ($opts  as $k=>$ogt) {
  if it is a text widget {
    $opt = searchandreplace($opt);
    update_option($ov,$opt);
  }
}

Turns out get_alloptions is deprecated  in favour of wp_load_options (i.e. the codex entry for it is wrong) therefor it does cache the options,but the array it returns is raw values which might be seialized while get_option return unserialized data. That was the source for my problem as I was assuming that get_alloptions is just a sytax sugar for calling get_option for each option at once.

The working code looks like

$opts = wp_load_options();
foreach ($opts  as $ov=>$opt) {
  if it is a text widget {
    $opt=get_option($ov); // already cached so no extra DB access
    $opt = searchandreplace($opt);
    update_option($ov,$opt);
  }
}

And then it gets even worse as suddenly I discover that wp_load_options returns only the autoloaded options but I want my code to be generic enough to work on not autoloaded as well so there is basically no alternative but to do a direct DB access

$opts = $wpdb->get_results( "SELECT option_name FROM $wpdb->options");
foreach ( (array) $opts  as $ov) {
  if it is a text widget {
    $opt=get_option($ov); // already cached so no extra DB access
    $opt = searchandreplace($opt);
    update_option($ov,$opt);
  }
}