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
72: Understanding Hook-Based Architecture
I've been looking at a legacy order processing system today, and it's a mess. Every time the business wants a new feature—like sending a Slack notification when a high-value order comes in or adding a custom tax for a specific region—someone has to open OrderProcessor.php and add another if statement or another service dependency to the constructor. It's a nightmare to test and a disaster for version control.
The Hard-Coded Headache
Let's look at how this usually starts. I've got a simple method that handles an order. It looks clean at first, right?
class OrderProcessor {
public function process($order) {
// Save to database
$this->db->save($order);
// Send confirmation email
$this->mailer->send($order->customer_email, "Order Confirmed!");
echo "Order processed successfully.";
}
}
But then my boss comes in and says, "We need to notify the warehouse team via SMS for orders over $500." Then the marketing lead says, "We need to add the customer to our loyalty program if they've spent more than $1,000 in total."
If I keep adding those calls directly into process(), that method will eventually be 300 lines long. I don't want the OrderProcessor to know about SMS gateways, loyalty programs, or Slack APIs. It should just process the order.
Opening a Door for External Code
I want a way to say, "I'm at the point where the order is finished; if anyone cares, do your thing now," without the processor actually knowing who "anyone" is. I'll try creating a simple registry for callbacks.
class HookManager {
private static $hooks = [];
public static function add_action($hook_name, $callback) {
self::$hooks[$hook_name][] = $callback;
}
public static function do_action($hook_name, ...$args) {
if (isset(self::$hooks[$hook_name])) {
foreach (self::$hooks[$hook_name] as $callback) {
call_user_func($callback, ...$args);
}
}
}
}
Now, let's see if we can strip the OrderProcessor of its dependencies. I'll replace the hard-coded mailer with a hook.
class OrderProcessor {
public function process($order) {
$this->db->save($order);
// Instead of calling $this->mailer, we trigger a hook
HookManager::do_action('after_order_processed', $order);
echo "Order processed successfully.";
}
}
// Now, elsewhere in the app (maybe in a Service Provider), I attach the logic:
HookManager::add_action('after_order_processed', function($order) {
echo "Sending email to " . $order->customer_email . "...\n";
});
HookManager::add_action('after_order_processed', function($order) {
if ($order->total > 500) {
echo "Sending SMS to warehouse...\n";
}
});
This is much better. The OrderProcessor is now "blind" to the side effects. I can add ten more notifications without ever touching the core processing logic again. This is the essence of an "Action" hook.
When We Need to Change the Data
But there's a catch. Sometimes I don't just want to trigger a side effect; I want to modify a value before it gets used. For example, imagine we want to apply a discount to the order total, but that discount logic varies wildly based on the user's membership level.
If I use do_action, I can't easily pass a value back. I need a "Filter." I'll add a new method to my HookManager that actually returns a modified value.
class HookManager {
// ... keep the previous add_action and do_action ...
private static $filters = [];
public static function add_filter($hook_name, $callback) {
self::$filters[$hook_name][] = $callback;
}
public static function apply_filters($hook_name, $value, ...$args) {
if (isset(self::$filters[$hook_name])) {
foreach (self::$filters[$hook_name] as $callback) {
// We pass the result of one filter into the next
$value = call_user_func($callback, $value, ...$args);
}
}
return $value;
}
}
Now, look at how this changes the workflow. I can "filter" the price before the order is saved:
$total = 100.00;
$order_id = 123;
// Apply filters to the total
$final_total = HookManager::apply_filters('calculate_order_total', $total, $order_id);
echo "Final Price: " . $final_total;
// Example filter: A 10% discount for VIPs
HookManager::add_filter('calculate_order_total', function($price, $id) {
return $price * 0.9;
});
// Example filter: Adding a $5 flat shipping fee
HookManager::add_filter('calculate_order_total', function($price, $id) {
return $price + 5.00;
});
If you run this, the total becomes 95.00 (100 * 0.9 + 5). The beauty here is the pipeline. Each function takes the output of the previous one. I've effectively built a plug-in system. I can write a whole new PHP file for "Summer Sales" that just adds a few filters, and the main application code never has to change.
The Trade-off
I'll be honest: this isn't a silver bullet. The biggest downside is "traceability." When you're debugging and you see apply_filters('calculate_order_total', $total), you can't just click "Go to Definition" in your IDE to see what's happening to the price. The logic is scattered across the codebase.
That's why you should only use hook-based architecture for things that are truly extensible. Don't use a hook for your primary business logic; use it for the "edges" of your application—notifications, data transformations, and third-party integrations.
📋 Practical Task
Building a Dynamic Discount Engine
You are tasked with creating a checkout system that allows different "Discount Modules" to modify the final price of a shopping cart without modifying the Checkout class itself.
- Implement a
HookManagerclass withadd_filterandapply_filtersmethods. - Create a
Checkoutclass with a methodcalculateFinalPrice($basePrice)that uses theHookManagerto apply filters to the price. - Register at least two different filters:
- A Percentage Discount (e.g., 20% off).
- A Flat Coupon (e.g., subtract $10).
- Instantiate the
Checkoutclass and print the final price to the screen to verify that both filters were applied in sequence.
There are no comments for now.