Why the WordPress Singleton Pattern Can Burn You

I was digging through a client’s site recently—the kind of project where three different developers had left their mark over five years. Total mess. The complaint was simple: “When we update a product, sometimes the inventory count doesn’t show up right on the frontend.” It was intermittent, maddening, and of course, costing them sales. After hours of tracing hooks, I found the culprit: a custom-built reporting plugin was tangled up in the WordPress Singleton Pattern, used for literally every class. A database object, a reporting object, a product object… everything was a Singleton. And that’s where the trouble began.

Look, a lot of us learned to use Singletons when we moved to OOP in WordPress. It solves a real problem: how do you get access to your main plugin object from anywhere without using a nasty global variable? The Singleton seems like a clean, clever answer. And for a while, I thought it was. It felt professional.

The “Singleton-for-Everything” Trap

The classic Singleton looks something like this. You have a private constructor, a static variable to hold your one-and-only instance, and a static method to get that instance. Simple.

class My_Messy_Plugin {
    private static $instance;

    private function __construct() {
        // Initialization stuff here...
    }

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function do_something() {
        // Plugin logic...
    }
}

// And you use it like this:
// My_Messy_Plugin::get_instance()->do_something();

My vulnerability? I used this pattern for years. My early plugins were littered with ::get_instance(). It works, right? Until you have to write a unit test. Or until two of your “unique” objects need to interact in a slightly different context. The Singleton pattern essentially creates a global state, but wrapped in a class so it feels less dirty. It’s a hidden dependency. When you call My_Other_Class::get_instance() from within your first class, you’ve just hard-coded a dependency that you can’t see from the outside. That’s exactly what happened with my client’s site. The reporting plugin’s “product” Singleton was holding onto stale data, and because it was globally unique, the rest of the site got that stale data, too. Total nightmare.

A Better Way: Static Registration

The real problem isn’t getting access to an object; it’s about properly initializing your plugin and its hooks. Instead of making the object globally accessible, you should just… not. Instantiate it once, tell WordPress about its methods, and then let it go. This builds on a great concept I saw over at carlalexander.ca. The idea is to decouple the class itself from the WordPress Plugin API.

Here’s the kicker: use a static method to bootstrap the plugin. It’s a simple, powerful change.

class My_Clean_Plugin {
    
    public function __construct() {
        // Constructor is for setting up the object's state,
        // NOT for adding hooks.
    }

    public static function init() {
        $plugin = new self();
        add_action( 'wp_enqueue_scripts', array( $plugin, 'enqueue_assets' ) );
        add_filter( 'the_content', array( $plugin, 'modify_content' ) );
    }

    public function enqueue_assets() {
        // Enqueue scripts and styles.
    }

    public function modify_content( $content ) {
        // Do something to the content.
        return $content;
    }
}

// In your main plugin file, you just call this once:
My_Clean_Plugin::init();

So, What’s the Point?

By moving the hook registration to a static init() method, you gain a few huge advantages:

  • Testability: Your constructor is clean. You can now pass in mock objects and test your class methods in isolation without loading all of WordPress.
  • Clarity: It’s obvious how the plugin starts. There’s one entry point. No hidden dependencies from a get_instance() call buried in some other method.
  • Flexibility: The class itself doesn’t depend on WordPress. The static init() method is the bridge. The core logic is just plain PHP.

The Singleton isn’t inherently evil, but it’s a tool for a very specific job—managing a single, shared resource like a database connection, not for general plugin architecture. In WordPress, it’s almost always a sign that you’re creating problems for your future self. Or for the next dev who has to clean it up.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *