WordPress.org

Make WordPress Core

Opened 21 months ago

Last modified 5 months ago

#21267 new defect (bug)

Kill the serialization of $wp_filter in get_terms()

Reported by: nacin Owned by:
Milestone: Future Release Priority: normal
Severity: normal Version:
Component: Cache API Keywords: has-patch 3.9-early
Focuses: Cc:

Description (last modified by scribu)

We use this as part of a persistent cache key:

serialize($GLOBALS['wp_filter']['list_terms_exclusions'])

See [8225].

This is not good for two reasons. First, well, it's a not fun to reach into our internal API like that. But worse, it's broken whenever an object method is used, because spl_object_hash() will be unique to that object, thus rendering the cache invalidated on the next pageload.

As an aside, we should probably have _wp_filter_build_unique_id() create a delimiter when dealing with a static class method — $function[0].$function[1] can conflict with a legitimate function name.

I'm not sure how this should be fixed.

Attachments (3)

filter-callback-keys.diff (5.2 KB) - added by wonderboymusic 21 months ago.
21267.diff (5.1 KB) - added by wonderboymusic 9 months ago.
21267.2.diff (7.4 KB) - added by wonderboymusic 7 months ago.

Download all attachments as: .zip

Change History (18)

comment:1 scribu21 months ago

  • Description modified (diff)
  • Keywords needs-patch added

That looks incredibly hacky. Besides, closures are not serializable (you get a fatal error).

comment:2 scribu21 months ago

  • Description modified (diff)

comment:3 wonderboymusic21 months ago

Here's how you can serialize a Closure instance, and by "serialize" I really mean "create a unique identifier":

ob_start();
echo new ReflectionFunction( function ( $var1, $var2 ) { 
	return $var1 + $var2;
} );
$source = ob_get_clean();
echo md5( $source );

// makes md5 of this:
Closure [ <user> function {closure} ] {
  @@ /Users/scott/Sites/wordpress-core/test.php 64 - 66

  - Parameters [2] {
    Parameter #0 [ <required> $var1 ]
    Parameter #1 [ <required> $var2 ]
  }
}

This works because ReflectionFunction implements the toString magic method and is better than spl_object_hash which will refresh the hash on every request.

http://www.php.net/manual/en/reflectionfunction.tostring.php

comment:4 wonderboymusic21 months ago

Since PHP 5.2 (the required version for WP is 5.2.4), spl_object_hash exists, which means the else statement and function_exists() check are completely useless in _wp_filter_build_unique_id()

Also, since all of that code is ditched and strings are returned for functions, there is nothing unique about the ID created, it just returns a string representation of a_function, MyClass::method, or whatever represents a hash for a closure

I am working up a patch

Last edited 21 months ago by wonderboymusic (previous) (diff)

comment:5 wonderboymusic21 months ago

FYI, here's a good way to debug this in functions.php or whatever:

function a_function( $filter ) { return $filter; }
class MyClass1 { function filter( $filter ) { return $filter; } }
class MyClass2 { static function filter( $filter ) { return $filter; } }

$my_class_1 = new MyClass1;

add_filter( 'list_terms_exclusions', 'a_function' );
add_filter( 'list_terms_exclusions', array( $my_class_1, 'filter' ) );
add_filter( 'list_terms_exclusions', array( 'MyClass2', 'filter' ) );
add_filter( 'list_terms_exclusions', array( 'MyClass2::filter' ) );
add_filter( 'list_terms_exclusions', array( 'MyClass2::filter', '' ) );
add_filter( 'list_terms_exclusions', function ( $filter ) { return $filter; } );

print_r( $GLOBALS['wp_filter']['list_terms_exclusions'] );

get_terms( 'link_category' );
exit();
Version 0, edited 21 months ago by wonderboymusic (next)

comment:6 wonderboymusic21 months ago

  • Keywords has-patch added; needs-patch removed

Attached the patch:

Removed the unreachable code
Created a function to hash a closure: _wp_build_unique_closure_id( $function )
Created a function to hash a list of callbacks by key: wp_filter_callbacks_hash( $tag )

_wp_filter_build_unique_id() now generally returns the string name of a function or class ('MyClass::method') which is what this function was doing anyways - it returns an md5 string representing a closure where applicable. For class instances, it will prepend spl_object_hash( $function[0] )~ which will be removed when the wp_filter_callbacks_hash is generated - allows persistence between requests

get_terms() now internally calls:

$filter_key = wp_filter_callbacks_hash( 'list_terms_exclusions' );

It previously called:

$filter_key = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : '';
Last edited 21 months ago by wonderboymusic (previous) (diff)

comment:7 wonderboymusic21 months ago

Updated the patch and edited my comments where appropriate

comment:8 nacin20 months ago

Clever.

Since this is the only time we've had to hash the filters being used, I wonder if this is a bit overkill.

comment:9 nacin18 months ago

  • Milestone changed from 3.5 to Future Release

comment:10 wonderboymusic15 months ago

@nacin - do we still care about this, or should we ditch the filter and throw a doing_it_wrong or something? I mean, this could be our chance to have an md5'd output buffer of ReflectionFunction::__toString in Core, which everyone's been asking for

comment:11 scribu15 months ago

this could be our chance to have an md5'd output buffer of ReflectionFunction::__toString in Core, which everyone's been asking for

Everybode? Really? :P

or should we ditch the filter and throw a doing_it_wrong or something?

Instead of basing the cache key on the list of callbacks attached to that filter, why not just use the term IDs currently returned by the filter?

comment:12 scribu15 months ago

Or, ugh, the SQL string returned by the filter rather.

wonderboymusic9 months ago

comment:13 wonderboymusic9 months ago

  • Keywords needs-unit-tests needs-testing added
  • Milestone changed from Future Release to 3.7

Refreshed against trunk. The bug is still legitimate - patch allows us to update the object hashing strategy

wonderboymusic7 months ago

comment:14 wonderboymusic7 months ago

  • Keywords needs-unit-tests needs-testing removed
  • Milestone changed from 3.7 to 3.8

Too big for right now

comment:15 wonderboymusic5 months ago

  • Keywords 3.9-early added
  • Milestone changed from 3.8 to Future Release
Note: See TracTickets for help on using tickets.