Make WordPress Core

Changeset 61857


Ignore:
Timestamp:
03/06/2026 08:04:31 PM (7 months ago)
Author:
dmsnell
Message:

Functions: Memoize wp_normalize_path().

wp_normalize_path() is called thousands of times on a given request. This patch adds memoization via a function-local static variable. This reduces the call count to the underlying wp_is_stream() function, and measured in testing around a 66% cache hit rate.

In testing, for a site making 4000 calls to wp_normalize_path(), this patch led to a reduction in runtime from 1.4 ms to 0.4 ms on the test computer. While small, this time occurs early in the hotpath of the loading WordPress.

Developed in: ​https://github.com/WordPress/wordpress-develop/pull/10770
Discussed in: https://core.trac.wordpress.org/ticket/64538

Props dmsnell, josephscott, mreishus, westonruter.
Fixes #64538.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/functions.php

    r61710 r61857  
    21802180 * @since 4.5.0 Allows for Windows network shares.
    21812181 * @since 4.9.7 Allows for PHP file wrappers.
     2182 * @since 7.0.0 Uses a static cache to store normalized paths.
    21822183 *
    21832184 * @param string $path Path to normalize.
    21842185 * @return string Normalized path.
    21852186 */
    2186 function wp_normalize_path( $path ) {
    2187         $wrapper = '';
     2187function wp_normalize_path( $path ): string {
     2188        $path = (string) $path;
     2189
     2190        static $cache = array();
     2191        if ( isset( $cache[ $path ] ) ) {
     2192                return $cache[ $path ];
     2193        }
     2194
     2195        $original_path = $path;
     2196        $wrapper       = '';
    21882197
    21892198        if ( wp_is_stream( $path ) ) {
    … …  
    21972206
    21982207        // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
    2199         $path = preg_replace( '|(?<=.)/+|', '/', $path );
     2208        $path = (string) preg_replace( '|(?<=.)/+|', '/', $path );
    22002209
    22012210        // Windows paths should uppercase the drive letter.
    … …  
    22042213        }
    22052214
    2206         return $wrapper . $path;
     2215        $cache[ $original_path ] = $wrapper . $path;
     2216        return $cache[ $original_path ];
    22072217}
    22082218
  • trunk/tests/phpunit/tests/functions.php

    r61328 r61857  
    223223                        array( 'http://example.com//path.ext', 'http://example.com/path.ext' ),
    224224                        array( 'file://c:\\www\\path\\', 'file://C:/www/path/' ),
    225                 );
     225
     226                        // Edge cases.
     227                        array( '', '' ), // Empty string should return empty string.
     228                        array( 123, '123' ), // Integer should be cast to string.
     229                );
     230        }
     231
     232        /**
     233         * Tests that wp_normalize_path() works with objects that have __toString().
     234         *
     235         * This is important because the function uses a static cache, and the input
     236         * must be cast to string before being used as an array key.
     237         *
     238         * @ticket 64538
     239         */
     240        public function test_wp_normalize_path_with_stringable_object() {
     241                $file_info = new SplFileInfo( '/var/www/html\\test' );
     242
     243                $this->assertSame( '/var/www/html/test', wp_normalize_path( $file_info ) );
     244        }
     245
     246        /**
     247         * Tests that wp_normalize_path() returns consistent results on repeated calls.
     248         *
     249         * The function uses a static cache, so this verifies cache behavior.
     250         *
     251         * @ticket 64538
     252         */
     253        public function test_wp_normalize_path_returns_consistent_results() {
     254                $path = 'C:\\www\\path\\';
     255
     256                $first_call  = wp_normalize_path( $path );
     257                $second_call = wp_normalize_path( $path );
     258                $third_call  = wp_normalize_path( $path );
     259
     260                $this->assertSame( $first_call, $second_call, 'Second call should return same result as first.' );
     261                $this->assertSame( $second_call, $third_call, 'Third call should return same result as second.' );
     262                $this->assertSame( 'C:/www/path/', $first_call, 'Normalized path should match expected value.' );
     263        }
     264
     265        /**
     266         * Tests that wp_normalize_path() static cache stores results.
     267         *
     268         * @ticket 64538
     269         */
     270        public function test_wp_normalize_path_static_cache() {
     271                $path     = '/var/www/cache-test\\subdir\\';
     272                $expected = '/var/www/cache-test/subdir/';
     273
     274                $result = wp_normalize_path( $path );
     275                $this->assertSame( $expected, $result );
     276
     277                $reflection  = new ReflectionFunction( 'wp_normalize_path' );
     278                $static_vars = $reflection->getStaticVariables();
     279
     280                $this->assertArrayHasKey( 'cache', $static_vars, 'Static cache array should exist.' );
     281                $this->assertArrayHasKey( $path, $static_vars['cache'], 'Cache should contain the normalized path.' );
     282                $this->assertSame( $expected, $static_vars['cache'][ $path ], 'Cached value should match the expected normalized path.' );
    226283        }
    227284
Note: See TracChangeset for help on using the changeset viewer.