Advanced MVC: Dependency Injection for WordPress Plugins
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.
Understanding Dependency Injection from First Principles
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.
Refactoring for Dependency Injection
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.
Step 1: Update the Class Constructor
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 } }
Step 2: The Service Container Pattern
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.
Improving Testability
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.
Hands-on Exercise: Refactor Your Model Injection
- Identify one Controller in your Knowledge Base plugin that currently instantiates a Model or a Helper class inside its
__constructmethod. - Create a simple
PluginContainerclass in your project. - Refactor the Controller to accept the dependency via the constructor.
- Update the code that initializes your Controller (likely in your main plugin file) to fetch the dependency from your new
PluginContainer.
Common Pitfalls
- Over-Engineering: Don't build a massive, complex dependency injection container library from scratch. For most WordPress plugins, a simple static factory class is sufficient.
- Constructor Bloat: If a class requires 10+ dependencies, it's a "code smell" indicating the class is doing too much (violating the Single Responsibility Principle). Break that class into smaller components.
- Hidden Dependencies: Avoid "Service Locator" anti-patterns where you pass the entire container into the constructor. Always pass only the specific objects the class actually needs.
Recap
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.
Work with me

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard — by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.

Custom WordPress Theme Development
A custom WordPress theme built exactly to your design — fast, clean, and easy to manage. No bloated page builders, no compromises.