wordpress中hook钩子机制
稍有接触过WordPress布景或外挂客制修改的朋友,对WordPress的Hook机制应该不陌生,但通常刚接触WordPress Hook的新手,对其运作原理可能会有点混乱或模糊。本文针对WordPress Hook运作大致做个简单的说明,而预设读者是理解基本的PHP function语法及运作,但对WordPress Hook机制不是很明白。
Hook机制里登场的角色
先从「登场角色」的个别说明开始:
WordPress核心
指的是WordPress内建的程式码架构,提供WordPress主要的基本功能。
Hook
也许你早已听说,Hook本身虽是钩子的意思,但直译又有点奇怪,所以一般通常都不直译它,而是直接称它Hook。WordPress的Hook也可以想像成「钩子」,这些「钩子」会埋在WordPress网站中特定几处的程式码中,埋进去时使用的语法,其「标示位置」的意义比较大,没有实质运作的内容。当程式执行到有埋Hook的地方时,它会找出所有对应到自己的Hook Function (也就是所有「钩到」该Hook的hook function),并一一执行。
因此若没有针对此Hook去「加入」要钩上去的Hook Function,执行到此什么也不会做。因此,它等于是WordPress核心预留一个执行的机会给未来想要加入客制功能的开发者。
Hook Function
Hook Function里会有实质运作的内容,即是实作了一些客制功能,可能是存取DB、增加HTML code、执行其他函式…等。我们在Hook Function里写好所需的功能后,就可以利用「加入至对应Hook」的语法,把Hook Function自已钩到该Hook上,使得该Hook被执行到时,也会连带执行自己。
Hook机制是如何运作的?
举个例子,我们拿wp_head及wp_footer这两个内建的hook来说明,wp_head这个hook就是用来埋在负责输出标签的程式码中,而wp_footer就是用来埋在输出页尾的程式码中(定义于wp-includes/general-template.php,用wp_head()及wp_footer()包装起来)。这两个hook,主要都是在布景档案中使用的,常见会出现在header.php及footer.php中。
请看下面的情境示例图,我们把wp_head及wp_footer看成是「钩子」,而别的hook functions就能来钩住它:
我们马上来写一个简单的例子。我们要写一个hook function,就叫它print_sth(),然后把它钩上wp_head这个hook。因为wp_head()的内容实际上就只有do_action(‘wp_head’); 这一行内容,而wp_footer()的内容也只有do_action(‘wp_footer’);,所以我们直接把do_action的语法换到图上去,比较容易做说明,因此示意图变成:
如此,只要执行到输出header.php时,就会执行到wp_head(),就如同执行到do_action(‘wp_head’),此时WP核心会去找所有「钩上」wp_head这个hook的hook function,于是就找到我们写的print_sth(),然后就执行它,所以结果它做的事就会出现在网站上,也完成了「客制」的动作:
简单的说,Hook机制就是:WP核心或其他plugin、theme提供想客制功能的人一个置入客制程式码(Hook Function)到特定的执行时间点(Hook)的机会。
WordPress的Action Hook与Filter Hook
WordPress中的Hook有两种,分别是「Action Hook」及「Filter Hook」,我们刚才举例的wp_head及wp_footer都是属于Action Hook。不过,一开始你可以先把这两种Hook看成是一样的东西,只是Filter多了一点点不同的特色,接着说明。
Action Hook
WP核心(或布景、外挂)在做它们该做的事时,如果执行到有埋action hook的程式码(即是do_action语法) 时,会去找寻对应到的hook functions,进而执行这些hook functions(即那些透过add_action()来加入的hook functions),借此完成客制功能。WP核心并不期待Action Hook functions会有回传值,所以这里的hook function只被视为一个「独立切出来运作的功能」。
WP核心做它该做的事,你做你想做的事,做完就各自结束。
Filter Hook
跟Action Hook一样,WP核心(或布景、外挂)在做它们该做的事时,如果执行到有埋filter hook的程式码(即是apply_filters语法) 时,就会去找寻对应的hook functions,进而执行这些hook functions(即那些透过add_filter()来加入的hook functions),借此完成客制功能。与Action Hook不同之处是,所有「钩上」Filter Hook的hook functions通常都会接收到参数,而WP核心会期待你拿到它提供的参数,并做完你想做的事后,要回传( return)一个值,让WP核心再利用你回传的值来接着完成它该做的事。
透过你的干涉,修改了WP核心丢给你的参数,WP核心再接着拿你改过的参数,继续完成它该做的事,此动作就像「过滤」的动作,因而得名filter。
比较Action Hook与Filter Hook的实作语法
比较一下两种Hook在埋进某处程式码时所用的语法,假设我们在某处(可能是在输出页首的程式码处,或输出文章标题、文章内容、侧边栏…等地方,要「出现客制效果」的地方)埋下这两种hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/*--------------- Action Hook ---------------*/ // 埋下一个名叫'do_more'的action hook do_action( 'do_more' ); /*--------------- Filter Hook ---------------*/ // 埋下一个名叫'get_special'的filter hook,注意它会有回传值 $c = apply_filters( 'get_special' , $a , $b ); |
然后我们可以在某处(可能是其他外挂、functions.php等处,要「实作客制功能」的地方) 实作对应的hook function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/*--------------- Action Hook Function---------------*/ // 增加要钩上'do_more'这个hook的hook function, // 并为此hook function取名叫more_func。 // 第一个参数是hook名称、第二个是hook function名称 add_action( 'do_more' , 'more_func' ); // 实作more_func的内容,不需回传值 function more_func() { echo 'do more thing...' ; } /*--------------- Filter Hook Function ---------------*/ // 增加要钩上'get_special' hook的hook function, // 并为此hook function取名叫special_func。 // 参数1是hook名称、参数2是hook function名称 // 参数3是Priority(优先序)、参数4是hook function参数的数目 add_filter( 'get_special' , 'special_func' , 10, 2); // 实作special_func的内容,需要给它回传值 function special( $a , $b ) { $c = $a . ' & ' . $b ; //做一些事,例如把两个参数连接起来 return $c ; //回传值 } |
所以其实两种Hook的运作方式几乎一样,只差在增加Action Hook函式不需回传值,而增加Filter Hook function时,你必须要回传一个值。所以Filter Hook函式通常都有提供参数,让想客制的人可以取得它,处理后再回传。
但如果有一个Filter Hook,它没有任何hook function有去钩它,它该怎么取得回传值?答案是,直接拿第一个它给的参数,以上面的例子来说,它会直接拿$a丢进$c。另外,其实我们也可以把filter写的跟action一样,只要不回传值就行,但action hook就没办法「模仿」filter hook,因为无法取得回传值。
Hook Function的优先序(Priority)
如果有很多地方(plugin或者布景functions.php)都add同一个hook,会怎么决定出现顺序?等案很显然是可以透过Hook Function的Priority参数来作优先序的设定:
就像我们刚才说明的例子中,我们使用add_filter加入special_func时设定的优先序是10,这也是Priority参数的预设值。如果你希望它能优先被执行,就设定小于10的数字,反之,就设个100、500之类的,让它延后被执行。
但其实这里有个隐含的冲突问题。
以wp_head这个hook为例,如果我写了一个外挂,希望透过wp_head来输出「增加a.css档案」的HTML语法,而a.css会重新设定body元素的样式,所以我希望它可以最后才被汇入,不要被其他css档干扰,于是我将Priority设为900,但我怎么知道Priority 900够不够大?若某个WP网站,它除了安装我的外挂,也安装了其他外挂,而其他外挂刚好也重新设定body元素的样式,然后把Priority设为950,此时我写的外挂在处理body样式时就出事了,于是就跟其他外挂冲突了。
所以此时我们需要了解的是:我的WP网站可能装了很多外挂,我怎么知道同一个Hook被加了多少Hook Function,而每个Hook Function的Priority被设定为多少?
答案是,我们可以透过$wp_filters这个global变数来取得所有hook的资讯,像是如下的function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
// 列出所有的hook function及其priority function list_hooked_functions( $tag =false) { global $wp_filter ; if ( $tag ) { $hook [ $tag ]= $wp_filter [ $tag ]; if (! is_array ( $hook [ $tag ])) { trigger_error( "Nothing found for '$tag' hook" , E_USER_WARNING); return ; } } else { $hook = $wp_filter ; ksort( $hook ); } echo '<pre>' ; foreach ( $hook as $tag => $priority ) { echo "<br />>>>>>\t<strong>$tag</strong><br />" ; ksort( $priority ); foreach ( $priority as $priority => $function ) { echo $priority ; foreach ( $function as $name => $properties ) echo "\t$name<br />" ; } } echo '</pre>' ; return ; } |
当我们呼叫list_hooked_functions(‘wp_head’); 时,就会列出wp_head这个Hook所钩住的所有hook function,可以看到priority 10之后有好几个都没有数字,因为它们都没有特别指定priority,所以都是10,包括我们刚才写的print_sth也在其中:
>>>>> wp_head
1 |
1 wp_enqueue_scripts2 feed_links3 feed_links_extra8 wp_print_styles9 wp_print_head_scripts10 rsd_linkwlwmanifest_linkindex_rel_linkparent_post_rel_linkstart_post_rel_linkadjacent_posts_rel_link_wp_headlocale_stylesheetwp_generatorrel_canonicalwp_shortlink_wp_headprint_sthwp_admin_bar_header_admin_bar_bump_cb |
所以,冲突很难提早避免,但发生冲突时,可以预先思考有没有可能是因为priority的设定,导致结果跟预期不符合。
- PHP中func_get_args函数的使用
- Apache中.htaccess25种技巧