mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2026-08-11 12:20:22 +00:00
Options, Meta APIs: Introduce prime_options() to load multiple options with a single database request.
WordPress's `get_option()` function generally relies on making individual database requests for each option, however with the majority of options (in most cases) being autoloaded, i.e. fetched once with a single database request and then stored in (memory) cache. As part of a greater effort to reduce the amount of options that are unnecessarily autoloaded, this changeset introduces an alternative way to retrieve multiple options in a performant manner, with a single database request. This provides a reasonable alternative for e.g. plugins that use several options which only need to be loaded in a few specific screens. Specifically, this changeset introduces the following functions: * `prime_options( $options )` is the foundation to load multiple specific options with a single database request. Only options that aren't already cached (in `alloptions` or an individual cache) are retrieved from the database. * `prime_options_by_group( $option_group )` is a convenience wrapper function for the above which allows to prime all options of a specific option group (as configured via `register_setting()`). * `get_options( $options )` is another wrapper function which first primes the requested options and then returns them in an associative array, calling `get_option()` for each of them. Props mukesh27, joemcgill, costdev, olliejones. Fixes #58962. git-svn-id: https://develop.svn.wordpress.org/trunk@56445 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
@@ -253,6 +253,117 @@ function get_option( $option, $default_value = false ) {
|
||||
return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Primes specific options into the cache with a single database query.
|
||||
*
|
||||
* Only options that do not already exist in cache will be primed.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @global wpdb $wpdb WordPress database abstraction object.
|
||||
*
|
||||
* @param array $options An array of option names to be primed.
|
||||
*/
|
||||
function prime_options( $options ) {
|
||||
$alloptions = wp_load_alloptions();
|
||||
$cached_options = wp_cache_get_multiple( $options, 'options' );
|
||||
|
||||
// Filter options that are not in the cache.
|
||||
$options_to_prime = array();
|
||||
foreach ( $options as $option ) {
|
||||
if ( ( ! isset( $cached_options[ $option ] ) || ! $cached_options[ $option ] ) && ! isset( $alloptions[ $option ] ) ) {
|
||||
$options_to_prime[] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
// Bail early if there are no options to be primed.
|
||||
if ( empty( $options_to_prime ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
sprintf(
|
||||
"SELECT option_name, option_value FROM $wpdb->options WHERE option_name IN (%s)",
|
||||
implode( ',', array_fill( 0, count( $options_to_prime ), '%s' ) )
|
||||
),
|
||||
$options_to_prime
|
||||
)
|
||||
);
|
||||
|
||||
$options_found = array();
|
||||
foreach ( $results as $result ) {
|
||||
$options_found[ $result->option_name ] = maybe_unserialize( $result->option_value );
|
||||
}
|
||||
wp_cache_set_multiple( $options_found, 'options' );
|
||||
|
||||
// If all options were found, no need to update `notoptions` cache.
|
||||
if ( count( $options_found ) === count( $options_to_prime ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$options_not_found = array_diff( $options_to_prime, array_keys( $options_found ) );
|
||||
|
||||
$notoptions = wp_cache_get( 'notoptions', 'options' );
|
||||
|
||||
if ( ! is_array( $notoptions ) ) {
|
||||
$notoptions = array();
|
||||
}
|
||||
|
||||
// Add the options that were not found to the cache.
|
||||
$update_notoptions = false;
|
||||
foreach ( $options_not_found as $option_name ) {
|
||||
if ( ! isset( $notoptions[ $option_name ] ) ) {
|
||||
$notoptions[ $option_name ] = true;
|
||||
$update_notoptions = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only update the cache if it was modified.
|
||||
if ( $update_notoptions ) {
|
||||
wp_cache_set( 'notoptions', $notoptions, 'options' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primes all options registered with a specific option group.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @global array $new_allowed_options
|
||||
*
|
||||
* @param string $option_group The option group to prime options for.
|
||||
*/
|
||||
function prime_options_by_group( $option_group ) {
|
||||
global $new_allowed_options;
|
||||
|
||||
if ( isset( $new_allowed_options[ $option_group ] ) ) {
|
||||
prime_options( $new_allowed_options[ $option_group ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves multiple options.
|
||||
*
|
||||
* Options are primed as necessary first in order to use a single database query at most.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param array $options An array of option names to retrieve.
|
||||
* @return array An array of key-value pairs for the requested options.
|
||||
*/
|
||||
function get_options( $options ) {
|
||||
prime_options( $options );
|
||||
|
||||
$result = array();
|
||||
foreach ( $options as $option ) {
|
||||
$result[ $option ] = get_option( $option );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protects WordPress special option from being modified.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Test get_options().
|
||||
*
|
||||
* @group option
|
||||
*
|
||||
* @covers ::get_options
|
||||
*/
|
||||
class Tests_Option_GetOptions extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that get_options() retrieves specified options.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_get_options() {
|
||||
// Create some options to prime.
|
||||
$options_to_prime = array(
|
||||
'option1',
|
||||
'option2',
|
||||
);
|
||||
|
||||
/*
|
||||
* Set values for the options,
|
||||
* clear the cache for the options,
|
||||
* check options are not in cache initially.
|
||||
*/
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
update_option( $option, "value_$option", false );
|
||||
wp_cache_delete( $option, 'options' );
|
||||
$this->assertFalse( wp_cache_get( $option, 'options' ), "$option was not deleted from the cache." );
|
||||
}
|
||||
|
||||
// Call the get_options function to retrieve the options.
|
||||
$options = get_options( array( 'option1', 'option2' ) );
|
||||
|
||||
// Check that options are now in the cache.
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
$this->assertSame( wp_cache_get( $option, 'options' ), get_option( $option ), "$option was not primed." );
|
||||
}
|
||||
|
||||
// Check that the retrieved options are correct.
|
||||
$this->assertSame( get_option( 'option1' ), $options['option1'], 'Retrieved option1 does not match expected value.' );
|
||||
$this->assertSame( get_option( 'option2' ), $options['option2'], 'Retrieved option2 does not match expected value.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests get_options() with an empty input array.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_get_options_with_empty_array() {
|
||||
// Call the get_options function with an empty array.
|
||||
$options = get_options( array() );
|
||||
|
||||
// Make sure the result is an empty array.
|
||||
$this->assertIsArray( $options, 'An array should have been returned.' );
|
||||
$this->assertEmpty( $options, 'No options should have been returned.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests get_options() with options that include some nonexistent options.
|
||||
*/
|
||||
public function test_get_options_with_nonexistent_options() {
|
||||
// Create some options to prime.
|
||||
$options_to_prime = array(
|
||||
'option1',
|
||||
);
|
||||
|
||||
// Make sure options are not in cache or database initially.
|
||||
$this->assertFalse( wp_cache_get( 'option1', 'options' ), 'option1 was not deleted from the cache.' );
|
||||
$this->assertFalse( wp_cache_get( 'nonexistent_option', 'options' ), 'nonexistent_option was not deleted from the cache.' );
|
||||
|
||||
// Call the get_options function with an array that includes a nonexistent option.
|
||||
$options = get_options( array( 'option1', 'nonexistent_option' ) );
|
||||
|
||||
// Check that the retrieved options are correct.
|
||||
$this->assertSame( get_option( 'option1' ), $options['option1'], 'Retrieved option1 does not match expected value.' );
|
||||
|
||||
// Check that options are present in the notoptions cache.
|
||||
$new_notoptions = wp_cache_get( 'notoptions', 'options' );
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
$this->assertTrue( isset( $new_notoptions[ $option ] ), "$option was not added to the notoptions cache." );
|
||||
}
|
||||
|
||||
// Check that the nonexistent option is in the result array.
|
||||
$this->assertArrayHasKey( 'nonexistent_option', $options, 'Result array should not contain nonexistent_option.' );
|
||||
|
||||
$this->assertFalse( $options['nonexistent_option'], 'nonexistent_option is present in option.' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Test prime_options().
|
||||
*
|
||||
* @group option
|
||||
*
|
||||
* @covers ::prime_options
|
||||
*/
|
||||
class Tests_Option_PrimeOptions extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that prime_options() primes multiple options.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_prime_options() {
|
||||
// Create some options to prime.
|
||||
$options_to_prime = array(
|
||||
'option1',
|
||||
'option2',
|
||||
'option3',
|
||||
);
|
||||
|
||||
/*
|
||||
* Set values for the options,
|
||||
* clear the cache for the options,
|
||||
* check options are not in cache initially.
|
||||
*/
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
update_option( $option, "value_$option", false );
|
||||
wp_cache_delete( $option, 'options' );
|
||||
$this->assertFalse( wp_cache_get( $option, 'options' ), "$option was not deleted from the cache." );
|
||||
}
|
||||
|
||||
// Call the prime_options function to prime the options.
|
||||
prime_options( $options_to_prime );
|
||||
|
||||
// Store the initial database query count.
|
||||
$initial_query_count = get_num_queries();
|
||||
|
||||
// Check that options are only in the 'options' cache group.
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
$this->assertSame(
|
||||
wp_cache_get( $option, 'options' ),
|
||||
get_option( $option ),
|
||||
"$option was not primed to the 'options' cache group."
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
wp_cache_get( $option, 'notoptions' ),
|
||||
get_option( $option ),
|
||||
"$option was primed to the 'notoptions' cache group."
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure no additional database queries were made.
|
||||
$this->assertSame(
|
||||
$initial_query_count,
|
||||
get_num_queries(),
|
||||
'Additional database queries were made.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests prime_options() with options that do not exist in the database.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_prime_options_with_nonexistent_options() {
|
||||
// Create some options to prime.
|
||||
$options_to_prime = array(
|
||||
'option1',
|
||||
'option2',
|
||||
);
|
||||
|
||||
/*
|
||||
* Set values for the options,
|
||||
* clear the cache for the options,
|
||||
* check options are not in cache initially.
|
||||
*/
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
$this->assertFalse( wp_cache_get( $option, 'options' ), "$option was not deleted from the cache." );
|
||||
}
|
||||
|
||||
// Call the prime_options function to prime the options.
|
||||
prime_options( $options_to_prime );
|
||||
|
||||
// Check that options are not in the cache or database.
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
$this->assertFalse( wp_cache_get( $option, 'options' ), "$option was not deleted from the cache." );
|
||||
}
|
||||
|
||||
// Check that options are present in the notoptions cache.
|
||||
$new_notoptions = wp_cache_get( 'notoptions', 'options' );
|
||||
$this->assertIsArray( $new_notoptions, 'The notoptions cache should be an array.' );
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
$this->assertArrayHasKey( $option, $new_notoptions, "$option was not added to the notoptions cache." );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests prime_options() with an empty array.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_prime_options_with_empty_array() {
|
||||
$alloptions = wp_load_alloptions();
|
||||
$notoptions = wp_cache_get( 'notoptions', 'options' );
|
||||
|
||||
prime_options( array() );
|
||||
|
||||
$this->assertSame( $alloptions, wp_cache_get( 'alloptions', 'options' ), 'The alloptions cache was modified.' );
|
||||
$this->assertSame( $notoptions, wp_cache_get( 'notoptions', 'options' ), 'The notoptions cache was modified.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that prime_options handles an empty "notoptions" cache.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_prime_options_handles_empty_notoptions_cache() {
|
||||
wp_cache_delete( 'notoptions', 'options' );
|
||||
|
||||
prime_options( array( 'nonexistent_option' ) );
|
||||
|
||||
$notoptions = wp_cache_get( 'notoptions', 'options' );
|
||||
$this->assertIsArray( $notoptions, 'The notoptions cache should be an array.' );
|
||||
$this->assertArrayHasKey( 'nonexistent_option', $notoptions, 'nonexistent_option was not added to notoptions.' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Test prime_options_by_group().
|
||||
*
|
||||
* @group option
|
||||
*
|
||||
* @covers ::prime_options_by_group
|
||||
*/
|
||||
class Tests_Option_PrimeOptionsByGroup extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests that prime_options_by_group() only primes options in the specified group.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_prime_options_by_group() {
|
||||
global $new_allowed_options;
|
||||
|
||||
// Create some options to prime.
|
||||
$new_allowed_options = array(
|
||||
'group1' => array(
|
||||
'option1',
|
||||
'option2',
|
||||
),
|
||||
'group2' => array(
|
||||
'option3',
|
||||
),
|
||||
);
|
||||
|
||||
$options_to_prime = array(
|
||||
'option1',
|
||||
'option2',
|
||||
'option3',
|
||||
);
|
||||
|
||||
/*
|
||||
* Set values for the options,
|
||||
* clear the cache for the options,
|
||||
* check options are not in cache initially.
|
||||
*/
|
||||
foreach ( $options_to_prime as $option ) {
|
||||
update_option( $option, "value_$option", false );
|
||||
wp_cache_delete( $option, 'options' );
|
||||
$this->assertFalse( wp_cache_get( $option, 'options' ), "$option was not deleted from the cache." );
|
||||
}
|
||||
|
||||
// Call the prime_options_by_group function to prime the options.
|
||||
prime_options_by_group( 'group1' );
|
||||
|
||||
// Check that options are now in the cache.
|
||||
$this->assertSame( get_option( 'option1' ), wp_cache_get( 'option1', 'options' ), 'option1 was not primed.' );
|
||||
$this->assertSame( get_option( 'option2' ), wp_cache_get( 'option2', 'options' ), 'option2 was not primed.' );
|
||||
|
||||
// Make sure option3 is still not in cache.
|
||||
$this->assertFalse( wp_cache_get( 'option3', 'options' ), 'option3 was not deleted from the cache.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests prime_options_by_group() with a nonexistent option group.
|
||||
*
|
||||
* @ticket 58962
|
||||
*/
|
||||
public function test_prime_options_by_group_with_nonexistent_group() {
|
||||
// Make sure options are not in cache or database initially.
|
||||
$this->assertFalse( wp_cache_get( 'option1', 'options' ), 'option1 was not deleted from the cache.' );
|
||||
$this->assertFalse( wp_cache_get( 'option2', 'options' ), 'option2 was not deleted from the cache.' );
|
||||
|
||||
// Call the prime_options_by_group function with a nonexistent group.
|
||||
prime_options_by_group( 'nonexistent_group' );
|
||||
|
||||
// Check that options are still not in the cache or database.
|
||||
$this->assertFalse( wp_cache_get( 'option1', 'options' ), 'option1 was not deleted from the cache.' );
|
||||
$this->assertFalse( wp_cache_get( 'option2', 'options' ), 'option2 was not deleted from the cache.' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user