Konstantin Kovshenin recently posted on his blog about creating a WP_Plugin class. He posted an example of a class to fit his thoughts around it on Gist, and from there, discussion has taken place on how such a class would be implemented.
There has been a fair bit of discussion on my fork of Konstantin’s code about this, and I’ve been updating the class with new ideas as we come across them.
However, as is usual with discussion regarding any semi-complicated piece of code, there has been some disagreement on how best to hook methods in. Mike Schinkel is a fan of mapping method names directly to hooks, whereas I much prefer prefixing methods that I want hooked with either action_
or filter_
. (We both agree that PHPDoc tags are a good idea though, although Mike also adds a @wp-nohook
to ignore any methods.)1 I thought I’d further flesh out why I’m not a fan of mapping the methods directly.
Personally, while I see the merit in naming methods for hooks directly, I hate magic. I hate not knowing when my code is used, and I think one of the biggest strengths of WordPress is that this hardly ever happens. If I want something used, I explicitly declare that through add_action
/add_filter
.
Don’t get me wrong: I love making things easier for myself. One of my favourite pieces of code ever is one written by Morten Fangel that I use in almost everything I do: _sortArgs()
. This piece of code will take an associative array, like array('a' => 'b')
and map the variables to parameters to my function. Combined with $_GET
and $_POST
, it’s an extremely powerful tool. However, _sortArgs
isn’t really that magical when it comes down to it. I’m specifying which parameters I want, and everything is explicitly written by me.
I can see the same thing with this plugin class. If I prefix a method with action_
or filter_
(or using PHPDoc tags), I’m explicitly stating that I want this hooked. On the other hand, a method like init
is completely implicit. It happens to match a WordPress action, but that could be a coincidence.
As an example of where this would be a problem for me: I often write a method like admin_page
for whatever page I’m adding to the admin. If I have things spread across several pages, I’ll factorise the common header bits and footer bits into admin_header
and admin_footer
. Except with implicit hooking, I’ve accidentally just hooked my footer method into the administration footer. Now, I have to undo that by specifying that I don’t want it hooked.
To hook implicitly requires that I know every action/filter in WordPress to avoid conflicting with them.
Even worse than this is that hooking implicitly breaks forward as well as backward compatibility. Let’s say I add a method called after_post
which I call from another class in my plugin, so I need it to be a public method. Everything is going well, until WordPress adds a hook into templates for adding content after a post. Oops, suddenly, my plugin breaks through no fault of my own, and through something that core developers shouldn’t (and wouldn’t) have to worry about.
Hooking implicitly breaks compatibility in every direction, and is too magical. It is absolutely not the way to consume a public API.
Sidenote: A discussion also emerged on how to use priorities. Mike and I both agree (I think) on using PHPDoc, while Thomas Scholz prefers preferred suffixing the method (i.e. action_init_2
). My problem with this is that distinguishing between an named init
with priority 2 and an action named init_2
is impossible.
Edit: Thomas dropped support for priorities in the method name, which I initially missed. Thanks for the correction.
Edit 2: Updated with a footnote about Mike’s position regarding implicit/explicit hooking.
- Mike has informed me that he does support explicit hooking for published code, but implicit hooking for prototyping. I’m not a fan of this either, since I can forsee people forgetting to do so. [↩]
I dropped the priority-in-function-name idea really fast. Yes, it was bad.
My current implementation https://gist.github.com/1631723 works with PHPDoc only.
Ah, didn't realise that. Updated the post.
Nice post Ryan and thanks for the reference.
However, you have some nuances regarding my opinion mis-stated. I think implicit is good for certain use-case such as for rapid prototyping for internally used method. But I think explicit is better for code you plan to publish and you suggest I'm only proposing implicit.
If you look at my examples you'll see it supports a "@wp-autohook explicit" for eliminating all "magic." When explicit is set then it requires the the methods have @wp-action or @wp-filter tags. What my main issue was with using naming prefixes for hooks like "action_init" and "filter_the_content"; I think that's a hack and can potentially cause problems.
So to clarify my opinion it is "If you want it explicit then use @wp-action or @wp-filter tags."
Defaulting to implicit is IMHO a bad idea. If you are going to support implicit hooking, I think explicit should still be the default. Apologies for misrepresenting your position though, I've added a footnote.
As I said, I'm not against magic, but I think it should be explicit.
Regarding prefixing with `action_` or `filter_`, what problems do you forsee regarding that?
When did I say "default to implicit?" Read the code: https://gist.github.com/1630212 🙂
The problem with "action_" and "filter_" is that from experience trying to use the convention 1.) it's easy to forget to include the prefix and 2.) it's ambiguous because I might write a function "filter_xxxx" intending to filter "xxx" and find out there is an action called "xxx", i.e. "filter_admin_menu."
I see it as a "magical" convention, which is what you were arguing against.
Huh! I swear it did last time I checked it. Stupid eyes.
Forgetting to include the prefix gives immediate results: nothing happens. Forgetting to specify nohook, on the other hand, can give weird bugs which might not be readily apparent.
In addition, if you are writing "filter_x", you'd be looking at the source to see whether it's an action or a filter anyway, so I don't see that as a problem.
It's the opposite of a magical convention, specifically because you are writing exactly what you want filtered.
Regarding “filter_x” I think you misunderstand what issue I was identifying to. Consider that I might be writing a function that I did not want nor intend to be called as a hook, and I called it “filter_admin_menu.” But even if you don’t think that’s an issue Gary Jone mentioned on your fork at Gist that the real problem is when someone else adds a hook that starts with “action_” or “filter_”, especially in another plugin. All in all, the prefix convention just has too many issues.
Also on your fork Rarst mentioned that using PHPDoc is unfortunately problematic with respect to opcode caches that might throw out the comments. I’m currently considering that we might be able to use “reserved name” constants instead of PHPDoc tags. It would be a bit less elegant but it would be more reliable.