Make WordPress Core

Opened 6 years ago

Last modified 4 years ago

#46991 new feature request

Stop propagation on actions or filter

Reported by: screamingdev's profile screamingdev Owned by:
Milestone: Awaiting Review Priority: low
Severity: normal Version:
Component: Plugins Keywords: 2nd-opinion
Focuses: Cc:

Description

It would be great to stop further filter-hooks/action-observer when we reached a state that should not continue.

This helps especially for custom implementations:

  • Rejection actions that should not run.
  • Return filtered values without giving other plugins the chance to change them.

All just for specific cases.

Possible implementation:

  • Normal but impossible: Event-Class as wrapper reflecting whether to continue or stop.
  • Semi-normal-way: Throwing a exception like \WP_Stop_Propagation_Exception telling WP_Hook to stop.
  • Absolute absurd way: Setting a global variable like $stop_propagation = true

... and there may be more.

Change History (5)

#1 @johnbillion
6 years ago

  • Component changed from General to Plugins
  • Keywords reporter-feedback 2nd-opinion added
  • Priority changed from normal to low

Is there a specific use case for this? It sounds like it has the potential to break the actions and filters system with little benefit.

#2 @screamingdev
6 years ago

Surely I can make up some use cases either small and big or simple and important. So I just point out that this leads to a better event-based-system (as possibly known from other languages or frameworks) and give an example for action and filter. I am sure there is a need among developers to stop WordPress or other plugins/themes from doing things. Currently noone has granular control over the things that happen in the event queue / hooks and deregister all remainder or the whole action/filter is no proper solution.

Action:

<?php
add_filter( 'wp_footer' , function () {
  if ( is_sales_page( 'footer' ) ) {
    echo get_page( 'footer-salespage' );
    // stopPropagation
  }
} );

Yee this can be done in the template too and I just made up functions.

Filter:

<?php
add_filter( 'register' , function () {
  // stopPropagation
} );

We simply disable something.


On the one hand this solves all the scenarios where customers needs changes for their own site. So if WordPress wants to become or name itself kind of "framework" with event sourcing then it truly should be capable of stopping events.
On the other hand this blocks out plugins and theme-functionality which is fine because someone truly intended to.

This can be solved in multiple ways:

  • add stopPropagation (and keep it in WordPress)
  • Allow a dropin for WP_Hook
  • Do not make WP_Hook a final class

What else do you like to discuss?

#3 @screamingdev
6 years ago

  • Keywords reporter-feedback removed

#4 @screamingdev
6 years ago

Hint @johnbillion :

  • This is like "Oh, the filter 'the_content' shall not continue with this kind of text".
  • Comparable to Event.stopPropagation() from JavaScript

This is a possible solution directly in \WP_Hook:

<?php

                do {
                        $this->current_priority[ $nesting_level ] = $priority = current( $this->iterations[ $nesting_level ] );

                        foreach ( $this->callbacks[ $priority ] as $the_ ) {
                                if( ! $this->doing_action ) {
                                        $args[ 0 ] = $value;
                                }

                                // Avoid the array_slice if possible.
                                if ( $the_['accepted_args'] == 0 ) {
                                        $value = call_user_func_array( $the_['function'], array() );
                                } elseif ( $the_['accepted_args'] >= $num_args ) {
                                        $value = call_user_func_array( $the_['function'], $args );
                                } else {
                                        $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) );
                                }

                                if ( $value instanceof \WP_Hook_Stop ) {
                                        $value = $value->get_value();
                                        break 2;
                                }
                        }

                } while ( false !== next( $this->iterations[ $nesting_level ] ) );
<?php

class WP_Hook_Stop {
        __construct( $value = '' );
        get_value();
}

This does not break anything and allows some dynamic in the WP_Hook queue.
There is nothing wrong with early termination of things when the solution is alrady known.

Last edited 6 years ago by screamingdev (previous) (diff)

#5 @mbabker
5 years ago

A practical use case where a stopPropagation type of function call would be beneficial.

I've written an import system to move a client's multiple websites from another platform to WordPress, and the import plugin makes liberal use of filters to be able to make site specific modifications to the imported data without stuffing it all in the core plugin. Part of this includes the ability for one of the hooks to cancel the import of a line of data from the import file. Because of the lack of a way to stop propagating the call down to later priority hooks, all of the hooking functions need to check if the import has already been cancelled for that line and behave differently, whereas with a stopPropagation type of call the hook that cancels the request could just tell WordPress I don't want it to call anymore hooks.

I could get around this locally by throwing Exceptions if I had to, but I couldn't find anything inside wp-includes/plugin.php that shows WordPress doing any form of error handling if a hook does throw, so I'm weary about going this route as it would probably leave some of the globals in an unexpected state.

Note: See TracTickets for help on using tickets.