I get a call from a client. Their membership site is acting up—user permissions randomly failing, content not unlocking when it should. Total mess. They mention another developer built a custom plugin to handle the integration with their forum software. That’s my first stop. I open up the main plugin file, find the primary class, and my stomach just drops. The __construct method is over 200 lines long. It’s a chaotic mess of add_action and add_filter calls. You can’t even instantiate the object without the entire WordPress environment lighting up. This, right here, is the classic mistake of improperly using WordPress hooks in classes.
Here’s the thing: a class constructor has one job, and one job only: to create an object and set its initial state. It’s not supposed to be a switchboard operator, connecting the class to every hook and filter in the WordPress ecosystem. When you jam all your hooks into the constructor, you create what we call tight coupling. The class becomes completely dependent on the WordPress environment to even exist. You can’t test it on its own. You can’t reuse it. It’s a dead end.
The “Quick Fix” That Solves Nothing
My first thought was just to refactor it for readability. I pulled all the hooks out of the constructor and moved them into a new method called init(). Then, right at the end of the constructor, I called $this->init();. There. Cleaner code. Felt good for about five minutes. Then I realized I hadn’t solved the actual problem. All I did was rearrange the furniture in a burning house. The constructor was still kicking everything off, making the class just as coupled and untestable as before. The core issue remained. Trust me on this, it was a rookie move.
Decoupling Your Hooks the Right Way
The real fix is to decouple object instantiation from its integration into WordPress. The class itself shouldn’t be responsible for hooking itself in; something else should. The class should just provide the public methods that are available to be hooked. This isn’t some new idea; it’s a foundational principle of good object-oriented design, a concept I saw articulated really well over at Carl Alexander’s blog. You let the code that loads the plugin handle the wiring.
<?php
class Sensible_Plugin_Class {
// The constructor should only handle dependencies and initial state.
// public function __construct( $some_dependency ) {
// $this->dependency = $some_dependency;
// }
/**
* Wires up the hooks.
*/
public function init() {
add_action( 'wp_loaded', [ $this, 'on_wp_loaded' ] );
add_filter( 'the_content', [ $this, 'modify_the_content' ] );
}
public function on_wp_loaded() {
// Your logic for the 'wp_loaded' action...
}
public function modify_the_content( $content ) {
// Your logic for filtering content...
return $content;
}
}
// In your main plugin file, *outside* the class:
$my_plugin_instance = new Sensible_Plugin_Class();
$my_plugin_instance->init();So What’s the Real Takeaway?
It’s about control and separation of concerns. Your class is a blueprint for an object with specific capabilities. That’s it. How that object gets used and integrated into the larger WordPress application is a completely different responsibility. By keeping the hooks out of the constructor, you create code that is:
- Testable: You can write unit tests for your methods without loading all of WordPress.
- Reusable: You could, in theory, use that same class in a different context.
- Maintainable: The next developer who sees your code (who might be you in six months) will immediately understand where the logic is versus where the integration is.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
But for the love of clean code, keep your hooks out of the constructor. Deal?
Leave a Reply