Make WordPress Core


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.

File:
1 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
Note: See TracChangeset for help on using the changeset viewer.