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
73: Building a Simple Plugin System with Hooks
I've seen this exact scenario play out in a dozen different projects. You want your application to be extensible, so you build a "plugin system." You create a way for other parts of the code to "hook" into a process, but then you realize that while your hooks are being triggered, the data isn't actually changing. It's a frustrating bug because the logs show the plugin is running, yet the output remains stubbornly identical to the original.
The Problem: Filters that don't actually filter
// The "broken" Plugin Manager
class PluginManager {
private $hooks = [];
public function add_filter($name, $callback) {
$this->hooks[$name][] = $callback;
}
public function apply_filters($name, $value) {
if (isset($this->hooks[$name])) {
foreach ($this->hooks[$name] as $callback) {
// The mistake is right here
$callback($value);
}
}
return $value;
}
}
// Usage
$pm = new PluginManager();
$pm->add_filter('calculate_total', function($price) {
return $price + 10; // Adding a shipping fee
});
$total = $pm->apply_filters('calculate_total', 100);
echo $total; // Outputs: 100. Wait, what happened to the 10?
If you're looking at that code and thinking, "Why is it still 100?", you've hit on the core misunderstanding of how filters work. In the example above, the $callback($value) line is executing the function, and the function is indeed returning 110. But that returned value is just floating in space—it's not being assigned to anything. The original $value variable remains 100, and that's what gets returned at the end.
Passing the Torch: Maintaining the Value Chain
A filter's job is to take a piece of data, transform it, and hand it off to the next person in line. To fix this, we need to capture the return value of each callback and pass it into the next one. I like to think of it as a relay race; if the runner doesn't actually hand over the baton, the race stops.
public function apply_filters($name, $value) {
if (isset($this->hooks[$name])) {
foreach ($this->hooks[$name] as $callback) {
// We update $value with the result of the callback
$value = $callback($value);
}
}
return $value;
}
Now, when the shipping fee plugin returns 110, $value becomes 110. If you have a second plugin that applies a 10% discount, it will receive 110, subtract the discount, and return 99. This chain is what makes a plugin system powerful.
Distinguishing Actions from Filters
Now, not everything in a plugin system should be a filter. Sometimes you don't want to change a value; you just want to trigger an event—like sending an email after an order is placed. We call these "Actions."
The key difference is that an Action doesn't need to return anything. It just does its job and finishes. I usually implement them as two different methods in the manager to keep the intent clear. If you use a filter for an action, you risk a plugin accidentally returning null or a string and breaking your entire data chain.
class PluginSystem {
private $filters = [];
private $actions = [];
public function add_filter($name, $callback) {
$this->filters[$name][] = $callback;
}
public function add_action($name, $callback) {
$this->actions[$name][] = $callback;
}
public function apply_filter($name, $value) {
foreach ($this->filters[$name] ?? [] as $callback) {
$value = $callback($value);
}
return $value;
}
public function do_action($name, ...$args) {
foreach ($this->actions[$name] ?? [] as $callback) {
// Actions just execute; we don't care about the return value
call_user_func($callback, ...$args);
}
}
}
By splitting these, you're telling other developers: "Use add_filter if you want to modify the data I'm giving you, and add_action if you just want to know that something happened." It's a small architectural choice that prevents massive headaches as your codebase grows.
📋 Practical Task
Building a Custom Order Processing Pipeline
You are tasked with creating a simple order system. Use the PluginSystem logic discussed in the lesson to implement the following:
- Create a
PluginSystemclass that supports bothadd_filterandadd_action. - Implement a "Checkout" process that does three things:
- Starts with a base price of 50.00.
- Applies a filter called
'order_total'(create at least two plugins: one to add a 5.00 service fee and one to apply a 10% discount). - Triggers an action called
'order_completed'(create a plugin that prints "Sending confirmation email to customer...").
- Ensure the final price is printed to the screen and the action message is displayed.
There are no comments for now.