Make WordPress Core

Changeset 61857


Ignore:
Timestamp:
03/06/2026 08:04:31 PM (26 hours 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.