Why WordPress hooks do not belong in a class constructor

I get a call from a client. Their membership site is acting up: user permissions randomly failing, content not unlocking when it should. A total mess. They mention another developer built a custom plugin to handle the integration with their forum software, so that is my first stop. I open the main plugin file, find the primary class, and my stomach drops. The __construct method is over 200 lines long. It is a chaotic mess of add_action and add_filter calls. You cannot even instantiate the object without the entire WordPress environment lighting up. This is the classic mistake of putting WordPress hooks straight into a class.

A class constructor has one job: to create an object and set its initial state. It is not meant to be a switchboard operator connecting the class to every hook and filter in WordPress. When you jam all your hooks into the constructor, you get tight coupling. The class then depends completely on the WordPress environment just to exist, which means you cannot test it on its own and you cannot reuse it. It is a dead end.

The “quick fix” that solves nothing

My first thought was 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();. Cleaner code. It felt good for about five minutes, until I realized I had not solved anything. All I had done was rearrange the furniture in a burning house. The constructor was still kicking everything off, so the class was just as coupled and untestable as before. It was a rookie move.

Decoupling your hooks properly

The fix is to separate creating the object from wiring it into WordPress. The class should not be responsible for hooking itself in; something else should. The class just provides the public methods that are available to be hooked. This is not a new idea. It is a foundational principle of good object-oriented design, one I saw laid out 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 takeaway?

It comes down to control and separation of concerns. Your class is a blueprint for an object with specific capabilities. That is it. How that object gets used and integrated into the larger WordPress application is a separate responsibility. Keep the hooks out of the constructor and you get 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.

This stuff gets complicated fast. If you are tired of debugging someone else’s mess and just want your site to work, drop my team a line. We have probably seen it before.

Whatever else you do, keep your hooks out of the constructor.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.