Make WordPress Core

Changeset 38571


Ignore:
Timestamp:
09/08/2016 03:54:13 AM (8 years ago)
Author:
pento
Message:

Hooks: Add the new class WP_Hook, and modify hook handling to make use of it.

Filters and actions have been the basis of WordPress' plugin functionality since time immemorial, they've always been a reliable method for acting upon the current state of WordPress, and will continue to be so.

Over the years, however, edge cases have cropped up. Particularly when it comes to recursively executing hooks, or a hook adding and removing itself, the existing implementation struggled to keep up with more complex use cases.

And so, we introduce WP_Hook. By changing $wp_filter from an array of arrays, to an array of objects, we reduce the complexity of the hook handling code, as the processing code (see ::apply_filters()) only needs to be aware of itself, rather than the state of all hooks. At the same time, we're able te handle more complex use cases, as the object can more easily keep track of its own state than an array ever could.

Props jbrinley for the original architecture and design of this patch.
Props SergeyBiryukov, cheeserolls, Denis-de-Bernardy, leewillis77, wonderboymusic, nacin, jorbin, DrewAPicture, ocean90, dougwollison, khag7, pento, noplanman and aaroncampbell for their testing, suggestions, contributions, patch maintenance, cajoling and patience as we got through this.
Fixes #17817.

Location:
trunk
Files:
12 added
7 edited

