PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
137: Dependency Injection in PHP
Imagine you're building a high-end coffee machine. If you weld the coffee bean hopper directly to the internal chassis and fill it with one specific brand of dark roast, you've built a machine that only makes one kind of coffee. If you suddenly want a light roast, or if that brand of coffee goes out of business, you have to take a blowtorch to your machine and rebuild the whole thing from scratch.
A better way to build it is to create a slot where the user can slide in any bean container they want. The machine doesn't care who produced the beans or what roast they are, as long as the container fits the slot. That's the essence of Dependency Injection (DI): instead of a class "reaching out" to create its own tools, you "inject" those tools into the class from the outside.
The trap of the "new" keyword
In PHP, we often fall into the trap of instantiating dependencies right inside our constructors. I've seen this in countless legacy codebases. It looks like this:
class UserRegistration {
private $mailer;
public function __construct() {
// This is the "welded hopper" approach
$this->mailer = new SendGridMailer();
}
public function register($email) {
// ... logic to save user ...
$this->mailer->sendWelcomeEmail($email);
}
}
At first glance, this seems fine. It's simple. But notice how UserRegistration is now married to SendGridMailer. If you want to switch to Mailchimp, or if you want to write a unit test and don't actually want to send a real email to a customer every time the test runs, you're stuck. You can't change the mailer without changing the UserRegistration code itself.
Handing the tools to the class
To fix this, we stop the class from deciding which mailer to use. Instead, we tell it: "You need a mailer to function; I'll give you one when I create you."
class UserRegistration {
private $mailer;
// We "inject" the dependency through the constructor
public function __construct(MailerInterface $mailer) {
$this->mailer = $mailer;
}
public function register($email) {
// ... logic to save user ...
$this->mailer->sendWelcomeEmail($email);
}
}
Now, look at the difference. UserRegistration no longer cares how the email is sent. It just knows that whatever object it receives will have a sendWelcomeEmail method. I've found that this one shift in thinking is usually the "aha!" moment for developers moving from script-writing to software engineering.
Why the Interface is the secret sauce
You'll notice I used MailerInterface in the type-hint above. This is crucial. If I just type-hinted SendGridMailer, I'd still be tied to that specific class. By using an interface, I'm defining a contract.
Here is how that maps back to our coffee machine:
- The Interface: The shape and size of the bean slot. It defines what is required, not how it's made.
- The Implementation: The actual beans (SendGrid, Mailchimp, or a "Mock" mailer for testing).
- The Injection: The act of sliding the beans into the slot.
When it comes time to actually run the code, you handle the instantiation outside the class:
$mailer = new SendGridMailer('api-key-123');
$registration = new UserRegistration($mailer);
$registration->register('dev@example.com');
If you're running a test, you just swap one line: $mailer = new MockMailer();. Your UserRegistration class remains untouched, untested, and blissfully unaware that it's not sending real emails. That is the power of decoupling.
📋 Practical Task
Refactoring the Payment Gateway to Support Multiple Providers
You have a Checkout class that is currently hard-coded to use a StripePaymentProcessor. This is causing problems because the business wants to add PayPal support for international customers.
Your Task:
- Create a
PaymentProcessorInterfacethat defines a single method:processPayment(float $amount): bool. - Modify the
StripePaymentProcessorclass to implement this interface. - Create a new
PayPalPaymentProcessorclass that also implements the interface. - Refactor the
Checkoutclass so that it no longer callsnew StripePaymentProcessor()in its constructor. Instead, it should accept aPaymentProcessorInterfacevia dependency injection. - Demonstrate the flexibility by instantiating the
Checkoutclass twice: once using Stripe and once using PayPal.
There are no comments for now.