Make WordPress Core

Changeset 38571

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    );
     110   
     111    );
    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 $;
    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 $;
    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( '_filter' );
    227227        foreach ( $globals as $key ) {
    228228            self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
     229
     230
     231
     232
    229233        }
    230234    }
     
    241245     */
    242246    protected function _restore_hooks() {
    243         $globals = array( 'merged_filters', 'wp_actions', 'wp_current_filter', 'wp_filter' );
     247        $globals = array( '_filter' );
    244248        foreach ( $globals as $key ) {
    245249            if ( isset( self::$hooks_saved[ $key ] ) ) {
    246250                $GLOBALS[ $key ] = self::$hooks_saved[ $key ];
     251
     252
     253
     254
     255
     256
    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
     119
     120
     121
     122
     123
     124
     125
     126
     127
     128
     129
     130
     131
     132
     133
     134
     135
     136
     137
     138
     139
     140
     141
     142
     143
     144
     145
     146
     147
     148
     149
     150
     151
     152
     153
    115154    }
    116155
     
    259298
    260299    /**
     300
     301
     302
     303
     304
     305
     306
     307
     308
     309
     310
     311
     312
     313
     314
     315
     316
     317
     318
     319
     320
     321
     322
     323
     324
     325
     326
     327
     328
     329
     330
     331
     332
     333
     334
     335
     336
     337
     338
     339
     340
     341
     342
     343
     344
     345
     346
     347
     348
     349
     350
     351
     352
     353
     354
     355
     356
     357
     358
     359
     360
     361
     362
     363
     364
     365
     366
     367
     368
     369
     370
     371
     372
     373
     374
     375
     376
     377
     378
     379
     380
     381
     382
     383
     384
     385
     386
     387
     388
     389
     390
     391
     392
     393
     394
     395
     396
     397
     398
     399
     400
     401
     402
     403
     404
     405
     406
     407
     408
     409
     410
     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->assert );
    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->assert );
    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->assert );
    701701    }
    702702
Note: See TracChangeset for help on using the changeset viewer.