I am developing a plugin which will show the caller of all the Hooks & Actions on a Page in Wordpress just like QueryMonitor. I know that global $wp_action & $wp_filter has all the information but it does not provide the Caller Component of the Action or Filter.

Can you please help me out with this.

If you need the Code, Comment down.

Thanks in advance.

Code : -

<?php
class MyTracker {

  static $hooks;

  static function track_hooks( ) {
    $filter = current_filter();
    if ( ! empty($GLOBALS['wp_filter'][$filter]) ) {
      foreach ( $GLOBALS['wp_filter'][$filter] as $priority => $tag_hooks ) {
        foreach ( $tag_hooks as $hook ) {
          if ( is_array($hook['function']) )  {
            if ( is_object($hook['function'][0]) ) {
              $func = get_class($hook['function'][0]) . '->' . $hook['function'][1];
            } elseif ( is_string($hook['function'][0]) ) {
              $func = $hook['function'][0] . '::' . $hook['function'][1];
            }
          } elseif( $hook['function'] instanceof Closure ) {
            $func = 'a closure';
          } elseif( is_string($hook['function']) ) {
            $func = $hook['function'];
          }
          self::$hooks[] = 'On hook <b>"' . $filter . '"</b> run <b>'. $func . '</b> at priority ' . $priority;
        }
      }
    }
  }

}

add_action( 'all', array('MyTracker', 'track_hooks') );

add_action( 'shutdown', function() {
    echo implode( '<br />', MyTracker::$hooks );
}, 9999);

This displays all the Actions on the Current page, What i Need is that the Execution time by each of these hooks.

  • it is not clear at all what is it that you actually need help with. Please edit the question, attach relevant code and explain exactly what is the problem you are facing. We just have no idea what is it that you are trying to do and how you do it – Mark Kaplun Aug 10 at 11:21
  • How does Query Monitor do it? Please include the code in your question, but I can tell you that WordPress does not have this information. You need to collect the information yourself from scratch using PHP, there is no WordPress API that will help you here, as WP does not sandbox or draw lines around plugins and themes. Once code is loaded it's just code, WP knows no difference between a hook a plugin added and a hook Core itself added. You'll have to intercept when the hook is added and figure out which file did it, and reverse engineer backwards from that path to figure out the plugin – Tom J Nowell Aug 10 at 11:36
  • As an aside, why not just use query monitor? Have you tried opening an issue asking the author how they did it? You've asked a few questions asking how to replicate query monitor functionality – Tom J Nowell Aug 10 at 11:38
  • 1
    QM uses the ReflectionFunction class to get the filename of the callback and determines the component from it's path. – Nathan Johnson Aug 12 at 20:23
up vote 1 down vote accepted

As I said in my comment to the question, you can use the PHP internal ReflectionFunction class to get the filename of the callable. The below code should be a good start to help you. It needs more logic for determining the callbacks and most of that should be abstracted out of the Monitor class anyway. I also didn't write the code that would get the nice name of the theme, plugin, core, or whatever else might have added the hook from the filename. That should be relatively easy to do though once you know the file that added the hook.

<?php
/**
 * Plugin Name:  Monitor
 */

namespace WPSE\Monitor;

class Monitor {
  protected $callbacks = [];

  protected function addCallback( $callback ) {
    $this->callbacks[] = $callback;
  }

  public function shutdown() {
    var_dump( $this->callbacks );
  }

  public function monitor() {
    global $wp_filter;
    $name = \current_filter();
    if( ! isset( $wp_filter[ $name ] ) ) {
      return;
    }

    $action = $wp_filter[ $name ];

    foreach ( $action->callbacks as $priority => $callbacks ) {
      foreach( $callbacks as $callback ) {
        try {
          if( \is_array( $callback[ 'function' ] ) ) {
            if( \is_object( $callback[ 'function' ][ 0 ] ) ) {
              $class = \get_class( $callback[ 'function' ][ 0 ] );
              $callback[ 'name' ] = $class . '->' . $callback[ 'function' ][ 1 ] . '()';
              $ref = new \ReflectionMethod( $class, $callback[ 'function' ][ 1 ] );
            }
            elseif( \is_string( $callback[ 'function' ][ 0 ] ) ) {
              $callback[ 'name' ] = $callback[ 'function' ][ 0 ] . '::' . $callback[ 'function' ][ 1 ];
              $ref = new \ReflectionMethod( $callback[ 'function' ][ 0 ], $callback[ 'function' ][ 1 ] );
            }
          }
          elseif( $callback[ 'function' ] instanceOf Closure ) {
            $callback[ 'name' ] = 'closure';
            $ref = new \ReflectionMethod( $callback[ 'function' ] );
          }
          elseif( \is_string( $callback[ 'function' ] ) ) {
            $callback[ 'name' ] = $callback[ 'function' ] . '()';
            $ref = new \ReflectionMethod( $callback[ 'name' ] );
          }
          else{
            return;
          }

          $callback[ 'filename' ] = $ref->getFileName();

          $this->addCallback( $callback );

        } catch( \ReflectionException $e ) {
          $callback['error'] = new \WP_Error( 'reflection_exception', $e->getMessage() );
        }
      }
    }
  }
}
class MonitorInit {

  private static $monitor;

  public static function init() {
    self::$monitor = new Monitor();
    \add_action( 'all', [ self::$monitor, 'monitor' ] );
    \add_action( 'shutdown', [ self::$monitor, 'shutdown' ] );
  }
}
\add_action( 'plugins_loaded', __NAMESPACE__ . '\MonitorInit::init', 0 );

The plugins_loaded hook is the first that's available to regular plugins. So if you want to catch hooks that are added before this, you'll need to do something tricky.

  • Hey there I have run the code it is giving a Array with 404 elements, can you please explain the output. – Akash Manhas Aug 13 at 6:54
  • Output can be found at [link](drive.google.com/file/d/1yABh2puSZZ41DYeDF9_PGAKVy7FerrK1/… ) – Akash Manhas Aug 13 at 6:58
  • 1
    Each of the elements of the 404 element array is itself an array of 4 elements. 'function' is the callback used by WordPress, 'accepted_args' is used by WordPress. The code adds the 'name' and 'filename' elements. The 'name' is a nice name of the callback and the 'filename' is the file that the callback is located in. If the file is in a subfolder of plugins it's obviously a plugin, if it's in a subfolder of themes, it's a theme. If it's not, it's probably core, but not necessarily. There are lots of possibilities. – Nathan Johnson Aug 13 at 16:47

Akash, It sounds like you are want to list out all the plugins & themes that have added actions and filters?

IN which case, scan all plugin & theme files in wp-content for 'add_action' and 'add_filter', note the filepath & the function in which they are added. Match the function names with the wp lists and excluded any that didnt actually get added in wp , or maybe not - they might only get added in certain circumstances and not actually be in the wp variables at the time your plugin runs. Might be safer to list the full list? You could note which plugins and themes are not active, which have created their own actions and filters, etc.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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