Make WordPress Core

Opened 3 years ago

Last modified 7 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:


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 3 years ago.
21267.diff (5.1 KB) - added by wonderboymusic 2 years ago.
21267.2.diff (7.4 KB) - added by wonderboymusic 22 months ago.

Download all attachments as: .zip

Change History (19)

comment:1 @scribu3 years ago

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

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

comment:2 @scribu3 years ago

  • Description modified (diff)

comment:3 @wonderboymusic3 years ago

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

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.


comment:4 @wonderboymusic3 years 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, and I also found a bug in add_filter() as pertains to call_user_func( array( 'MyClass::static_method' ) ) - if $arr[1] is empty, a Warning is thrown and the callback is invalid

Version 0, edited 3 years ago by wonderboymusic (next)

comment:5 @wonderboymusic3 years ago

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

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

$my_class_1a = new MyClass1;
$my_class_1b = new MyClass1;

add_filter( 'list_terms_exclusions', 'a_function' );
add_filter( 'list_terms_exclusions', array( $my_class_1a, 'filter' ) );
add_filter( 'list_terms_exclusions', array( $my_class_1b, 'filter' ) );
add_filter( 'list_terms_exclusions', array( 'MyClass2', 'filter' ) );
add_filter( 'list_terms_exclusions', 'MyClass2::filter' );
add_filter( 'list_terms_exclusions', function ( $filter ) { error_log( 'called' ); return $filter; } );

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

echo wp_filter_callbacks_hash( 'list_terms_exclusions' );

get_terms( 'link_category' );
Last edited 3 years ago by wonderboymusic (previous) (diff)

comment:6 @wonderboymusic3 years 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 3 years ago by wonderboymusic (previous) (diff)

comment:7 @wonderboymusic3 years ago

Updated the patch and edited my comments where appropriate

comment:8 @nacin3 years ago


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 @nacin3 years ago

  • Milestone changed from 3.5 to Future Release

comment:10 @wonderboymusic3 years 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 @scribu3 years 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 @scribu3 years ago

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

@wonderboymusic2 years ago

comment:13 @wonderboymusic2 years 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

@wonderboymusic22 months ago

comment:14 @wonderboymusic22 months ago

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

Too big for right now

comment:15 @wonderboymusic21 months ago

  • Keywords 3.9-early added
  • Milestone changed from 3.8 to Future Release

comment:16 @wonderboymusic7 months ago

#30960 was marked as a duplicate.

Note: See TracTickets for help on using tickets.