Learn how to implement Dependency Injection in WordPress to decouple your plugin components, improve testability, and master professional software architecture.
Previously in this course, we explored The Model Layer for Data to abstract our database interactions. While our model now handles data fetching, our controllers are likely still "newing up" these models inside their constructors. This tight coupling makes our code rigid and difficult to unit test. In this lesson, we will replace that hard-coded instantiation with Dependency Injection (DI) to create a more modular, testable architecture.
In a typical WordPress plugin, a class often relies on other services. For example, your KnowledgeBaseController probably needs an instance of KnowledgeBaseModel to fetch articles. Traditionally, you might write:
PHPclass KnowledgeBaseController { protected $model; public function __construct() { #6A9955">// Tight coupling: The controller is responsible for creating its own dependency $this->model = new KnowledgeBaseModel(); } }
This approach is problematic. If you ever need to swap KnowledgeBaseModel for a MockKnowledgeBaseModel (for testing) or a CachedKnowledgeBaseModel (for performance), you have to modify the KnowledgeBaseController source code.
Dependency Injection is a design pattern where an object receives its dependencies from an external source rather than creating them itself. Instead of the controller asking for a specific concrete class, we "inject" the dependency through the constructor. This is the cornerstone of decoupling your code.
Let’s refactor our KnowledgeBaseController to accept its dependencies. By moving the instantiation logic out of the class, we make it "agnostic" to how the dependency is created.
Modify your controller to require an interface or a class instance in the constructor:
PHPclass KnowledgeBaseController { protected $model; #6A9955">// We no longer instantiate the model here public function __construct(KnowledgeBaseModel $model) { $this->model = $model; } public function render_archive() { $articles = $this->model->get_all_articles(); #6A9955">// ... render logic } }
Now that our controller requires a model, where does that model come from? We need a central place to manage these objects—a Service Container. In a simple WordPress plugin, this can be a static class or a method within your main plugin core that acts as a factory.
PHPclass ServiceContainer { protected static $instances = []; public static function get_knowledge_base_model() { if (!isset(self::$instances['model'])) { self::$instances['model'] = new KnowledgeBaseModel(); } return self::$instances['model']; } public static function get_knowledge_base_controller() { #6A9955">// Inject the dependency retrieved from the container return new KnowledgeBaseController(self::get_knowledge_base_model()); } }
By using this container, we centralize the "wiring" of our application. If we decide to use a different implementation for our model later, we only change the ServiceContainer logic, not the controller.
The primary benefit of this architecture is how it facilitates testing. Because the controller doesn't care how the model is created, we can inject a "mock" version during our PHPUnit tests.
PHPpublic function test_render_archive_fetches_data() { #6A9955">// Create a mock of the model $mockModel = $this->createMock(KnowledgeBaseModel::class); $mockModel->method('get_all_articles')->willReturn(['Test Article']); #6A9955">// Inject the mock into the controller $controller = new KnowledgeBaseController($mockModel); $this->assertIsArray($controller->render_archive()); }
This is significantly cleaner than trying to mock global WordPress database functions, and it aligns with advanced patterns like WordPress REST API Dependency Injection.
__construct method.PluginContainer class in your project.PluginContainer.Dependency injection is about control inversion. By shifting the responsibility of object creation from the consumer to a central container, we achieve a decoupled, maintainable, and testable codebase. You've now moved from hard-coded dependencies to a professional architecture that allows for easier unit testing and future-proofed modularity.
Up next: We will look at handling large datasets, where we'll apply these architectural lessons to optimize how we query and display thousands of articles.
Learn how to implement a Controller layer for WordPress admin pages. Separate your UI and business logic to maintain a scalable, professional plugin architecture.
Read moreLearn to organize your WordPress plugin using the MVC pattern. Master directory structures, class loading, and separation of concerns for scalable code.
Advanced MVC: Dependency Injection