Make WordPress Core

Changeset 57030 for branches/6.4


Ignore:
Timestamp:
10/30/2023 11:21:03 PM (3 years ago)
Author:
peterwilsoncc
Message:

Options, Meta APIs: Fast follow fixes for option cache priming functions.

A collection of fixes for wp_prime_option_caches():

  • cache arrays and objects in their serialized form for consistency with get_option() and wp_load_alloptions()
  • prevent repeat database queries for falsey and known non-existent options (notoptions)

Additional tests for wp_prime_option_caches() to ensure:

  • additional database queries are not made repriming options (known, known-unknown and alloptions)
  • cache is primed consistently
  • get_option() returns a consistent value regardless of how it is primed
  • database queries do not contain earlier primed options
  • get_option does not prime the cache when testing the cache has been successfully primed

Fixes a test for wp_prime_option_caches_by_group() to ensure get_option does not prime the cache when testing the cache has been successfully primed.

Follow up to [56445],[56990],[57013].

Reviewed by flixos90, hellofromTonya, joemcgill.
Merges [57029] to the 6.4 branch.

Props peterwilsoncc, costdev, flixos90, hellofromTonya, mikeschroder, joemcgill.
Fixes #59738. See #58962.

Location:
branches/6.4
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • branches/6.4

  • branches/6.4/src/wp-includes/option.php

    r57016 r57030  
    262262        $alloptions     = wp_load_alloptions();
    263263        $cached_options = wp_cache_get_multiple( $options, 'options' );
     264        $notoptions     = wp_cache_get( 'notoptions', 'options' );
     265        if ( ! is_array( $notoptions ) ) {
     266                $notoptions = array();
     267        }
    264268
    265269        // Filter options that are not in the cache.
    266270        $options_to_prime = array();
    267271        foreach ( $options as $option ) {
    268                 if ( ( ! isset( $cached_options[ $option ] ) || ! $cached_options[ $option ] ) && ! isset( $alloptions[ $option ] ) ) {
     272                if (
     273                        ( ! isset( $cached_options[ $option ] ) || false === $cached_options[ $option ] )
     274                        && ! isset( $alloptions[ $option ] )
     275                        && ! isset( $notoptions[ $option ] )
     276                ) {
    269277                        $options_to_prime[] = $option;
    270278                }
     
    289297        $options_found = array();
    290298        foreach ( $results as $result ) {
    291                 $options_found[ $result->option_name ] = maybe_unserialize( $result->option_value );
     299                /*
     300                 * The cache is primed with the raw value (i.e. not maybe_unserialized).
     301                 *
     302                 * `get_option()` will handle unserializing the value as needed.
     303                 */
     304                $options_found[ $result->option_name ] = $result->option_value;
    292305        }
    293306        wp_cache_set_multiple( $options_found, 'options' );
     
    299312
    300313        $options_not_found = array_diff( $options_to_prime, array_keys( $options_found ) );
    301 
    302         $notoptions = wp_cache_get( 'notoptions', 'options' );
    303 
    304         if ( ! is_array( $notoptions ) ) {
    305                 $notoptions = array();
    306         }
    307314
    308315        // Add the options that were not found to the cache.
  • branches/6.4/tests/phpunit/tests/option/wpPrimeOptionCaches.php

    r57016 r57030  
    4242                foreach ( $options_to_prime as $option ) {
    4343                        $this->assertSame(
     44                                "value_$option",
    4445                                wp_cache_get( $option, 'options' ),
    45                                 get_option( $option ),
    4646                                "$option was not primed in the 'options' cache group."
    4747                        );
    4848
    49                         $this->assertFalse(
    50                                 wp_cache_get( $option, 'notoptions' ),
    51                                 get_option( $option ),
    52                                 "$option was primed in the 'notoptions' cache group."
     49                        $new_notoptions = wp_cache_get( $option, 'notoptions' );
     50                        if ( ! is_array( $new_notoptions ) ) {
     51                                $new_notoptions = array();
     52                        }
     53                        $this->assertArrayNotHasKey(
     54                                $option,
     55                                $new_notoptions,
     56                                "$option was primed in the 'notoptions' cache."
    5357                        );
    5458                }
     
    6367
    6468        /**
     69         * Tests that wp_prime_option_caches() handles a mix of primed and unprimed options.
     70         *
     71         * @ticket 58962
     72         */
     73        public function test_wp_prime_option_caches_handles_a_mix_of_primed_and_unprimed_options() {
     74                global $wpdb;
     75                // Create some options to prime.
     76                $options_to_prime = array(
     77                        'option1',
     78                        'option2',
     79                        'option3',
     80                );
     81
     82                /*
     83                 * Set values for the options,
     84                 * clear the cache for the options,
     85                 * check options are not in cache initially.
     86                 */
     87                foreach ( $options_to_prime as $option ) {
     88                        update_option( $option, "value_$option", false );
     89                        wp_cache_delete( $option, 'options' );
     90                        $this->assertFalse( wp_cache_get( $option, 'options' ), "$option was not deleted from the cache." );
     91                }
     92
     93                // Add non-existent option to the options to prime.
     94                $options_to_prime[] = 'option404notfound';
     95
     96                // Prime the first option with a non-existent option.
     97                wp_prime_option_caches( array( 'option1', 'option404notfound' ) );
     98
     99                // Store the initial database query count.
     100                $initial_query_count = get_num_queries();
     101
     102                // Prime all the options, including the pre-primed option.
     103                wp_prime_option_caches( $options_to_prime );
     104
     105                // Ensure an additional database query was made.
     106                $this->assertSame(
     107                        1,
     108                        get_num_queries() - $initial_query_count,
     109                        'Additional database queries were not made.'
     110                );
     111
     112                // Ensure the last query does not contain the pre-primed option.
     113                $this->assertStringNotContainsString(
     114                        "\'option1\'",
     115                        $wpdb->last_query,
     116                        'The last query should not contain the pre-primed option.'
     117                );
     118
     119                // Ensure the last query does not contain the pre-primed notoption.
     120                $this->assertStringNotContainsString(
     121                        "\'option404notfound\'",
     122                        $wpdb->last_query,
     123                        'The last query should not contain the pre-primed non-existent option.'
     124                );
     125        }
     126
     127        /**
    65128         * Tests wp_prime_option_caches() with options that do not exist in the database.
    66129         *
    67130         * @ticket 58962
     131         * @ticket 59738
    68132         */
    69133        public function test_wp_prime_option_caches_with_nonexistent_options() {
     
    97161                        $this->assertArrayHasKey( $option, $new_notoptions, "$option was not added to the notoptions cache." );
    98162                }
     163
     164                // Check getting and re-priming the options does not result in additional database queries.
     165                $initial_query_count = get_num_queries();
     166                foreach ( $options_to_prime as $option ) {
     167                        get_option( $option );
     168                        $this->assertSame(
     169                                0,
     170                                get_num_queries() - $initial_query_count,
     171                                "Additional database queries were made getting option $option."
     172                        );
     173                }
     174
     175                wp_prime_option_caches( $options_to_prime );
     176                $this->assertSame(
     177                        0,
     178                        get_num_queries() - $initial_query_count,
     179                        'Additional database queries were made re-priming the options.'
     180                );
    99181        }
    100182
     
    103185         *
    104186         * @ticket 58962
     187         * @ticket 59738
    105188         */
    106189        public function test_wp_prime_option_caches_with_empty_array() {
     
    108191                $notoptions = wp_cache_get( 'notoptions', 'options' );
    109192
     193                $initial_query_count = get_num_queries();
    110194                wp_prime_option_caches( array() );
    111195
    112196                $this->assertSame( $alloptions, wp_cache_get( 'alloptions', 'options' ), 'The alloptions cache was modified.' );
    113197                $this->assertSame( $notoptions, wp_cache_get( 'notoptions', 'options' ), 'The notoptions cache was modified.' );
     198
     199                // Check priming an empty array does not result in additional database queries.
     200                $this->assertSame(
     201                        0,
     202                        get_num_queries() - $initial_query_count,
     203                        'Additional database queries were made.'
     204                );
    114205        }
    115206
     
    118209         *
    119210         * @ticket 58962
     211         * @ticket 59738
    120212         */
    121213        public function test_wp_prime_option_caches_handles_empty_notoptions_cache() {
     
    127219                $this->assertIsArray( $notoptions, 'The notoptions cache should be an array.' );
    128220                $this->assertArrayHasKey( 'nonexistent_option', $notoptions, 'nonexistent_option was not added to notoptions.' );
     221
     222                // Check getting and re-priming the options does not result in additional database queries.
     223                $initial_query_count = get_num_queries();
     224
     225                get_option( 'nonexistent_option' );
     226                $this->assertSame(
     227                        0,
     228                        get_num_queries() - $initial_query_count,
     229                        'Additional database queries were made getting nonexistent_option.'
     230                );
     231
     232                wp_prime_option_caches( array( 'nonexistent_option' ) );
     233                $this->assertSame(
     234                        0,
     235                        get_num_queries() - $initial_query_count,
     236                        'Additional database queries were made.'
     237                );
     238        }
     239
     240        /**
     241         * Test options primed by the wp_prime_option_caches() function are identical to those primed by get_option().
     242         *
     243         * @ticket 59738
     244         *
     245         * @dataProvider data_option_types
     246         *
     247         * @param mixed $option_value An option value.
     248         */
     249        public function test_get_option_should_return_identical_value_when_pre_primed_by_wp_prime_option_caches( $option_value ) {
     250                // As this includes a test setting the value to `(bool) false`, update_option() can not be used so add_option() is used instead.
     251                add_option( 'type_of_option', $option_value, '', false );
     252                wp_cache_delete( 'type_of_option', 'options' );
     253
     254                $this->assertFalse( wp_cache_get( 'type_of_option', 'options' ), 'type_of_option was not deleted from the cache for priming.' );
     255
     256                // Call the wp_prime_option_caches function to prime the options.
     257                wp_prime_option_caches( array( 'type_of_option' ) );
     258                $value_after_pre_priming = get_option( 'type_of_option' );
     259
     260                // Clear the cache and call get_option directly.
     261                wp_cache_delete( 'type_of_option', 'options' );
     262                $this->assertFalse( wp_cache_get( 'type_of_option', 'options' ), 'type_of_option was not deleted from the cache for get_option.' );
     263                $value_after_get_option = get_option( 'type_of_option' );
     264
     265                /*
     266                 * If the option value is an object, use assertEquals() to compare the values.
     267                 *
     268                 * This is to compare the shape of the object rather than the identity of the object.
     269                 */
     270                if ( is_object( $option_value ) ) {
     271                        $this->assertEquals( $value_after_get_option, $value_after_pre_priming, 'The values should be equal.' );
     272                } else {
     273                        $this->assertSame( $value_after_get_option, $value_after_pre_priming, 'The values should be identical.' );
     274                }
     275        }
     276
     277        /**
     278         * Tests that wp_prime_option_caches() shapes the cache in the same fashion as get_option()
     279         *
     280         * @ticket 59738
     281         *
     282         * @dataProvider data_option_types
     283         *
     284         * @param mixed $option_value An option value.
     285         */
     286        public function test_wp_prime_option_caches_cache_should_be_identical_to_get_option_cache( $option_value ) {
     287                // As this includes a test setting the value to `(bool) false`, update_option() can not be used so add_option() is used instead.
     288                add_option( 'type_of_option', $option_value, '', false );
     289                wp_cache_delete( 'type_of_option', 'options' );
     290
     291                $this->assertFalse( wp_cache_get( 'type_of_option', 'options' ), 'type_of_option was not deleted from the cache for wp_prime_option_caches().' );
     292
     293                // Call the wp_prime_option_caches function to prime the options.
     294                wp_prime_option_caches( array( 'type_of_option' ) );
     295                $value_from_priming = wp_cache_get( 'type_of_option', 'options' );
     296
     297                wp_cache_delete( 'type_of_option', 'options' );
     298                $this->assertFalse( wp_cache_get( 'type_of_option', 'options' ), 'type_of_option was not deleted from the cache for get_option().' );
     299
     300                // Call get_option() to prime the options.
     301                get_option( 'type_of_option' );
     302                $value_from_get_option = wp_cache_get( 'type_of_option', 'options' );
     303
     304                $this->assertIsString( $value_from_get_option, 'Cache from get_option() should always be a string' );
     305                $this->assertIsString( $value_from_priming, 'Cache from wp_prime_option_caches() should always be a string' );
     306                $this->assertSame( $value_from_get_option, $value_from_priming, 'The values should be identical.' );
     307        }
     308
     309        /**
     310         * Tests that wp_prime_option_caches() doesn't trigger DB queries on already primed options.
     311         *
     312         * @ticket 59738
     313         *
     314         * @dataProvider data_option_types
     315         *
     316         * @param mixed $option_value An option value.
     317         */
     318        public function test_wp_prime_option_caches_does_not_trigger_db_queries_repriming_options( $option_value ) {
     319                // As this includes a test setting the value to `(bool) false`, update_option() can not be used so add_option() is used instead.
     320                add_option( 'double_primed_option', $option_value, '', false );
     321                wp_cache_delete( 'double_primed_option', 'options' );
     322                $options_to_prime = array( 'double_primed_option' );
     323
     324                $this->assertFalse( wp_cache_get( 'double_primed_option', 'options' ), 'double_primed_option was not deleted from the cache.' );
     325
     326                // Call the wp_prime_option_caches function to prime the options.
     327                wp_prime_option_caches( $options_to_prime );
     328
     329                // Store the initial database query count.
     330                $initial_query_count = get_num_queries();
     331
     332                // Check that options are only in the 'options' cache group.
     333                foreach ( $options_to_prime as $option ) {
     334                        $this->assertNotFalse(
     335                                wp_cache_get( $option, 'options' ),
     336                                "$option was not primed in the 'options' cache group."
     337                        );
     338
     339                        $new_notoptions = wp_cache_get( $option, 'notoptions' );
     340                        if ( ! is_array( $new_notoptions ) ) {
     341                                $new_notoptions = array();
     342                        }
     343                        $this->assertArrayNotHasKey(
     344                                $option,
     345                                $new_notoptions,
     346                                "$option was primed in the 'notoptions' cache."
     347                        );
     348                }
     349
     350                // Call the wp_prime_option_caches function to prime the options.
     351                wp_prime_option_caches( $options_to_prime );
     352
     353                // Ensure no additional database queries were made.
     354                $this->assertSame(
     355                        $initial_query_count,
     356                        get_num_queries(),
     357                        'Additional database queries were made.'
     358                );
     359        }
     360
     361        /**
     362         * Tests that wp_prime_option_caches() doesn't trigger DB queries for items primed in alloptions.
     363         *
     364         * @ticket 59738
     365         *
     366         * @dataProvider data_option_types
     367         *
     368         * @param mixed $option_value An option value.
     369         */
     370        public function test_wp_prime_option_caches_does_not_trigger_db_queries_for_alloptions( $option_value ) {
     371                // As this includes a test setting the value to `(bool) false`, update_option() can not be used so add_option() is used instead.
     372                add_option( 'option_in_alloptions', $option_value, '', true );
     373                wp_cache_delete( 'alloptions', 'options' );
     374                wp_cache_delete( 'option_in_alloptions', 'options' );
     375                $options_to_prime = array( 'option_in_alloptions' );
     376
     377                $this->assertFalse( wp_cache_get( 'option_in_alloptions', 'options' ), 'option_in_alloptions was not deleted from the cache.' );
     378                $this->assertFalse( wp_cache_get( 'alloptions', 'options' ), 'alloptions was not deleted from the cache.' );
     379
     380                // Prime the alloptions cache.
     381                wp_load_alloptions();
     382
     383                // Store the initial database query count.
     384                $initial_query_count = get_num_queries();
     385
     386                // Call the wp_prime_option_caches function to reprime the option.
     387                wp_prime_option_caches( $options_to_prime );
     388
     389                // Check that options are in the 'alloptions' cache only.
     390                foreach ( $options_to_prime as $option ) {
     391                        $this->assertFalse(
     392                                wp_cache_get( $option, 'options' ),
     393                                "$option was primed in the 'options' cache group."
     394                        );
     395
     396                        $new_notoptions = wp_cache_get( $option, 'notoptions' );
     397                        if ( ! is_array( $new_notoptions ) ) {
     398                                $new_notoptions = array();
     399                        }
     400                        $this->assertArrayNotHasKey(
     401                                $option,
     402                                $new_notoptions,
     403                                "$option was primed in the 'notoptions' cache."
     404                        );
     405
     406                        $new_alloptions = wp_cache_get( 'alloptions', 'options' );
     407                        if ( ! is_array( $new_alloptions ) ) {
     408                                $new_alloptions = array();
     409                        }
     410                        $this->assertArrayHasKey(
     411                                $option,
     412                                $new_alloptions,
     413                                "$option was not primed in the 'alloptions' cache."
     414                        );
     415                }
     416
     417                // Ensure no additional database queries were made.
     418                $this->assertSame(
     419                        0,
     420                        get_num_queries() - $initial_query_count,
     421                        'Additional database queries were made.'
     422                );
     423        }
     424
     425        /**
     426         * Data provider.
     427         *
     428         * @return array[]
     429         */
     430        public function data_option_types() {
     431                return array(
     432                        'null'                              => array( null ),
     433                        '(bool) false'                      => array( false ),
     434                        '(bool) true'                       => array( true ),
     435                        '(int) 0'                           => array( 0 ),
     436                        '(int) -0'                          => array( -0 ),
     437                        '(int) 1'                           => array( 1 ),
     438                        '(int) -1'                          => array( -1 ),
     439                        '(float) 0.0'                       => array( 0.0 ),
     440                        '(float) -0.0'                      => array( -0.0 ),
     441                        '(float) 1.0'                       => array( 1.0 ),
     442                        'empty string'                      => array( '' ),
     443                        'string with only tabs'             => array( "\t\t" ),
     444                        'string with only newlines'         => array( "\n\n" ),
     445                        'string with only carriage returns' => array( "\r\r" ),
     446                        'string with only spaces'           => array( '   ' ),
     447                        'populated string'                  => array( 'string' ),
     448                        'string (1)'                        => array( '1' ),
     449                        'string (0)'                        => array( '0' ),
     450                        'string (0.0)'                      => array( '0.0' ),
     451                        'string (-0)'                       => array( '-0' ),
     452                        'string (-0.0)'                     => array( '-0.0' ),
     453                        'empty array'                       => array( array() ),
     454                        'populated array'                   => array( array( 'string' ) ),
     455                        'empty object'                      => array( new stdClass() ),
     456                        'populated object'                  => array( (object) array( 'string' ) ),
     457                        'INF'                               => array( INF ),
     458                        'NAN'                               => array( NAN ),
     459                );
    129460        }
    130461}
  • branches/6.4/tests/phpunit/tests/option/wpPrimeOptionCachesByGroup.php

    r57016 r57030  
    4848                wp_prime_option_caches_by_group( 'group1' );
    4949
    50                 // Check that options are now in the cache.
    51                 $this->assertSame( get_option( 'option1' ), wp_cache_get( 'option1', 'options' ), 'option1\'s cache was not primed.' );
    52                 $this->assertSame( get_option( 'option2' ), wp_cache_get( 'option2', 'options' ), 'option2\'s cache was not primed.' );
     50                /*
     51                 * Check that options are now in the cache.
     52                 *
     53                 * Repeat the string here rather than using get_option as get_option
     54                 * will prime the cache before the call to wp_cache_get if the option
     55                 * is not in the cache. Thus causing the tests to pass when they should
     56                 * fail.
     57                 */
     58                $this->assertSame( 'value_option1', wp_cache_get( 'option1', 'options' ), 'option1\'s cache was not primed.' );
     59                $this->assertSame( 'value_option2', wp_cache_get( 'option2', 'options' ), 'option2\'s cache was not primed.' );
    5360
    5461                // Make sure option3 is still not in cache.
Note: See TracChangeset for help on using the changeset viewer.