Learn how to implement Dependency Injection and a service container in WordPress to eliminate global state and build modular, testable plugins.
Previously in this course, we established the foundation for modern development by configuring PSR-4 autoloading and Composer. With those standards in place, we can now move away from the "global variable" trap that plagues most WordPress plugins.
In this lesson, we address the architectural debt created by tight coupling. By implementing Dependency Injection (DI) and a Service Container, we will transform your plugin into a collection of loosely coupled, swappable components.
In procedural WordPress development, we often see code like this:
PHPfunction get_kb_data() { global $wpdb; #6A9955">// Hard dependency return $wpdb->get_results("SELECT * FROM {$wpdb->prefix}kb_articles"); }
This code is difficult to test because it's impossible to swap $wpdb for a mock database object. It’s also hard to maintain; if your plugin’s logic grows, you’ll find yourself passing global variables across dozens of functions.
Dependency Injection (DI) solves this by requiring classes to receive their dependencies (via constructor or setter) rather than creating them or fetching them from the global scope. Design Patterns like the Service Container act as the central "factory" that manages the instantiation of these objects.
A Service Container is simply an associative array that holds "recipes" for creating your objects. When you need an object, you ask the container to resolve it.
Let's build a minimalist container for our Knowledge Base plugin.
PHPnamespace KnowledgeBase\Core; class Container { protected $bindings = []; protected $instances = []; #6A9955">// Register a recipe public function bind($abstract, callable $concrete) { $this->bindings[$abstract] = $concrete; } #6A9955">// Resolve an instance public function get($abstract) { if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } $concrete = $this->bindings[$abstract]; $this->instances[$abstract] = $concrete($this); return $this->instances[$abstract]; } }
Now, let's apply this to our plugin. Suppose we have a Logger class and an ArticleManager class. Instead of ArticleManager creating a Logger internally, we inject it.
PHPnamespace KnowledgeBase\Services; class Logger { public function log($message) { error_log($message); } } class ArticleManager { protected $logger; #6A9955">// Constructor Injection public function __construct(Logger $logger) { $this->logger = $logger; } public function publish($id) { $this->logger->log("Published article: $id"); } }
By injecting the Logger, we can easily pass a "NullLogger" or "MockLogger" during unit testing without changing the ArticleManager code. This is the essence of decoupling.
In your main plugin file, you register your services during the initialization phase:
PHP$container = new \KnowledgeBase\Core\Container(); $container->bind('Logger', function() { return new \KnowledgeBase\Services\Logger(); }); $container->bind('ArticleManager', function($c) { return new \KnowledgeBase\Services\ArticleManager($c->get('Logger')); }); #6A9955">// Use it $manager = $container->get('ArticleManager'); $manager->publish(123);
Database class in your Knowledge Base plugin that wraps $wpdb.Settings service.AdminPage class that requires both Database and Settings in its constructor.AdminPage, ensuring that the dependencies are passed correctly at runtime.If you have worked with Laravel, you might recognize these patterns. Concepts like Laravel service container binding and Advanced Dependency Injection with Laravel Service Providers are foundational to this approach, even within the WordPress ecosystem.
Dependency Injection moves us from "pulling" global dependencies to "pushing" them into our classes. By using a Service Container, we define the structure of our application in one place, making our code modular, testable, and significantly easier to debug as the Knowledge Base plugin grows.
Up next: Architecting Service Providers — we will learn how to move our container registrations into dedicated classes to keep our main plugin file clean and organized.
Learn how to implement Dependency Injection in WordPress to decouple your plugin components, improve testability, and master professional software architecture.
Read moreLearn how to implement a Controller layer for WordPress admin pages. Separate your UI and business logic to maintain a scalable, professional plugin architecture.
Custom Hooks for React