0

I am writing a plugin that uses a lot of different action/filter hooks to manipulate different parts of the users.php page (e.g. manage_users_columns, admin_head, user_row_actions, editable_roles)

In the callback function for each hook I use wp_get_current_user() and perform a WP query to count the number of posts where the user id of the current user is stored as a meta value. I use this result to decide on the filter output/action performed (Complex task, cannot be changed).

As a result, when loading the users.php page, the WP Query seem to be executed in every hook callback function (which makes sense).

For performance reasons I would like to "nest" the hooks. In the "init" action hook, the WP query should be performed and the result should be passed to the following action hooks and filters. Two questions remain:

1) Is it necessary at all to care about performance here? Or does Wordpress have some internal "caching" mechanism that recognises identical queries and perform them only once when loading a page?

2a) The following example works for now, but I am not sure if this is safe to work with this in general. Question is: As my custom hook "myfuncs" is defined inside a hook (init), is it always known and can be hooked into, e.g. in functions.php or Plugin Code?

2b) Regarding the nested WP hooks (e.g. admin_head-users.php): are they executed normally according to their priority or may they be delayed/not executed at all sometimes as they are nested?

3) Is there a different solution/best practice?

add_action('init', 'initfunc', 1 );
add_action('myfuncs', 'myfuncs_callback', 1 );

public function initfunc() {
    global $pagenow;
    $args = (array) $pagenow; // only to simplify this example.

    do_action( 'myfuncs', $args );
}

public function myfuncs_callback ($args) {
    if($args[0] === "somevalue") {
        add_action( 'admin_head-users.php', 'add_user_css' ,1 );
    }

}

public function add_user_css () {
    echo "<style>/*some styles*/</style>";
}

1 Answer 1

0

This is a good use case for either Transients or the Object Cache. The choice of which to use depends on whether you need to perform this query/check on every single request, or whether it only needs to be performed once every hour/day/week etc.

For both options would would start by creating a separate function that performs the check and returns the number of results. You would then use this function inside each hook callback to determine the output/action performed (as you put it).

function wpse_344520_count_user_posts() {
    $user_id = get_current_user_id();
    $query   = new WP_Query(
        [
                // etc.
        ]
    );

    $count = $query->found_posts;

    return $count;
}

add_action(
    'manage_users_columns',
    function() {
        $count = wpse_344520_count_user_posts();

        if ( $count > 0 ) {
            // Do something.
        }
    }
);

add_action(
    'admin_head',
    function() {
        $count = wpse_344520_count_user_posts();

        if ( $count > 0 ) {
            // Do something.
        }
    }
);

// etc.

The trick is that inside this function you should check if the query has already been performed, and return the existing result if it exists. You would use transients or the object cache as the storage mechanism, depending on your requirements.

The object cache verson would look like this:

function wpse_344520_count_user_posts() {
    // Check if we've cached a result.
    $count = wp_cache_get( 'wpse_344520_count', $user_id );

    // If we have...
    if ( false !== $count ) { // Must be strict comparison so we can store 0 if necessary.
        // ...return it.
        return $count;
    } 

    // Otherwise perform the query...
    $user_id = get_current_user_id();

    $query = new WP_Query(
        [
            // etc.
        ]
    );

    $count = $query->found_posts;

    // ..cache the result... 
    wp_cache_set( 'wpse_344520_count', $count, $user_id );

    // ...and return it.
    return $count;
}

Now the query will only run once, and subsequent calls to wpse_344520_count_user_posts() will use the cached result.

The transient method is similar, but saves its result in the database, which lets you cache the result over multiple separate requests. Use this method if the result being accurate up to the second isn't necessary:

function wpse_344520_count_user_posts() {
    $user_id = get_current_user_id();

    // Check if we've cached a result.
    $count = get_transient( 'wpse_344520_count_' . $user_id );

    // If we have...
    if ( $count !== false ) { // Must be strict comparison so we can store 0 if necessary.
        // ...return it.
        return $count;
    } 

    // Otherwise perform the query...
    $query = new WP_Query(
        [
            // etc.
        ]
    );

    $count = $query->found_posts;

    // ..and cache the result... 
    set_transient( 'wpse_344520_count_' . $user_id, $count, HOUR_IN_SECONDS );

    // ...then return it.
    return $count;
}

As you can see, it's very similar. The only differences are:

  • We use get_transient() and set_transient() instead of wp_cache_get() and wp_cache_set().
  • We use the user ID as part of the transient name to make sure that the value is stored separately for each user. This is necessary because transients do not support "groups" the way the object cache does.
  • We pass a number of seconds that the value should be cached to set_transient(). In my example I've used one of the built in constants to set the time to one hour.
1
  • Thank you Jacob! Exactly what I needed.
    – Nic
    Commented Aug 8, 2019 at 19:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.