How to Write Better WordPress Code with Dependency Injection in 30 Days?
Understanding DI intellectually is easy. Applying it consistently? That requires rewiring your brain. Here’s how to transition from “WordPress hacker” to “software architect.”
Table of Contents
Step 1: Adopt the “New is Glue” Mantra
Tattoo this phrase on your brain:
“New is Glue.”
Every time you type new ClassName() inside another class, you’re supergluing those two classes together forever.
The Habit:
Before writing new, pause and ask yourself:
“Should I be creating this object here, or should someone else create it and pass it to me?”
Example:
// BAD: Creating dependencies inside the class
class EmailSender {
public function send() {
$mailer = new PHPMailer(); // GLUE!
$logger = new FileLogger(); // MORE GLUE!
}
}
// GOOD: Inject dependencies
class EmailSender {
public function __construct(
MailerInterface $mailer,
LoggerInterface $logger
) { ... }
}
Step 2: The 15-Minute Planning Ritual
You cannot code architecturally in a rush. Professional developers plan before they type.
The Daily Routine:
Before touching your keyboard, spend 10-15 minutes with a piece of paper (yes, actual paper):
Draw your classes as boxes: One box per class you’ll need.
Draw arrows for dependencies: If Class A needs Class B, draw an arrow from A to B.
Check for circular dependencies: If you see a circle (A needs B, B needs A), STOP. Redesign before coding.
Identify what to inject: For each class, write down what it needs in its constructor.
This 15-minute investment saves hours of refactoring later. You’ll catch design flaws before they’re immortalized in code.
Step 3: Start Small, Then Scale
Don’t try to refactor your entire codebase overnight. Start with one class.
Your First DI Win (10-Minute Exercise):
Find a class that uses global $wpdb
Add a constructor that accepts wpdb as a parameter
Store it in a protected property
Replace all global $wpdb calls with $this->db
Update the instantiation to pass $wpdb
Congratulations, you just wrote your first dependency-injected class. Do this once a day for a week, and it becomes second nature.
Step 4: Graduate to a Dependency Injection Container
Initially, manually wiring dependencies is fine:
$mailer = new PHPMailer();
$logger = new FileLogger();
$sender = new EmailSender( $mailer, $logger );
But as your application grows, this becomes tedious. Enter: Dependency Injection Containers (DIC).
Popular PHP DI Containers:
PHP-DI: The most powerful and feature-rich
Pimple: Lightweight and simple
Acorn (Roots Sage): Built into modern WordPress theme frameworks
A container automatically resolves dependencies. You register your classes once:
$container->set( EmailSender::class, function() {
return new EmailSender(
new PHPMailer(),
new FileLogger()
);
});
Then retrieve fully-wired instances anywhere:
$sender = $container->get( EmailSender::class );
Step 5: Think in Contracts (Interfaces)
The final mindset shift: stop thinking about concrete classes. Start thinking about contracts (interfaces).
Instead of requiring a specific PHPMailer class, require any class that can send mail:
interface MailerInterface {
public function send( $to, $subject, $body );
}
class EmailSender {
public function __construct( MailerInterface $mailer ) {
// Accepts ANY mailer: PHPMailer, SendGrid, Mailgun, etc.
$this->mailer = $mailer;
}
}
Now you can swap mail providers without touching EmailSender:
// Today: PHPMailer
$sender = new EmailSender( new PHPMailer() );
// Tomorrow: SendGrid
$sender = new EmailSender( new SendGridMailer() );
// Next week: Your custom solution
$sender = new EmailSender( new CustomMailer() );
This is the ultimate flexibility. You’re coding to contracts, not concrete implementations.
Advanced Mindset Techniques: From Good to Great
The “Dependency Audit” Weekly Exercise
Every Friday, spend 30 minutes reviewing code you wrote that week:
Highlight every global keyword in your code
Highlight every new keyword inside methods (not constructors)
Ask yourself: Could these be injected instead?
Track your progress over time. Watch as your “global” count drops from 50 to 10 to zero.
The “Interface First” Design Pattern
Here’s a pro technique: write the interface before writing the class.
// Step 1: Define the contract
interface PostRepositoryInterface {
public function save( $data );
public function find( $id );
public function delete( $id );
}
// Step 2: Implement it
class WordPressPostRepository implements PostRepositoryInterface {
public function __construct( wpdb $db ) { ... }
// Implementation details...
}
Why? Because interfaces force you to think about behavior before implementation. You design the API your code needs, then build toward it.
The “Composition Over Inheritance” Shift
Old-school WordPress developers love extending classes:
class MyPlugin extends SomeFramework { ... }
Modern developers prefer composition:
class MyPlugin {
public function __construct(
Framework $framework,
Logger $logger
) { ... }
}
Inheritance says “is-a.” Composition says “has-a.” Composition gives you flexibility. Inheritance gives you chains and shackles.
Real-World WordPress DI Example: A Complete Plugin Class
Let’s put it all together. Here’s a real WordPress plugin class using Dependency Injection:
<?php
/**
* Main Plugin Class with Dependency Injection
*/
interface CacheInterface {
public function get( $key );
public function set( $key, $value, $expiration );
}
interface LoggerInterface {
public function log( $message, $level );
}
class MyAwesomePlugin {
protected $db;
protected $cache;
protected $logger;
/**
* All dependencies declared upfront - crystal clear!
*/
public function __construct(
wpdb $database,
CacheInterface $cache,
LoggerInterface $logger
) {
$this->db = $database;
$this->cache = $cache;
$this->logger = $logger;
}
public function process_user_data( $user_id ) {
// Try cache first
$cached = $this->cache->get( "user_{$user_id}" );
if ( $cached ) {
return $cached;
}
// Fetch from database
$data = $this->db->get_row(
$this->db->prepare(
"SELECT * FROM {$this->db->prefix}my_table WHERE user_id = %d",
$user_id
)
);
// Cache it
$this->cache->set( "user_{$user_id}", $data, 3600 );
// Log it
$this->logger->log( "Processed user {$user_id}", 'info' );
return $data;
}
}
// Bootstrap the plugin
function bootstrap_my_plugin() {
global $wpdb;
$cache = new WordPressCache(); // Implements CacheInterface
$logger = new FileLogger(); // Implements LoggerInterface
$plugin = new MyAwesomePlugin( $wpdb, $cache, $logger );
// Wire up WordPress hooks
add_action( 'init', [ $plugin, 'process_user_data' ] );
}
add_action( 'plugins_loaded', 'bootstrap_my_plugin' );
What Makes This Beautiful:
Every dependency is explicit in the constructor
Easy to test (inject mock cache, mock logger)
Easy to swap implementations (Redis cache instead of WP cache? One line change)
Self-documenting (anyone can see what this class needs)
No hidden globals lurking in methods
Common Pitfalls and How to Avoid Them
Pitfall #1: “Service Locator” Disguised as DI
// This is NOT Dependency Injection!
class BadExample {
public function do_something() {
$container = ServiceLocator::getInstance();
$db = $container->get( 'database' );
}
}
This is just a global variable with extra steps. True DI means dependencies come in through the constructor or method parameters, not fetched from a global container inside methods.
Pitfall #2: Constructor Bloat
If your constructor has 10 parameters, you have a design problem:
// TOO MANY DEPENDENCIES!
public function __construct(
$db, $cache, $logger, $mailer, $validator,
$sanitizer, $formatter, $parser, $renderer, $compiler
) { ... }
Solution: Your class is doing too much. Break it into smaller, focused classes.
Pitfall #3: Injecting Concrete Classes Instead of Interfaces
// Brittle: Locked to PHPMailer
public function __construct( PHPMailer $mailer ) { }
// Flexible: Works with any mailer
public function __construct( MailerInterface $mailer ) { }
Always inject interfaces when possible. This is called “Dependency Inversion Principle” (the “D” in SOLID).
Your 30-Day Dependency Injection Transformation Plan
Ready to commit? Here’s your roadmap:
Week 1: Awareness
Days 1-3: Read this guide twice. Take notes.
Days 4-7: Review your existing code. Count globals and new keywords. Just observe, don’t change anything yet.
Week 2: Baby Steps
Days 8-10: Refactor one class. Remove one global. Add one constructor injection.
Days 11-14: Refactor three more classes. Share your code with a colleague for feedback.
Week 3: Practice
Days 15-18: Write a new feature using DI from the start. No globals allowed.
Days 19-21: Sketch dependencies on paper before coding. Make it a habit.
Week 4: Mastery
Days 22-25: Install a DI container (PHP-DI or Pimple). Wire up your plugin.
Days 26-28: Write your first unit test using mock dependencies.
Days 29-30: Write a blog post or tutorial teaching someone else what you learned.
By day 30, Dependency Injection will feel natural. You’ll wonder how you ever coded without it.
Leave a Reply