A Practical Guide to WordPress Dependency Injection

I got a call to take over a project with a massive, custom-built events plugin. It was a total mess. The previous developers had built everything with classes, which is a good start, but they were all tangled together. The main ticketing class would create a new instance of the email handler, which in turn would create a new instance of a logging utility. You couldn’t change one thing without breaking five others. It was brittle, impossible to test, and a nightmare to maintain. This is a classic side effect of not using WordPress dependency injection. The code ends up fighting you every step of the way.

When classes create the objects they depend on directly (e.g., $logger = new MyLogger();), they become tightly coupled. They’re stuck together. You can’t use the ticketing class without also dragging in that specific email handler and that specific logger. What if you want to use a different logging service for certain events? You can’t. Not without rewriting the whole thing.

The Obvious Fix That Isn’t Enough

My first thought was to just refactor the constructors. Instead of classes creating their own dependencies, I’d pass them in. So the constructor for the ticketing class would look like public function __construct( $email_handler, $logger ). And yeah, that worked… for a minute. It decoupled the classes from each other, but it just pushed the problem somewhere else. Now, the main plugin file was a disaster, responsible for creating every single object in the exact right order. It was a huge, fragile block of initialization code. Better, but not a real solution.

A Simple and Practical DI Container

Here’s the kicker: you don’t need a huge, complicated framework to solve this. You just need a central place to manage how your objects get built. A simple Dependency Injection (DI) container. The basic idea, which builds on a great concept I saw over at carlalexander.ca, is a class that holds recipes for creating your objects. When you need an object, you just ask the container for it, and it builds it for you with all the right dependencies.

We can build a lightweight container that uses anonymous functions (Closures) to define these “recipes.” Trust me on this, it’s simpler than it sounds and makes your code incredibly flexible.

<?php
class MyPlugin_Container implements ArrayAccess {

    private $values = [];

    // Method to define a "service" - an object that should only be created once.
    public function service(Closure $closure) {
        return function ($c) use ($closure) {
            static $object;

            if (null === $object) {
                $object = $closure($c);
            }

            return $object;
        };
    }

    // ArrayAccess methods to make the container behave like an array.
    public function offsetSet($key, $value) {
        $this->values[$key] = $value;
    }

    public function offsetGet($key) {
        if (!isset($this->values[$key])) {
            throw new InvalidArgumentException("Value '{$key}' not found.");
        }
        
        $value = $this->values[$key];

        // If it's a function, run it to create the object.
        // Pass the container itself so it can resolve other dependencies.
        return $value instanceof Closure ? $value($this) : $value;
    }

    public function offsetExists($key) {
        return isset($this->values[$key]);
    }

    public function offsetUnset($key) {
        unset($this->values[$key]);
    }
}

// --- How you use it ---

$container = new MyPlugin_Container();

// Define the logger as a service (it will only be created once).
$container['logger'] = $container->service(function() {
    return new My_File_Logger('/path/to/logs.txt');
});

// Define the email handler, which depends on the logger.
$container['email_handler'] = function($c) {
    return new My_Email_Handler($c['logger']);
};

// Now, get the email handler. The container builds it with the logger automatically.
$email_handler = $container['email_handler'];

So, What’s the Point?

This approach completely changes how you build plugins. Instead of a tangled web of dependencies, you have a central, organized way to manage your objects. Here’s what you gain:

  • Flexibility: Want to swap out the file logger for a database logger? You only have to change one line in your container setup. Nothing else breaks.
  • Testability: You can easily test your classes in isolation by passing mock objects as dependencies. Total game-changer for writing reliable code.
  • Maintainability: New developers can look at your container setup and immediately understand how the plugin is put together. No more digging through dozens of files.

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.

Moving away from singletons and direct instantiation is a huge step up in writing professional, scalable WordPress code. A simple DI container is one of the most powerful tools you can have. Period.

Leave a Reply

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