Have you met this “Carl” guy? He’s always blabbing about “object-oriented this” and “object-oriented that”. You decide to dip your toes into the subject (maybe he’s on to something…).
You try to apply a subset of what he teaches by creating a class. You start to code it. Life is good. And then it happens. You need to use a WordPress hook.
What do you with them?
Three possible solutions
You do some research on “ye ol’ interwebz”. Your research nets you three general methods for dealing with WordPress hooks. You can:
- Put all your hooks in the
__construct
method of your class. - Put all your hooks in a separate class method (often called init).
- Put all your hooks outside the class.
Now, design is all about finding solutions to a problem. These are all valid solutions to the problem: How to hook an object into WordPress? You’ve solved the problem and it works. So what’s the issue?
The issue is how you handle WordPress hooks in your class. That’s what option 1 and 2 are about. One of them is better than the other.
Option 1 works, but it’s a weak solution. It misunderstands the role of the constructor. On the other hand, option 2 doesn’t. As long as you don’t recreate the same issue as option 1.
Option 3 doesn’t do anything wrong. That’s because it doesn’t try to solve the problem inside the class. And you know what? That’s fine.
You won’t confuse the purpose of the constructor that way.
The role of the constructor
The job of the constructor is to create the initial state of a new object. I think everyone can agree with that (I hope!). Things break down when determining the limits of that job.
The constructor is only in charge of the initial INTERNAL state of a new object. That’s where the issue with putting hooks in the __construct
method comes from.
WordPress hooks have nothing to do with setting up an initial internal state of an object. Hooking into WordPress is a separate job (from an object-oriented design perspective). It’s better to handle it as such. Or at least, it’s better practice for you.
Unit Tests
Nowhere is this more obvious than when you try to unit test your code. Unit testing is a huge (and misunderstood) topic. We won’t dive into it much here since it’s quite complex.
There’s a crucial thing to understand about unit tests though. It’s that you run them in isolation. An ideal unit test only runs the code that you’re testing. This isn’t always possible. That said, you should strive to run as little code as possible during a test. Nothing more.
You can’t achieve that if your hooks are in the constructor. You can’t create an instance of your class without loading WordPress. A unit test shouldn’t need any (or almost any) WordPress code to do its job.
Let’s say that you can’t unit test your code. It’s a strong indicator that something is wrong design-wise. The problem here is the tight coupling to WordPress in your constructor.
Fixing the issue
Let’s take a look at several ways we can fix the issue.
Using a separate method
Putting your hooks in a separate method (Option 2) is a great (and simple) way to solve the issue. You just need to rearrange your code a bit. You have to be careful though. There’s still a wrong way for you to do it. That’s what we’ll look at first.
class MyPlugin { public function __construct() { $this->init(); } public function init() { add_action('wp_loaded', array($this, 'on_loaded')); } public function on_loaded() { // ... } }
You can see we have init
method with all our hooks. That part is all fine and good. The problem is that you’re still calling it from your class constructor. This hasn’t solved the coupling issue at all.
class MyPlugin { public function init() { add_action('wp_loaded', array($this, 'on_loaded')); } public function on_loaded() { // ... } } $my_plugin = new MyPlugin(); $my_plugin->init();
You can fix this by removing init
method from the __construct
method. Instead of calling it there, you call it outside your class. The result is the same, but you removed the coupling of your class to WordPress.
Using a custom constructor
You can also do that by using a static method as a custom constructor. It’s not something that you see used a lot (except on this site). It’s an elegant way to get something like option 1.
class MyPlugin { public static function init() { $self = new self(); add_action('wp_loaded', array($self, 'on_loaded')); } public function on_loaded() { // ... } } MyPlugin::init();
We changed the init
method to a static method. new self()
creates our object which we then pass to the hooks. Our class still handles everything (including construction). Yet you reduced the coupling of your class to WordPress.
So how do you load your class hooks this way? You have two options available to you. You can call the init
method right after you defined your class (like above). You can also create a plugins_loaded
hook with an array callback to the init
method.
class MyPlugin { public static function init() { $self = new self(); add_action('wp_loaded', array($self, 'on_loaded')); } public function on_loaded() { // ... } } add_action('plugins_loaded', array('MyPlugin', 'init'));
Why use that hook and not another? It’s the first hook that WordPress calls once it’s done loading all plugins. It’s there to prevent potential conflicts or issues.
Using an external function
You might not have noticed, but a lot of this code is almost identical to what you’d see for option 3. The difference between the two is that option 3 removes all coupling with WordPress. You move all that code into a function outside the class.
class MyPlugin { public function on_loaded() { // ... } } function load_my_plugin() { $my_plugin = new MyPlugin(); add_action('wp_loaded', array($my_plugin, 'on_loaded')); } add_action('plugins_loaded', 'load_my_plugin');
The example uses a function with the plugins_loaded
hook. It isn’t the only way to do it though. You don’t even have to use a function. You can just hook everything below the class if you’re not worried about conflicts.
class MyPlugin { public function on_loaded() { // ... } } $my_plugin = new MyPlugin(); add_action('wp_loaded', array($my_plugin, 'on_loaded'));
You have a lot of options
So what’s the best option then? It’s a valid question to ask yourself. At this point, the answer depends on you. There’s no right answer.
You might find this frustrating. The truth is that this is what software design is about. There isn’t always a perfect solution in design. You often have to make tradeoffs.
Do you want your code to be a bit more coupled? That way you can maintain a bit of context. Or do you decouple everything? Do you care about using a custom constructor or is a method fine? You have to make a choice.
Let’s just keep WordPress hooks out of the constructor. Deal?