WordPress.org

Make WordPress Core

Opened 6 months ago

Closed 6 months ago

Last modified 6 months ago

#50245 closed enhancement (invalid)

extend functionality to stop further actions on hook on a priority range

Reported by: Alignak Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Cache API Keywords:
Focuses: performance Cc:

Description

I would like to be possible to stop further actions on a hook or filter, based on a priority range (or prevent further processing by preventing other actions from being added on upper priorities).

For example,

I would like to hook into the_content on priority -10 and prevent any other action / filter from being applied until priority 10.

This would allow me for example, to cache a hook.

Real case scenario:

A site uses a theme builder that generates thousands of shortcodes. For every pageview that cannot be cached, it has to run the do_shortcode sometimes thousands of times.

If I were to implement a transient on the_content using the max priority possible, I could then use the lowest priority possible next to check if a transient exists, and "prevent further processing on the_content" by returning the content that I cached earlier.

Currently, all I could do would be to save the transient as a global, leave the_content empty (which doesn't avoid any other filters on the empty string), and then print it at the last priority.

Or what makes sense, create a the_content alternative function that does this (what I am doing in a couple of projects where the_content takes almost 3 seconds to generate code from the Divi theme).

So as a developer, a way to skip any further processing (either on a priority range or completely), would make performance tweaks much easier, instead of having to write new functions just for that.

I am aware of the remove_all_filters action, which is great, but unfortunately, it doesn't work when called from inside itself.

I cannot do for example:

<?php
add_filter( 'the_content', 'my_filter_start', PHP_INT_MIN);
function my_filter_start( $content ) {
  
        $prefix_tkey = md5($content);
        global $prefix_tkey;
        $value = get_transient($prefix_tkey);
        if ( false !== $value ) {
        
                # unfortunately, this doesn't work (feature request)
                # and even if it did, I would like to limit it up to a certain priority level, or whitelist certain actions
                remove_all_filters('the_content');
                                
                # return transient code, which will be cached on PHP_INT_MAX priority 
                return $value;
                
        }
    
    return $content;
}

add_filter( 'the_content', 'my_filter_end', PHP_INT_MAX);
function my_filter_end( $content ) {
        
        global $prefix_tkey;
        
        # cache html if we are here
        if(isset($prefix_tkey) && strlen($prefix_tkey) == 32) {
                set_transient( $prefix_tkey, $content, 24 * HOUR_IN_SECONDS );
        }
    
    return $content;
}

I'm filling this under the cache api, because the main purpose for this is cache.
I think it could be implemented on wordpress side, with an option to cache the_content, wp_head, and maybe menus.

Change History (6)

#1 @apedog
6 months ago

This is a support question.
The functionality you want already exists.
You can do that with code like this.
Hook your function really early (PHP_INT_MIN/PHP_INT_MAX is a bit excessive and might not even be consistent across different systems. You should avoid using that.)
And have a look at wp-includes/wp-class-hook.php for more methods that might be useful.

<?php
add_action( 'your_hook', 'custom_filter_remover', -10 );
function custom_filter_remover(){
        global $wp_filter;
        $hook_name = 'your_hook';
        $actual_hook = $wp_filter[$hook_name];

        foreach ( $actual_hook->callbacks as $priority => $callbacks ){
                foreach ($callbacks as $the_){

                        if ( $the_['function'] == 'function_i_dont_want_to_remove' )
                                continue;
                        if ($priority >= 10 || $priority <= -10 )
                                continue;

                        remove_filter( $hook_name, $the_['function'], $priority );
                }
        }
}

#2 @Alignak
6 months ago

@apedog many thanks for the example, I wasn't aware that this was possible and it will greatly improve some performance tests that I am doing right now.

I apologize for abusing your kindness... but do you know if it's possible to make anonymous functions used by themes, somehow "static" without editing the original code?

For example, there are some theme authors (such as the Divi theme and their editor) that make use of anonymous functions to inline css code and generate stuff in the_content. This causes their css code to change on every pageview (usually a prefix for a class or id attribute). I was wondering if it's possible to prevent that randomness somehow.

The code you posted will likely be useful to maybe cache some of that stuff, but I was wondering if there is any other way.

I will inspect the wp-includes/wp-class-hook.php file, thank you very much.

#3 @Alignak
6 months ago

  • Resolution set to invalid
  • Status changed from assigned to closed

#4 follow-up: @apedog
6 months ago

  • Keywords dev-feedback removed

I don't know of any way to get a static/reliable reference to a closure in PHP.
You could use debug_backtrace to find every closure's callstack. But I'm pretty certain that is not considered "best practice" by any means (and might even introduce slight performance issues).

Or install Query Monitor plugin to see where and how all your themes and plugins hook into WordPress. Maybe you'll find a cleaner way to remove what you're trying to remove. Micro-managing an external plugin the way you're going about it doesn't seem sustainable in the long run (been there done that). It could very easily break on your next update. Fun way to learn WordPress tho.

You might also want to look into installing an Object Cache plugin to replace the use of transients.

Also - StackExchange.

Good Luck!

#5 @SergeyBiryukov
6 months ago

  • Focuses coding-standards removed
  • Milestone Awaiting Review deleted

#6 in reply to: ↑ 4 @Alignak
6 months ago

Replying to apedog:

Thank you very much.

I am a programmer for 10 years and I always have to deal with poor code standards from multiple developers.

Query Monitor, Object Cache, debug_backtrace, etc... I know how to use it.
What I am missing (always too busy) is to spend a week getting more familiar with wordpress core and avoid not knowing about such useful information as the one you posted.

In my field I need to micro manage certain things, because I do both performance audits and server stuff.

Frequently, I see sites from clients that use fancy editors and then the page loads like 3 MB worth of shortcodes, which on some servers take a while to generate and cause a TTFB to go over 3, 4, 5 seconds.

One of my ideas was to cache the_content (often the slowest part of the page) on redis, (I said transient early for the sake of simplicity explaining the concept) and completely bypass the rebuilding process by serving it from cache when available.

Obviously it won't work for all possible case scenarios and it will need a plugin to purge it, but for some cases it will and it will speed things up dramatically when I cannot use page caching.

I tried the function you posted and it did what I want to do, however one must be careful not to return anything after removing the filters, else the filters still run (according to wp profile) if I return $content on other condition.

But your answer definitely put me in the right path for what I am trying to achieve and I am very thankful.

Thanks again

Last edited 6 months ago by Alignak (previous) (diff)
Note: See TracTickets for help on using tickets.