Skip to content

Instantly share code, notes, and snippets.

@mikeschinkel
Created August 14, 2010 01:46
Show Gist options
  • Save mikeschinkel/523831 to your computer and use it in GitHub Desktop.
Save mikeschinkel/523831 to your computer and use it in GitHub Desktop.
<?php
/*
Plugin Name: Instrument Hooks for WordPress
Description: Instruments Hooks for a Page. Outputs during the Shutdown Hook.
Version: 0.1
Author: Mike Schinkel
Author URI: http://mikeschinkel.com
*/
if (isset($_GET['instrument']) && $_GET['instrument']=='hooks') {
add_action('shutdown','instrument_hooks');
function instrument_hooks() {
global $wpdb;
$hooks = $wpdb->get_results("SELECT * FROM wp_hook_list ORDER BY first_call");
$html = array();
$html[] = '<style>#instrumented-hook-list table,#instrumented-hook-list th,#instrumented-hook-list td {border:1px solid gray;padding:2px 5px;}</style>
<div align="center" id="instrumented-hook-list">
<table>
<tr>
<th>First Call</th>
<th>Hook Name</th>
<th>Hook Type</th>
<th>Arg Count</th>
<th>Called By</th>
<th>Line #</th>
<th>File Name</th>
</tr>';
foreach($hooks as $hook) {
$html[] = "<tr>
<td>{$hook->first_call}</td>
<td>{$hook->hook_name}</td>
<td>{$hook->hook_type}</td>
<td>{$hook->arg_count}</td>
<td>{$hook->called_by}</td>
<td>{$hook->line_num}</td>
<td>{$hook->file_name}</td>
</tr>";
}
$html[] = '</table></div>';
echo implode("\n",$html);
}
add_action('all','record_hook_usage');
function record_hook_usage($hook){
global $wpdb;
static $in_hook = false;
static $first_call = 1;
static $doc_root;
$callstack = debug_backtrace();
if (!$in_hook) {
$in_hook = true;
if ($first_call==1) {
$doc_root = $_SERVER['DOCUMENT_ROOT'];
$results = $wpdb->get_results("SHOW TABLE STATUS LIKE 'wp_hook_list'");
if (count($results)==1) {
$wpdb->query("TRUNCATE TABLE wp_hook_list");
} else {
$wpdb->query("CREATE TABLE wp_hook_list (
called_by varchar(96) NOT NULL,
hook_name varchar(96) NOT NULL,
hook_type varchar(15) NOT NULL,
first_call int(11) NOT NULL,
arg_count tinyint(4) NOT NULL,
file_name varchar(128) NOT NULL,
line_num smallint NOT NULL,
PRIMARY KEY (first_call,hook_name))"
);
}
}
$args = func_get_args();
$arg_count = count($args)-1;
$hook_type = str_replace('do_','',
str_replace('apply_filters','filter',
str_replace('_ref_array','[]',
$callstack[3]['function'])));
$file_name = addslashes(str_replace($doc_root,'',$callstack[3]['file']));
$line_num = $callstack[3]['line'];
$called_by = $callstack[4]['function'];
$wpdb->query("INSERT wp_hook_list
(first_call,called_by,hook_name,hook_type,arg_count,file_name,line_num)
VALUES ($first_call,'$called_by()','$hook','$hook_type',$arg_count,'$file_name',$line_num)");
$first_call++;
$in_hook = false;
}
}
}
@arafalov
Copy link

Just tested this. Looks good. A small problem on windows - the file names do not escape backslashes so the path runs all together (cwampwww...)

@mikeschinkel
Copy link
Author

Send a patch?

@arafalov
Copy link

I am not setup for that right now. Sorry. So, just a bug report for now.

@mikeschinkel
Copy link
Author

Can you explain the solution? I'm not sure what you are suggesting.

@arafalov
Copy link

Oh, sorry I wasn't clear.
Basically, when the instrumentation table is displayed it, the file name does not take into account the fact that Windows file names use backslashes.
So, if the file path is: C:\Wamp\www\network, it will show up as C:wampwww[\n]etwork, where [\n] is now new line.

I think changing line 77 to use addslashes does the trick:
$file_name = addslashes(str_replace($doc_root,'',$callstack[3]['file']));

@mikeschinkel
Copy link
Author

Perfect, thanks! I've edited. Can you confirm it works for you?

@arafalov
Copy link

Seems to work fine. Thank you.

@GeertDD
Copy link

GeertDD commented Nov 19, 2011

You should do an isset or empty check for $_GET['instrument'] to prevent undefined variable errors.

@mikeschinkel
Copy link
Author

@GeertDD - Sure, done.

@juancaser
Copy link

perfect just what im looking for...

made small customisation where i could filter which hook type i need to view

https://gist.github.com/thejuanwhocode/7ddb83d2a1fb0c78d359

$_GET['type']=filter

@ve3
Copy link

ve3 commented Dec 23, 2023

Tested with WordPress 6.5-alpha-57224 but when I added ?instrument=hooks to the URL, table is shown but no list and everything seems to be stopped for unknown reason. No errors show up or no in debug.log.

I try to wrap if (isset($_GET['instrument']) && $_GET['instrument']=='hooks') {...} with only add_action('all','record_hook_usage'); part and it seems to work (DB inserted) but table result did not show.
I come back to normal page and result is showing full.

I have no idea why it stopped inside record_hook_usage() without any errors.

@mikeschinkel
Copy link
Author

@ve3 — I have not worked with WordPress for over 4 years so can’t tell you what’s wrong.

My best suggestion is to get PhpStorm, and use it’s debugger to figure out why it does not work.

@ve3
Copy link

ve3 commented Dec 24, 2023

Thanks @mikeschinkel for your work. May I post a link to my gist here? It's a simple plugin that can be trace actions/filters hooks on enabled trace option.

The difference than other plugins is most of them can list actions and filters of current page but when the page is redirect or act by normal user you cannot know anything about it. My simple plugin can work on any request as long as enable trace hook option is turn on.

@mikeschinkel
Copy link
Author

Sure.

@ve3
Copy link

ve3 commented Dec 24, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment