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
78: The Observer Pattern in PHP
I've seen this happen in almost every mid-sized project I've inherited: a class that started simple but slowly mutated into a "God Object." It usually happens when you have a core business action that needs to trigger five other unrelated things across your system.
Take a look at this Order class. It looks fine at first glance, but it's actually a maintenance nightmare waiting to happen.
class Order {
private $status = 'pending';
private $emailService;
private $inventoryManager;
private $shippingDepartment;
public function __construct($emailService, $inventoryManager, $shippingDepartment) {
$this->emailService = $emailService;
$this->inventoryManager = $inventoryManager;
$this->shippingDepartment = $shippingDepartment;
}
public function shipOrder() {
$this->status = 'shipped';
// Triggering side effects
$this->emailService->sendOrderConfirmation();
$this->inventoryManager->reduceStock();
$this->shippingDepartment->generateLabel();
echo "Order has been shipped!";
}
}
The Tight Coupling Trap
The "bug" here isn't a syntax error; it's a design flaw. The Order class knows way too much. It knows that when an order ships, the email service needs to run, the inventory needs to drop, and the shipping label needs to be printed.
What happens next week when the marketing team tells you they also want to trigger a "Customer Loyalty Point" update whenever an order ships? You have to go back into the Order class, inject a LoyaltyService into the constructor, and add another line to the shipOrder method. You're violating the Open/Closed Principle: the class is open for modification (which is dangerous) rather than open for extension.
Decoupling with SplSubject and SplObserver
PHP provides built-in interfaces for the Observer pattern: SplSubject and SplObserver. Instead of the Order class calling specific services, it simply announces, "Hey, I've changed!" and lets any registered "observers" decide how to react.
Here is how we refactor that mess into something professional.
class Order implements SplSubject {
private $observers = [];
public $status = 'pending';
public function attach(SplObserver $observer): void {
$this->observers[] = $observer;
}
public function detach(SplObserver $observer): void {
$this->observers = array_filter($this->observers, fn($o) => $o !== $observer);
}
public function notify(): void {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function shipOrder() {
$this->status = 'shipped';
echo "Order status updated to shipped. Notifying observers...\n";
$this->notify();
}
}
class EmailNotifier implements SplObserver {
public function update(SplSubject $subject): void {
if ($subject->status === 'shipped') {
echo "EmailNotifier: Sending shipment confirmation email.\n";
}
}
}
class InventoryNotifier implements SplObserver {
public function update(SplSubject $subject): void {
if ($subject->status === 'shipped') {
echo "InventoryNotifier: Reducing stock levels.\n";
}
}
}
// Usage
$order = new Order();
$order->attach(new EmailNotifier());
$order->attach(new InventoryNotifier());
$order->shipOrder();
Why this actually solves the problem
Notice that the Order class no longer knows that EmailNotifier or InventoryNotifier even exist. It only knows that it has a list of SplObserver objects.
If the marketing team comes back and wants those loyalty points, you don't touch the Order class at all. You just create a new LoyaltyNotifier class that implements SplObserver and attach it at runtime. I love this approach because it isolates your business logic from your side effects. If the email service crashes or needs to be replaced with a different provider, your core Order logic remains untouched and untested.
One quick tip: in a real-world production app, you'd likely use a Dependency Injection container or an Event Dispatcher (like the one in Symfony) to handle the "attaching" part, but under the hood, it's still this exact Observer pattern.
📋 Practical Task
Build a User Account Security Monitor
Your task is to implement the Observer pattern to monitor security events on a user account.
Requirements:
- Create a
UserAccountclass that implementsSplSubject. It should have a methodchangePassword($newPassword). - Create a
SecurityLogObserverthat implementsSplObserver. When notified, it should print: "Security Log: Password changed for user [username]." - Create an
EmailAlertObserverthat implementsSplObserver. When notified, it should print: "Email Alert: A password change was detected. If this wasn't you, contact support." - Instantiate the account, attach both observers, and trigger a password change to verify that both systems react without the
UserAccountclass knowing the details of the logging or emailing systems.
There are no comments for now.