Introduction
In the previous chapter, we discovered a fundamental problem in software architecture.
As applications grow, new features constantly appear:
- SEO metadata
- Advertisements
- Analytics
- Logging
- Caching
- Notifications
- Custom themes
- Third-party plugins
If every feature requires modifying existing framework code, the application gradually becomes difficult to maintain.
We concluded that instead of allowing plugins to edit the framework, the framework should expose extension points where plugins can participate.
One of the most widely used extension mechanisms is the Filter.
Filters are used by CMSs, web frameworks, compilers, game engines, logging systems, middleware pipelines, and countless other software systems.
Although the name "Filter" sounds complicated, the underlying idea is remarkably simple.
A filter receives a value, optionally modifies it, and passes the modified value to the next filter.
That single concept is powerful enough to build entire plugin ecosystems.
What Is a Filter?
A filter is a function that accepts a value, transforms it if necessary, and returns the result.
Mathematically, it can be represented as:
Output = Filter(Input)For example,
Input: Hello World
Filter: Convert everything to uppercase
Output: HELLO WORLDThe important point is that the original code doesn't know how the value was modified.
It simply receives the final result.
The Smallest Possible Filter
Consider the following function.
std::string addExclamation(const std::string& text)
{
return text + "!";
}Input: Hello
Output: Hello!The function transformed the value.
That is exactly what a filter does.
A Filter Is Not an Event
Many beginners confuse filters with events.
Although they look similar, they solve different problems.
A filter changes data.
An event performs an action.
Filter:
Input
↓
Modify
↓
Return New ValueEvent:
Something Happened
↓
Notify Listeners
↓
DoneNotice that events usually don't return anything.
Filters always return something.
A Real Example
Suppose our application displays blog articles.
Without filters:
std::string renderArticle()
{
return article.content;
}Output:
<p>Hello World</p>Now suppose someone wants advertisements inserted.
Instead of changing the renderer, we execute a filter.
content = applyFilters("article.content", content);The renderer still knows nothing about advertisements.
The advertisement plugin modifies the content.
Output becomes
<p>Hello World</p>
<div>Advertisement</div>The renderer didn't change.
Only the filter did.
The Filter Pipeline
One filter is useful.
Multiple filters become extremely powerful.
Imagine three plugins.
Plugin A
Trim spaces
Plugin B
Convert to uppercase
Plugin C
Add starsExecution
Original
Hello
↓
Trim Hello
↓
Uppercase HELLO
↓
Stars ***HELLO***
↓
Final OutputEach filter receives the output produced by the previous one.
Visual Workflow
Original Value
│
▼
+----------------------+
| Filter A |
+----------------------+
│
▼
+----------------------+
| Filter B |
+----------------------+
│
▼
+----------------------+
| Filter C |
+----------------------+
│
▼
Final ValueThis sequence is often called a pipeline.
Why Pass the Value Along?
Suppose each filter always received the original value.
Example
Hello
Uppercase filter
HELLO
Star filter
***Hello***Now which result should the framework use?
The filters are conflicting.
Instead, each filter receives the latest value.
Hello
↓
HELLO
↓
***HELLO***Every modification builds upon the previous one.
The General Algorithm
Every filter system follows nearly the same algorithm.
value = original
for every registered filter
value = filter(value)
return valueThat's all.
Everything else—priorities, plugins, callbacks—is built on top of this simple loop.
Why Filters Return Values
Suppose we have three filters.
A
B
CIf Filter A didn't return anything, Filter B would have nothing to process.
Likewise, Filter C would receive nothing.
Returning the modified value creates a continuous processing chain.
Think of it like an assembly line in a factory.
Raw Material
↓
Machine A
↓
Machine B
↓
Machine C
↓
Finished ProductEach machine works on the result of the previous one.
The Framework Doesn't Know the Filters
This is one of the most important ideas.
The framework writes only this:
content = applyFilters("article.content", content);It never writes
content = seoPlugin(content);
content = adsPlugin(content);
content = analyticsPlugin(content);The framework has no knowledge of any plugin.
Instead, it simply asks:
"Who wants to modify this value?"
That makes the architecture extensible.
Registering Filters
Before a filter can execute, it must be registered.
Conceptually,
Register Filter
↓
Store Callback
↓
WaitNothing happens immediately.
The framework simply remembers that someone is interested in a particular hook.
Applying Filters
Later, the framework reaches an extension point.
applyFilters("article.content", html);Internally,
Find every filter registered under
article.content
↓
Execute them one by one
↓
Return final valueThe caller doesn't know how many filters exist.
It doesn't even know whether any filters exist.
Multiple Filters
Suppose four plugins register.
SEO
Ads
Translator
Markdown ParserExecution becomes
Original HTML
↓
Markdown
↓
Translation
↓
SEO
↓
Ads
↓
Final HTMLEach plugin performs only its own responsibility.
None of them knows about the others.
Additional Parameters
Sometimes filters need more than just the value.
Imagine rendering a page.
Besides the HTML, a plugin might also need:
- Current user
- Current language
- Current theme
- Current page
Conceptually,
Filter(
HTML,
CurrentUser,
Language,
Theme
)Only the first parameter is modified.
The remaining parameters provide context.
For example, an advertisement plugin may display different advertisements depending on the current language.
Execution Order
Suppose two filters exist.
Filter A Compress HTML
Filter B Inject AdvertisementIf compression runs first,
the advertisement can no longer be inserted correctly.
Correct order:
Inject Advertisement
↓
Compress HTMLExecution order matters.
That is why filter systems support priorities.
Priorities
Each filter receives a priority number.
Example
Priority 10
SEO
Priority 20
Advertisements
Priority 30
CompressionExecution follows numerical order.
SEO
↓
Advertisements
↓
CompressionPriorities make execution deterministic.
No matter how many plugins are installed, the framework always knows which one executes first.
What Happens If No Filters Exist?
Suppose nobody registers anything.
Framework
content = applyFilters("article.content", content);Internally,
No registered filters
↓
Return original valueNothing breaks.
The framework behaves exactly as before.
This makes filters optional.
Plugins can come and go without affecting the framework.
Advantages of Filters
Filters provide several architectural benefits.
Open for Extension
New behavior can be added without editing existing framework code.
Loose Coupling
The framework never depends on plugin implementations.
Plugin Ecosystem
Independent developers can extend the application without modifying the core.
Reusability
Multiple plugins can reuse the same extension point.
Maintainability
The framework remains small while functionality continues to grow.
Runtime Extensibility
Plugins can be enabled or disabled without rewriting existing components.
Limitations
Filters are powerful, but they require discipline.
If too many plugins modify the same value, understanding the execution flow becomes difficult.
Poorly chosen priorities can lead to unexpected behavior.
Debugging may require inspecting every registered filter.
Excessive filtering can also introduce performance overhead if dozens of callbacks execute for every request.
These issues are manageable, but they highlight the importance of designing hook systems carefully.
Real-World Examples
Filters appear in many different forms.
A compiler transforms source code through multiple optimization passes.
A web framework modifies an HTTP request before it reaches a controller.
A logging library filters log messages before writing them to disk.
An image editor applies brightness, contrast, sharpening, and color correction filters in sequence.
A CMS allows plugins to modify HTML before it is sent to the browser.
Although the domains differ, the underlying idea is always the same:
Pass data through a sequence of independent transformations.Leave a comment
Your email address will not be published. Required fields are marked *
