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
17: Inheritance and Interfaces
I was looking at some old code for a payment processing module last week, and it was a disaster. I had three different classes—CreditCardPayment, PayPalPayment, and BankTransferPayment—and about 70% of the code was identical. They all handled the amount, the currency, and a basic logging mechanism. It's the kind of redundancy that makes you dread a simple change because you have to remember to update it in three places.
Tired of copy-pasting
Let's look at how I started cleaning this up. Initially, I had something like this:
class CreditCardPayment {
public $amount;
public function process() {
echo "Processing credit card payment of {$this->amount}...";
}
}
class PayPalPayment {
public $amount;
public function process() {
echo "Processing PayPal payment of {$this->amount}...";
}
}
It works, but it's lazy. If I decide to add a transactionId to every payment, I'm editing every single class. So, I decided to pull the shared logic "upwards." I created a parent class—a blueprint—that handles the basics.
class Payment {
protected $amount;
public function __construct($amount) {
$this->amount = $amount;
}
public function getAmount() {
return $this->amount;
}
}
class CreditCardPayment extends Payment {
public function process() {
echo "Charging credit card for {$this->getAmount()}...";
}
}
class PayPalPayment extends Payment {
public function process() {
echo "Redirecting to PayPal for {$this->getAmount()}...";
}
}
Now, CreditCardPayment and PayPalPayment "inherit" from Payment. Notice I used protected for $amount. I did that because private would lock it away from the child classes, and public lets any random piece of code change the amount mid-transaction, which is a recipe for a security nightmare. protected is the sweet spot: the children can see it, but the outside world can't.
The "Square Peg, Round Hole" problem
Here is where I hit a wall. I wanted to add a refund() method. Most payments are refundable, but some—like certain types of digital gift cards or one-time credits—absolutely are not.
My first instinct was to put refund() in the Payment base class. But then I realized that would force GiftCardPayment to have a refund() method it can't actually use. I'd end up writing a method that just throws an exception saying "Not supported." That's a huge red flag in software design; it means my class hierarchy is lying about what it can actually do.
I need a way to say: "I don't care what kind of object you are, as long as you are capable of being refunded."
Defining a contract
This is where Interfaces come in. An interface isn't a class; it's a contract. It doesn't contain any actual logic—no code, just a list of methods that a class must implement if it signs the contract.
interface Refundable {
public function refund($amount);
}
Now, I can apply this selectively. I'll keep my inheritance for the "is-a" relationship (a Credit Card payment is a payment), but use the interface for the "can-do" relationship (this payment can be refunded).
class CreditCardPayment extends Payment implements Refundable {
public function process() {
echo "Charging card...";
}
public function refund($amount) {
echo "Refunding {$amount} back to the card...";
}
}
class GiftCardPayment extends Payment {
public function process() {
echo "Deducting from gift card...";
}
// No refund method here, because GiftCardPayment doesn't implement Refundable
}
The real magic happens when you write a function to handle refunds. Instead of checking if the object is a CreditCardPayment or a PayPalPayment, I just check if it implements the interface.
function processRefund(Refundable $payment, $amount) {
$payment->refund($amount);
}
If I try to pass a GiftCardPayment into processRefund(), PHP will stop me immediately with a TypeError. It won't even try to run the code. This is infinitely better than running the code and having it crash halfway through because a method was missing. I've moved the error from "runtime" (where the user sees it) to "type-checking" (where the developer sees it).
📋 Practical Task
Build a Multi-Channel Notification System
You are building a notification system for an app. Some notifications are sent via Email, some via SMS, and some via Push Notifications. Not all of these channels support "Urgent" priority (which requires a special high-priority API call).
Your requirements:
- Create a base class
Notifierthat stores the$recipientand has a methodgetRecipient(). - Create an interface
UrgentCapablewith a methodsendUrgent($message). - Create three classes:
EmailNotifier,SmsNotifier, andPushNotifier. All three must extendNotifier. - Only
SmsNotifierandPushNotifiershould implementUrgentCapable. - Implement a function called
dispatchUrgentNotification(UrgentCapable $notifier, $message)that calls thesendUrgentmethod.
Test your code by attempting to pass an SmsNotifier into dispatchUrgentNotification (which should work) and then attempting to pass an EmailNotifier (which should trigger a PHP TypeError).
There are no comments for now.