Legend:

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

    r38282 r38571  
    2323
    2424// Initialize the filter globals.
    25 global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
    26 
    27 if ( ! isset( $wp_filter ) )
     25require( ABSPATH . WPINC . '/class-wp-hook.php' );
     26
     27/** @var WP_Hook[] $wp_filter */
     28global $wp_filter, $wp_actions, $wp_current_filter;
     29
     30if ( $wp_filter ) {
     31    $wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter );
     32} else {
    2833    $wp_filter = array();
     34}
    2935
    3036if ( ! isset( $wp_actions ) )
    3137    $wp_actions = array();
    32 
    33 if ( ! isset( $merged_filters ) )
    34     $merged_filters = array();
    3538
    3639if ( ! isset( $wp_current_filter ) )
     
    9093 *
    9194 * @global array $wp_filter      A multidimensional array of all hooks and the callbacks hooked to them.
    92  * @global array $merged_filters Tracks the tags that need to be merged for later. If the hook is added,
    93  *                               it doesn't need to run through that process.
    9495 *
    9596 * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
     
    104105 */
    105106function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    106     global $wp_filter, $merged_filters;
    107 
    108     $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
    109     $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
    110     unset( $merged_filters[ $tag ] );
     107    global $wp_filter;
     108    if ( ! isset( $wp_filter[ $tag ] ) ) {
     109        $wp_filter[ $tag ] = new WP_Hook();
     110    }
     111    $wp_filter[ $tag ]->add_filter( $tag, $function_to_add, $priority, $accepted_args );
    111112    return true;
    112113}
     
    129130 */
    130131function has_filter($tag, $function_to_check = false) {
    131     // Don't reset the internal array pointer
    132     $wp_filter = $GLOBALS['wp_filter'];
    133 
    134     $has = ! empty( $wp_filter[ $tag ] );
    135 
    136     // Make sure at least one priority has a filter callback
    137     if ( $has ) {
    138         $exists = false;
    139         foreach ( $wp_filter[ $tag ] as $callbacks ) {
    140             if ( ! empty( $callbacks ) ) {
    141                 $exists = true;
    142                 break;
    143             }
    144         }
    145 
    146         if ( ! $exists ) {
    147             $has = false;
    148         }
    149     }
    150 
    151     if ( false === $function_to_check || false === $has )
    152         return $has;
    153 
    154     if ( !$idx = _wp_filter_build_unique_id($tag, $function_to_check, false) )
     132    global $wp_filter;
     133
     134    if ( ! isset( $wp_filter[ $tag ] ) ) {
    155135        return false;
    156 
    157     foreach ( (array) array_keys($wp_filter[$tag]) as $priority ) {
    158         if ( isset($wp_filter[$tag][$priority][$idx]) )
    159             return $priority;
    160     }
    161 
    162     return false;
     136    }
     137
     138    return $wp_filter[ $tag ]->has_filter( $tag, $function_to_check );
    163139}
    164140
     
    191167 *
    192168 * @global array $wp_filter         Stores all of the filters.
    193  * @global array $merged_filters    Merges the filter hooks using this function.
    194169 * @global array $wp_current_filter Stores the list of current filters with the current one last.
    195170 *
     
    200175 */
    201176function apply_filters( $tag, $value ) {
    202     global $wp_filter, $merged_filters, $wp_current_filter;
     177    global $wp_filter, $wp_current_filter;
    203178
    204179    $args = array();
     
    220195        $wp_current_filter[] = $tag;
    221196
    222     // Sort.
    223     if ( !isset( $merged_filters[ $tag ] ) ) {
    224         ksort($wp_filter[$tag]);
    225         $merged_filters[ $tag ] = true;
    226     }
    227 
    228     reset( $wp_filter[ $tag ] );
    229 
    230197    if ( empty($args) )
    231198        $args = func_get_args();
    232199
    233     do {
    234         foreach ( (array) current($wp_filter[$tag]) as $the_ )
    235             if ( !is_null($the_['function']) ){
    236                 $args[1] = $value;
    237                 $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
    238             }
    239 
    240     } while ( next($wp_filter[$tag]) !== false );
     200    // don't pass the tag name to WP_Hook
     201    array_shift( $args );
     202
     203    $filtered = $wp_filter[ $tag ]->apply_filters( $value, $args );
    241204
    242205    array_pop( $wp_current_filter );
    243206
    244     return $value;
     207    return $filtered;
    245208}
    246209
     
    254217 *
    255218 * @global array $wp_filter         Stores all of the filters
    256  * @global array $merged_filters    Merges the filter hooks using this function.
    257219 * @global array $wp_current_filter Stores the list of current filters with the current one last
    258220 *
     
    262224 */
    263225function apply_filters_ref_array($tag, $args) {
    264     global $wp_filter, $merged_filters, $wp_current_filter;
     226    global $wp_filter, $wp_current_filter;
    265227
    266228    // Do 'all' actions first
     
    280242        $wp_current_filter[] = $tag;
    281243
    282     // Sort
    283     if ( !isset( $merged_filters[ $tag ] ) ) {
    284         ksort($wp_filter[$tag]);
    285         $merged_filters[ $tag ] = true;
    286     }
    287 
    288     reset( $wp_filter[ $tag ] );
    289 
    290     do {
    291         foreach ( (array) current($wp_filter[$tag]) as $the_ )
    292             if ( !is_null($the_['function']) )
    293                 $args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
    294 
    295     } while ( next($wp_filter[$tag]) !== false );
     244    $filtered = $wp_filter[ $tag ]->apply_filters( $args[0], $args );
    296245
    297246    array_pop( $wp_current_filter );
    298247
    299     return $args[0];
     248    return $filtered;
    300249}
    301250
     
    314263 *
    315264 * @global array $wp_filter         Stores all of the filters
    316  * @global array $merged_filters    Merges the filter hooks using this function.
    317265 *
    318266 * @param string   $tag                The filter hook to which the function to be removed is hooked.
     
    322270 */
    323271function remove_filter( $tag, $function_to_remove, $priority = 10 ) {
    324     $function_to_remove = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );
    325 
    326     $r = isset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] );
    327 
    328     if ( true === $r ) {
    329         unset( $GLOBALS['wp_filter'][ $tag ][ $priority ][ $function_to_remove ] );
    330         if ( empty( $GLOBALS['wp_filter'][ $tag ][ $priority ] ) ) {
    331             unset( $GLOBALS['wp_filter'][ $tag ][ $priority ] );
     272    global $wp_filter;
     273
     274    $r = false;
     275    if ( isset( $wp_filter[ $tag ] ) ) {
     276        $r = $wp_filter[ $tag ]->remove_filter( $tag, $function_to_remove, $priority );
     277        if ( ! $wp_filter[ $tag ]->callbacks ) {
     278            unset( $wp_filter[ $tag ] );
    332279        }
    333         if ( empty( $GLOBALS['wp_filter'][ $tag ] ) ) {
    334             $GLOBALS['wp_filter'][ $tag ] = array();
    335         }
    336         unset( $GLOBALS['merged_filters'][ $tag ] );
    337280    }
    338281
     
    345288 * @since 2.7.0
    346289 *
    347  * @global array $wp_filter         Stores all of the filters
    348  * @global array $merged_filters    Merges the filter hooks using this function.
     290 * @global array $wp_filter  Stores all of the filters
    349291 *
    350292 * @param string   $tag      The filter to remove hooks from.
     
    353295 */
    354296function remove_all_filters( $tag, $priority = false ) {
    355     global $wp_filter, $merged_filters;
     297    global $wp_filter;
    356298
    357299    if ( isset( $wp_filter[ $tag ]) ) {
    358         if ( false === $priority ) {
    359             $wp_filter[ $tag ] = array();
    360         } elseif ( isset( $wp_filter[ $tag ][ $priority ] ) ) {
    361             $wp_filter[ $tag ][ $priority ] = array();
     300        $wp_filter[ $tag ]->remove_all_filters( $priority );
     301        if ( ! $wp_filter[ $tag ]->has_filters() ) {
     302            unset( $wp_filter[ $tag ] );
    362303        }
    363304    }
    364 
    365     unset( $merged_filters[ $tag ] );
    366305
    367306    return true;
     
    474413 * @global array $wp_filter         Stores all of the filters
    475414 * @global array $wp_actions        Increments the amount of times action was triggered.
    476  * @global array $merged_filters    Merges the filter hooks using this function.
    477415 * @global array $wp_current_filter Stores the list of current filters with the current one last
    478416 *
     
    482420 */
    483421function do_action($tag, $arg = '') {
    484     global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
     422    global $wp_filter, $wp_actions, $wp_current_filter;
    485423
    486424    if ( ! isset($wp_actions[$tag]) )
     
    513451        $args[] = func_get_arg($a);
    514452
    515     // Sort
    516     if ( !isset( $merged_filters[ $tag ] ) ) {
    517         ksort($wp_filter[$tag]);
    518         $merged_filters[ $tag ] = true;
    519     }
    520 
    521     reset( $wp_filter[ $tag ] );
    522 
    523     do {
    524         foreach ( (array) current($wp_filter[$tag]) as $the_ )
    525             if ( !is_null($the_['function']) )
    526                 call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
    527 
    528     } while ( next($wp_filter[$tag]) !== false );
     453    $wp_filter[ $tag ]->do_action( $args );
    529454
    530455    array_pop($wp_current_filter);
     
    559484 * @global array $wp_filter         Stores all of the filters
    560485 * @global array $wp_actions        Increments the amount of times action was triggered.
    561  * @global array $merged_filters    Merges the filter hooks using this function.
    562486 * @global array $wp_current_filter Stores the list of current filters with the current one last
    563487 *
     
    566490 */
    567491function do_action_ref_array($tag, $args) {
    568     global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
     492    global $wp_filter, $wp_actions, $wp_current_filter;
    569493
    570494    if ( ! isset($wp_actions[$tag]) )
     
    589513        $wp_current_filter[] = $tag;
    590514
    591     // Sort
    592     if ( !isset( $merged_filters[ $tag ] ) ) {
    593         ksort($wp_filter[$tag]);
    594         $merged_filters[ $tag ] = true;
    595     }
    596 
    597     reset( $wp_filter[ $tag ] );
    598 
    599     do {
    600         foreach ( (array) current($wp_filter[$tag]) as $the_ )
    601             if ( !is_null($the_['function']) )
    602                 call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
    603 
    604     } while ( next($wp_filter[$tag]) !== false );
     515    $wp_filter[ $tag ]->do_action( $args );
    605516
    606517    array_pop($wp_current_filter);
     
    924835    global $wp_filter;
    925836
    926     reset( $wp_filter['all'] );
    927     do {
    928         foreach ( (array) current($wp_filter['all']) as $the_ )
    929             if ( !is_null($the_['function']) )
    930                 call_user_func_array($the_['function'], $args);
    931 
    932     } while ( next($wp_filter['all']) !== false );
     837    $wp_filter['all']->do_all_hook( $args );
    933838}
    934839
  • trunk/tests/phpunit/includes/functions.php

    r38445 r38571  
    2222// For adding hooks before loading WP
    2323function tests_add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    24     global $wp_filter, $merged_filters;
     24    global $wp_filter;
    2525
    2626    $idx = _test_filter_build_unique_id($tag, $function_to_add, $priority);
    2727    $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
    28     unset( $merged_filters[ $tag ] );
    2928    return true;
    3029}
    3130
    3231function _test_filter_build_unique_id($tag, $function, $priority) {
    33     global $wp_filter;
    34     static $filter_id_count = 0;
    35 
    3632    if ( is_string($function) )
    3733        return $function;
  • trunk/tests/phpunit/includes/testcase.php

    r38405 r38571  
    224224     */
    225225    protected function _backup_hooks() {
    226         $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     226        $globals = array( 'wp_actions', 'wp_current_filter' );
    227227        foreach ( $globals as $key ) {
    228228            self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
     229        }
     230        self::$hooks_saved['wp_filter'] = array();
     231        foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) {
     232            self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object;
    229233        }
    230234    }
     
    241245     */
    242246    protected function _restore_hooks() {
    243         $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     247        $globals = array( 'wp_actions', 'wp_current_filter' );
    244248        foreach ( $globals as $key ) {
    245249            if ( isset( self::$hooks_saved[ $key ] ) ) {
    246250                $GLOBALS[ $key ] = self::$hooks_saved[ $key ];
     251            }
     252        }
     253        if ( isset( self::$hooks_saved['wp_filter'] ) ) {
     254            $GLOBALS['wp_filter'] = array();
     255            foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) {
     256                $GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object;
    247257            }
    248258        }
  • trunk/tests/phpunit/tests/actions.php

    r38382 r38571  
    113113        $argsvar2 = $a2->get_args();
    114114        $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) );
     115    }
     116
     117    /**
     118     * Test that multiple callbacks receive the correct number of args even when the number
     119     * is less than, or greater than previous hooks.
     120     *
     121     * @see https://core.trac.wordpress.org/ticket/17817#comment:72
     122     * @ticket 17817
     123     */
     124    function test_action_args_3() {
     125        $a1 = new MockAction();
     126        $a2 = new MockAction();
     127        $a3 = new MockAction();
     128        $tag = rand_str();
     129        $val1 = rand_str();
     130        $val2 = rand_str();
     131
     132        // a1 accepts two arguments, a2 doesn't, a3 accepts two arguments
     133        add_action( $tag, array( &$a1, 'action' ), 10, 2 );
     134        add_action( $tag, array( &$a2, 'action' ) );
     135        add_action( $tag, array( &$a3, 'action' ), 10, 2 );
     136        // call the action with two arguments
     137        do_action( $tag, $val1, $val2 );
     138
     139        $call_count = $a1->get_call_count();
     140        // a1 should be called with both args
     141        $this->assertEquals( 1, $call_count );
     142        $argsvar1 = $a1->get_args();
     143        $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar1 ) );
     144
     145        // a2 should be called with one only
     146        $this->assertEquals( 1, $a2->get_call_count() );
     147        $argsvar2 = $a2->get_args();
     148        $this->assertEquals( array( $val1 ), array_pop( $argsvar2 ) );
     149
     150        // a3 should be called with both args
     151        $this->assertEquals( 1, $a3->get_call_count() );
     152        $argsvar3 = $a3->get_args();
     153        $this->assertEquals( array( $val1, $val2 ), array_pop( $argsvar3 ) );
    115154    }
    116155
     
    259298
    260299    /**
     300     * @ticket 17817
     301     */
     302    function test_action_recursion() {
     303        $tag = rand_str();
     304        $a = new MockAction();
     305        $b = new MockAction();
     306
     307        add_action( $tag, array( $a, 'action' ), 11, 1 );
     308        add_action( $tag, array( $b, 'action' ), 13, 1 );
     309        add_action( $tag, array( $this, 'action_that_causes_recursion' ), 12, 1 );
     310        do_action( $tag, $tag );
     311
     312        $this->assertEquals( 2, $a->get_call_count(), 'recursive actions should call all callbacks with earlier priority' );
     313        $this->assertEquals( 2, $b->get_call_count(), 'recursive actions should call callbacks with later priority' );
     314    }
     315
     316    function action_that_causes_recursion( $tag ) {
     317        static $recursing = false;
     318        if ( ! $recursing ) {
     319            $recursing = true;
     320            do_action( $tag, $tag );
     321        }
     322        $recursing = false;
     323    }
     324
     325    /**
     326     * @ticket 9968
     327     * @ticket 17817
     328     */
     329    function test_action_callback_manipulation_while_running() {
     330        $tag = rand_str();
     331        $a = new MockAction();
     332        $b = new MockAction();
     333        $c = new MockAction();
     334        $d = new MockAction();
     335        $e = new MockAction();
     336
     337        add_action( $tag, array( $a, 'action' ), 11, 2 );
     338        add_action( $tag, array( $this, 'action_that_manipulates_a_running_hook' ), 12, 2 );
     339        add_action( $tag, array( $b, 'action' ), 12, 2 );
     340
     341        do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) );
     342        do_action( $tag, $tag, array( $a, $b, $c, $d, $e ) );
     343
     344        $this->assertEquals( 2, $a->get_call_count(), 'callbacks should run unless otherwise instructed' );
     345        $this->assertEquals( 1, $b->get_call_count(), 'callback removed by same priority callback should still get called' );
     346        $this->assertEquals( 1, $c->get_call_count(), 'callback added by same priority callback should not get called' );
     347        $this->assertEquals( 2, $d->get_call_count(), 'callback added by earlier priority callback should get called' );
     348        $this->assertEquals( 1, $e->get_call_count(), 'callback added by later priority callback should not get called' );
     349    }
     350
     351    function action_that_manipulates_a_running_hook( $tag, $mocks ) {
     352        remove_action( $tag, array( $mocks[ 1 ], 'action' ), 12, 2 );
     353        add_action( $tag, array( $mocks[ 2 ], 'action' ), 12, 2 );
     354        add_action( $tag, array( $mocks[ 3 ], 'action' ), 13, 2 );
     355        add_action( $tag, array( $mocks[ 4 ], 'action' ), 10, 2 );
     356    }
     357
     358    /**
     359     * @ticket 17817
     360     *
     361     * This specificaly addresses the concern raised at
     362     * https://core.trac.wordpress.org/ticket/17817#comment:52
     363     */
     364    function test_remove_anonymous_callback() {
     365        $tag = rand_str();
     366        $a = new MockAction();
     367        add_action( $tag, array( $a, 'action' ), 12, 1 );
     368        $this->assertTrue( has_action( $tag ) );
     369
     370        $hook = $GLOBALS['wp_filter'][ $tag ];
     371
     372        // From http://wordpress.stackexchange.com/a/57088/6445
     373        foreach ( $hook as $priority => $filter ) {
     374            foreach ( $filter as $identifier => $function ) {
     375                if ( is_array( $function )
     376                    && is_a( $function['function'][ 0 ], 'MockAction' )
     377                    && 'action' === $function['function'][ 1 ]
     378                ) {
     379                    remove_filter(
     380                        $tag,
     381                        array( $function['function'][ 0 ], 'action' ),
     382                        $priority
     383                    );
     384                }
     385            }
     386        }
     387
     388        $this->assertFalse( has_action( $tag ) );
     389    }
     390
     391
     392    /**
     393     * Test the ArrayAccess methods of WP_Hook
     394     *
     395     * @ticket 17817
     396     */
     397    function test_array_access_of_wp_filter_global() {
     398        global $wp_filter;
     399        $tag = rand_str();
     400
     401        add_action( $tag, '__return_null', 11, 1 );
     402
     403        $this->assertTrue( isset( $wp_filter[ $tag ][ 11 ] ) );
     404        $this->assertArrayHasKey( '__return_null', $wp_filter[ $tag ][ 11 ] );
     405
     406        unset( $wp_filter[ $tag ][ 11 ] );
     407        $this->assertFalse( has_action( $tag, '__return_null' ) );
     408
     409        $wp_filter[ $tag ][ 11 ] = array( '__return_null' => array( 'function' => '__return_null', 'accepted_args' => 1 ) );
     410        $this->assertEquals( 11, has_action( $tag, '__return_null' ) );
     411    }
     412
     413    /**
    261414     * Make sure current_action() behaves as current_filter()
    262415     *
  • trunk/tests/phpunit/tests/filters.php

    r37911 r38571  
    297297
    298298    /**
    299      * @ticket 29070
    300      */
    301      function test_has_filter_doesnt_reset_wp_filter() {
    302         add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 1 );
    303         add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 2 );
    304         add_action( 'action_test_has_filter_doesnt_reset_wp_filter', '__return_null', 3 );
    305         add_action( 'action_test_has_filter_doesnt_reset_wp_filter', array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ), 4 );
    306 
    307         do_action( 'action_test_has_filter_doesnt_reset_wp_filter' );
    308      }
    309      function _action_test_has_filter_doesnt_reset_wp_filter() {
    310         global $wp_filter;
    311 
    312         has_action( 'action_test_has_filter_doesnt_reset_wp_filter', '_function_that_doesnt_exist' );
    313 
    314         $filters = current( $wp_filter['action_test_has_filter_doesnt_reset_wp_filter'] );
    315         $the_ = current( $filters );
    316         $this->assertEquals( $the_['function'], array( $this, '_action_test_has_filter_doesnt_reset_wp_filter' ) );
    317      }
    318 
    319     /**
    320299     * @ticket 10441
    321300     * @expectedDeprecated tests_apply_filters_deprecated
  • trunk/tests/phpunit/tests/post/types.php

    r37890 r38571  
    447447        $this->assertSame( 1, count( $wp_filter['future_foo'] ) );
    448448        $this->assertTrue( unregister_post_type( 'foo' ) );
    449         $this->assertSame( array(), $wp_filter['future_foo'] );
     449        $this->assertArrayNotHasKey( 'future_foo', $wp_filter );
    450450    }
    451451
     
    463463        $this->assertSame( 1, count( $wp_filter['add_meta_boxes_foo'] ) );
    464464        $this->assertTrue( unregister_post_type( 'foo' ) );
    465         $this->assertSame( array(), $wp_filter['add_meta_boxes_foo'] );
     465        $this->assertArrayNotHasKey( 'add_meta_boxes_foo', $wp_filter );
    466466    }
    467467
  • trunk/tests/phpunit/tests/taxonomy.php

    r38078 r38571  
    698698        $this->assertSame( 1, count( $wp_filter['wp_ajax_add-foo'] ) );
    699699        $this->assertTrue( unregister_taxonomy( 'foo' ) );
    700         $this->assertSame( array(), $wp_filter['wp_ajax_add-foo'] );
     700        $this->assertArrayNotHasKey( 'wp_ajax_add-foo', $wp_filter );
    701701    }
    702702
Note: See TracChangeset for help on using the changeset viewer.