WordPress.org

Make WordPress Core

Opened 3 years ago

Last modified 19 months ago

#38743 new feature request

Introduce add_action_once()

Reported by: tfrommen Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Plugins Keywords:
Focuses: Cc:
PR Number:

Description

Hi there.

I was wondering if noone ever needed to add a callback to some action or filter hook only if it hasn't been added (potentially by someone else) before.

Possible use cases are:

  • only add my_callback if it hasn't been added before, no matter with what priority;
  • only add my_callback if it hasn't been added before with a specific priority.

Naming-wise, I'd suggest add_action_once() and add_filter_once(), respetively, although one could be more verbose (e.g., add_action_if_not_exists() etc.).

Here is a possible implementation of this:

<?php

function add_action_once(
    $tag,
    callable $function_to_add,
    $priority = 10,
    $accepted_args = 1,
    $check_priority = false
) {

    return add_filter_once( $tag, $function_to_add, $priority, $accepted_args, $check_priority );
}

function add_filter_once(
    $tag,
    callable $function_to_add,
    $priority = 10,
    $accepted_args = 1,
    $check_priority = false
) {

    global $wp_filter;

    if ( empty( $wp_filter[ $tag ] ) ) {
        return add_filter( $tag, $function_to_add, $priority, $accepted_args );
    }

    if ( false === $check_priority ) ) {
        if ( has_filter( $tag, $function_to_add ) ) {
            return false;
        }
    } else {
        foreach ( $wp_filter[ $tag ] as $functions ) {
            foreach ( $functions as $data ) {
                if ( isset( $data['function'] ) && $function_to_add === $data['function'] ) {
                    return false;
                }
            }
        }
    }

    return add_filter( $tag, $function_to_add, $priority, $accepted_args );
}

Yes, it is possible to check whether or not a specific callback has been added to an action or filter hook by using has_filter(). And as you can see, the above code uses that function internally.

However, this would mean that your code, where you want to add a callback if it hasn't been added before, would have to perform has_filter() and add_action()/add_filter().

And secondly, by using has_filter(), you cannot check if a callback has been added with a specific priority.

The two functions above take care of both as well.

Example:

<?php

// Somewhere in your code... Maybe!
add_filter( 'the_title', 'add_exclamation_mark', 20 );

// Some other place, that maybe doesn't know about the place above...
add_filter( 'the_title', 'add_exclamation_mark', 10 );

// Yet another place...
echo apply_filters( 'the_title', 'Hi there' );

// Hi there!!

By using add_filter_once() (in this example withouth the last parameter as I'm not interested in where/when the callback gets fired), this would look like so:

<?php

// Somewhere in your code... Maybe!
add_filter_once( 'the_title', 'add_exclamation_mark', 20 );

// Some other place, that maybe doesn't know about the place above...
add_filter_once( 'the_title', 'add_exclamation_mark', 10 );

// Yet another place...
echo apply_filters( 'the_title', 'Hi there' );

// Hi there!

So, what do you think?

Cheers,
Thorsten

Change History (6)

#1 @tfrommen
3 years ago

Seems like I'm unable to edit this ticket...

The second function should look like this, acutally:

<?php

function add_filter_once(
    $tag,
    callable $function_to_add,
    $priority = 10,
    $accepted_args = 1,
    $check_priority = false
) {

    global $wp_filter;

    if ( empty( $wp_filter[ $tag ] ) ) {
        return add_filter( $tag, $function_to_add, $priority, $accepted_args );
    }

    if ( false === $check_priority ) ) {
        if ( has_filter( $tag, $function_to_add ) ) {
            return false;
        }
    } else {
        if ( empty( $wp_filter[ $tag ][ $check_priority ] ) {
            return add_filter( $tag, $function_to_add, $priority, $accepted_args );
        }
        
        foreach ( $wp_filter[ $tag ][ $check_priority ] as $data ) {
            if ( isset( $data['function'] ) && $function_to_add === $data['function'] ) {
                return false;
            }
        }
    }

    return add_filter( $tag, $function_to_add, $priority, $accepted_args );
}

I had several version here, with and without has_filter() - and mixed them up. :)

#2 @SergeyBiryukov
3 years ago

  • Component changed from General to Plugins

#3 @lukecavanagh
3 years ago

@tfrommen

Seems like a solid idea.

#4 follow-up: @soulseekah
19 months ago

Oops, looks like #43621 is intending to use the same name. Do you think you can come up with a different name concept? :)

add_filter_unique() maybe?

Last edited 19 months ago by soulseekah (previous) (diff)

#5 in reply to: ↑ 4 @tfrommen
19 months ago

Replying to soulseekah:

Opps, looks like #43621 is intending to use the same name. Do you think you can come up with a different name concept? :)

add_filter_unique() maybe?

Hm, interesting. In both cases, that name would make sense.

In this case, I could think of something like ensure_action()... Not sure. Also, there's not really an interest in this at all, considering both the impact opening this ticket has had, and also the plugin API is not really young, and yet noone ever really seems to have needed something like this...?

#6 @soulseekah
19 months ago

To tell you the truth, I had initially misread your issue. I was going to work on this as I thought "Wow, this is really useful!". Then, I started implementing it, and reading into your implementation, and realized that you need something different :)

And I apologize for skimming the name you came up with initially, though :)

Judging by the amount of starsgazers on this issue, though, seems like people are indeed interested in unique callback adding, although not sure how coordinated the use would be. Like how do you get plugin developers to use ensure_action() instead of add_action(), as protection for their own filters perhaps?

Thoughts?

Note: See TracTickets for help on using tickets.