Your analogy here is partially correct, but incomplete:
I understand that when do_action is called wp looks for all registered
listeners and invokes them according set priority.
You need to consider what happens when you call add_action
. WordPress never searches for the add_action
statements, it has those in a list. When you call add_action
, it updates a list of callbacks to call when that action happens.
With this in mind, it’s more useful to use an events analogy.
do_action( 'init' )
Fire the ‘init’ actionadd_action('init', 'myfunction' )
When theinit
event happens, callmyfunction
function myfunction() {}
Ah theinit
event happened
The events analogy helps enormously when understanding how actions and filters work, because there’s a whole class of problems that happen when you don’t understand related to timing.
Lets take your example of calling add_action
in the footer for the wp_head
action. wp_head
fires when wp_head()
is called, which should happen in the header between the <header>
tags. If we try to add an action in the footer, then it will be added, and the next time wp_head()
is called it will fire. But wp_head
has already been called, it’s too late. It’s like asking a child “remember to pack your bag before the party” just after the party has ended.
With this in mind I have some general rules and recommendations that would act as heuristics for more maintainable code:
- Put all code inside functions that fire on events.
init
,after_theme_setup
,wp_head
, andadmin_init
should cover most of it - The only code outside of a hook or filter Would be template files, and the initial
add_action
calls - Don’t add filters and hooks inside templates, do them in
functions.php
or a plugin, or if those files useinclude
orrequire
statements for an includes folder
There are some situations you’ll want to break those rules, usually to prevent recursion when saving things, etc, but those should be rare exceptions. Experiment and see what works, and look at this post that shows which actions fire when, and what’s safe to use at those times
A final note, actions and filters are the same thing. Internally they’re treated the same, but there are some important differences if you want things to work well:
- Actions do things
- Filters modify things
- Actions don’t need to return anything
- Filters are passed what’s being ‘filtered’ as the first argument, and always return it or a modified version
- Filters are called a lot, so never do heavy work in filters, it will slow things down massively, only modify, doing work is what actions are for
- Be wary of hooking into the translation and escaping filters, it can be dangerous, and has a major impact on performance as those are the most commonly called filters
- There is an especially dangerous action/filter called
all
, that will fire for every filter and hook. Useful sometimes for debugging, but I’d recommend using a plugin such as query monitor instead.