Make WordPress Core

Opened 5 months ago

Closed 5 months ago

Last modified 5 months ago

#63610 closed defect (bug) (duplicate)

Inconsistency in responses from get_option() for false value

Reported by: ravanh's profile RavanH Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Options, Meta APIs Keywords:
Focuses: Cc:

Description

When saving an option as false with update_option( 'test_option', false ) the value in the database will be empty, same as when doing update_option( 'test_option', '' ). When fetched with get_option( 'test_option' ) the returned value will be an empty string. Only when an option does not exist in the database, the returned value will be the boolean false.

So far, so good: Both false and empty are stored and returned as empty string and a non-existant option can be recognized as boolean false.

But...

This behavior is different in two scenarios:

  1. when an option is updated as false and then fetched within the same request, or
  2. when there is a persistent object cache active.

In these cases, after doing update_option( 'test_option', false ) then get_option( 'test_option' ) will return the boolean false and not an empty string as should be expected because it exists in the database as en empty string.

To reproduce, create a file called test.php in a fresh site root, then populate with:

<?php
require('./wp-load.php');

// Get a non-existent option for comparison.
// Should always return false.
var_dump( get_option( 'non_existant_option' ) );

// Prepare an option in the DB.
// This is only done once, on the first run.
add_option( 'test_option', 'first run' );

// Get our option from the DB (or from options cache the first time).
// Should return "first run" on the first request, then after
// the last update to 'false' it should return an empty string.
var_dump( get_option( 'test_option' ) );

// Update the test option with non-falsy value.
update_option( 'test_option', 'some value' );

// Update the test option with false.
update_option( 'test_option', false );

// Get our option a second time.
// Should also return an empty string.
var_dump( get_option( 'test_option' ) );

The first request for /test.php should render:

bool(false) string(9) "first run" string(0) ""

And subsequent request should render:

bool(false) string(0) "" string(0) ""

But in practice, the last dump gives an unexpected bool(false) instead (same as the non-existant option) even though the option exists in the database.

This shows the issue happening within one request, which might be unusual but is still possible. But with a persistent object cache (redis, memcached), this inconsistency between the first returned test value and the second one is made persistent across requests and much more likely to cause unexpected behavior! Hence my initial assumption it was an object cache bug, as discussed on https://wordpress.org/support/topic/object-cache-bug-3

But it seems the issue lies with how false is stored in the options cache with wp_cache_set().

Note: This happens only for options with autoload set to "auto" (in this example), "auto-on" or "on". When the option is first created with add_option( 'test_option', 'first run', null, false ); so autoload is set to "off", the last dump will show the expected string(0) ""!

Change History (2)

#1 @peterwilsoncc
5 months ago

  • Milestone Awaiting Review deleted
  • Resolution set to duplicate
  • Status changed from new to closed
  • Version 6.8 deleted

Hi @RavanH and thanks for the report.

I've closed this off as a duplicate of #32848 as the root cause of the issue is the same, the cache is warmed incorrectly when the options are first primed compared to when the data is retrieved from the database.

The other ticket uses null as an example which has further complications than false but the patch on that ticket will resolve both issues.

#2 @RavanH
5 months ago

O wow, that one is 10 years old. Will it ever be fixed?

Note: See TracTickets for help on using tickets.