CLOSE
Updated on 11 Jul, 202628 mins read 26 views

Introduction

Every software project begins life as something remarkably simple.

A single developer starts with a few files, a handful of functions, and one clear objective: make the application work.

At this stage, software is usually easy to understand. Every feature lives exactly where you expect it to. If a page needs a display a menu, you write the code inside the page. If an article needs SEO metadata, you add a few lines before rendering the HTML. If a user logs in, you write the login logic directly inside the authentication controller.

For a while, this works perfectly.

Then the application grows.

New developers join the project. New features are requested. Third-party integrations become necessary. Suddenly, the software is no longer just an application – it becomes a platform.

This is where one of the biggest architectural problems in software engineering appears:

How can we allow software to grow without constantly modifying its existing code?

The Growth of Software

Imagine you are building a blogging platform.

Initially, displaying a blog post is straightforward.

void renderPost(Post& post)
{
	renderHeader();
	
	renderContent(post);
	
	renderFooter();
}

The function has a single responsibility.

Display a blog post; Nothing more.

The execution flow is simple.

renderPost()
   |
   v
Header
   |
   v
Content
   |
   v
Footer

Everything is clean.

The First New Requirement

A few weeks later, marketing arrives with a request.

“Can we display advertisements inside every article?”

Seems easy.

void renderPost(Post& post)
{
	renderHeader();
	renderAdvertisement();
	renderContent(post);
	renderFooter();
}

Problem solved.

The Second Requirement

Now the SEO team joins.

“Before displaying the page, generate SEO metadata”

The function changes again.

void renderPost(Post& post)
{
	generateSEO(post);
	renderHeader();
	renderAdvertisement();
	renderContent(post);
	renderFooter();
}

Still manageable.

The Third Requirement

Analytics wants to track page views.

trackPageVisit(post);

The function becomes:

void renderPost(Post& post)
{
	trackPageVisit(post);
	generateSEO(post);
	renderHeader();
	renderAdvertisement();
	renderContent(post);
	renderFooter();
}

The Fourth Requirement

Someone wants social media sharing.

injectSocialButton(post);

The Fifth Requirement

Someone wants recommendation widgets.

renderRecommendations(post);

The Sixth Requirement

Someone wants syntax highlighting.

The Seventh Requirement

Someone wants comments.

The Eighth Requirement

Someone wants translation support.

Eventaully the function becomes something like this:

void renderPost(Post& post)
{
	authenticateUser();
	checkPermissions();
	trackPageVisit(post);
	generateSEO(post);
	injectStructuredData(post);
	loadTranslations();
	renderHeader();
	renderAdvertisement();
	renderContent(post);
	renderRecommendations();
	renderComments();
	injectSocialButtons();
	renderFooter();
}

What started as three lines of code has turned into a large collection of unrelated responsibilities.

The Real Problem

Notice something important.

None of these new features are actually part of the original responsibilities.

The original purpose was simply.

Render a blog post.

Instead, the function now performs authentication, analytics, advertising, SEO, recommendation generation, comments, localization, and social media integration.

Every new feature forces developers to modify existing code.

This creates a dangerous dependency between the framework and every feature built on top of it.

Why This Doesn't Scale

Imagine your software has 100 plugins.

Plugin A modifies SEO.
Plugin B inserts advertisements.
Plugin C adds analytics.
Plugin D inserts related posts.
Plugin E modifies HTMl
Plugin F adds a watermark.
Plugin G performs A/B testing.
Plugin H adds structured data.

If every plugin modifies the same function, every update becomes risky.

Multiple developers edits the same files.

Merge conflicts become common.

Testing becomes harder.

Framework upgrades become painful.

Eventually, developers become afraid of touching the core code.

This phenomenon is often called framework erosion, where the framework slowly accumulates unrelated responsibilities until it becomes difficult to maintain or evolve.

Tight Coupling

The previous example demonstrates a classic case of tight coupling.

A component is tightly coupled when it knows too much about the components around it.

In our example, the rendering function knows about:

  • advertisements
  • SEO
  • analytics
  • comments
  • recommendations
  • localization
  • permissions
  • social sharing

None of these should be the rendering function's concern.

The rendering function has become dependent on every subsystem.

Any change to those subsystems forces changes to the renderer itself.

The Open-Closed Principle

One of the SOLID principles addresses exactly this situation.

The Open-Closed Principle states:

Software entities should be open for extension but closed for modification.

The goal is simple.

Instead of modifying existing code whenever new functionality is needed, we should extend the software externally.

A framework should expose extension points rather than requiring direct modification.

This dramatically reduces maintenance costs and allows independent modules to evolve without affecting the core system.

Frameworks vs Applications

Understanding hooks requires understanding the difference between an application and a framework.

In a normal application, your code controls the execution flow.

Application
↓
Your Function
↓
Another Function
↓
Program Ends

You decide exactly which function executes next.

A framework works differently.

The framework controls the execution.

Your code simply plugs into predefined extension points.

Framework
↓
Hook
↓
Your Plugin
↓
Framework Continues

This inversion of control is one of the defining characteristics of modern frameworks.

Rather than your code calling the framework, the framework calls your code when appropriate.

The Need for Extension Points

Instead of hardcoding every possible feature, imagine if the framework simply said:

“I'm about to render a page, If anyone wants to do something here, now is your change”

Or:

“I'm about to display content. Anyone interested may modify it”

Or:

“A user has just logged in. Notify whoever cares”

These moments are called extension points.

An extension point is simply a predefined location where external code is allowed to participate in the application's execution  without modifying the framework itself.

A Better Architecture

Instead of this:

Framework
↓
SEO
↓
Ads
↓
Analytics
↓
Comments
↓
Recommendations

We move to:

Framework
↓
Hook
↓
Plugin A
Plugin B
Plugin C
Plugin D
Plugin E

The framework no longer knows anything about individual plugins.

It only knows that a hook exists.

Plugins register themselves.

The framework remains stable while new functionality is added independently.

Real-World Examples

Hook systems are not unique to content management systems. They appear throughout software engineering.

A browser allows extensions to modify pages before they are displayed.

A game engine allows mods to react when a player joins or when a level loads.

An IDE allows plugins to add new menu items or code inspections.

A build system allows custom steps before or after compilation.

A web framework allows middleware to inspect requests before controllers execute.

Although the terminology differs, the underlying architectural idea remains remarkably similar:

Expose extension points instead of forcing modifications to existing code.

The Birth of Hook Systems

This need for extensibility led to the development of hook systems.

Rather than embedding every feature into the framework, developers introduced a simple but powerful concept:

  • Define named extension points.
  • Allow external modules to register callbacks.
  • Execute those callbacks when the extension point is reached.

From this idea emerged two fundamental types of hooks:

Actions, which allow external code to react to something happening.
Filters, which allow external code to modify data before it continues through the system.

These two concepts form the foundation of plugin architectures in frameworks such as Botble CMS, WordPress, and many other extensible software systems.

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *

Your experience on this site will be improved by allowing cookies Cookie Policy