Tony Marston's Blog About software development, PHP and OOP

How Radicore's Hook System Works

Posted on 16th September 2017 by Tony Marston

Amended on 1st September 2018

Introduction
Radicore's processing flow
Hook methods
The Template Method pattern
Summary
Amendment History
Comments

Introduction

What exactly is a software hook? whatis.com provides the following definition:

In programming, a hook is a place and usually an interface provided in packaged code that allows a programmer to insert customized programming.

A hook may also be known as a plug-in, as indicated by the following definition from Wikipedia:

In computing, a plug-in (or plugin, add-in, addin, add-on, addon, or extension) is a software component that adds a specific feature to an existing computer program. When a program supports plug-ins, it enables customization.

In other words where you have a processing flow you provide the ability for a developer to interrupt that processing flow by saying:

When you get to this place you should execute this code

I recently came across an article on How CodeIgniter's Hook System Works, and I was fascinated to see how much effort it requires. After a quick search on the interweb thingy I found Best way to do PHP hooks and Best way to allow plugins for a PHP application which propose the use of the observer or mediator pattern, or having a hook object which contains the customisable methods, or even having a separate script with a name that indicates where and when it should be executed.

This is not how it's done in the RADICORE framework. As a long-time follower of the KISS principle I prefer to avoid unnecessary complications.

Radicore's processing flow

Before you can interrupt a processing flow you must have a processing flow to begin with, plus a method of interrupting that flow on demand. The RADICORE framework was designed specifically for the development of web-based database applications which utilise HTML forms at the front end and a relational database at the back end. It uses a combination of the 3-Tier Architecture and Model-View-Controller design pattern where the application logic is split into the following parts:

The processing flow through these various objects is shown in Figure 1.

Figure 1 - MVC plus 3 Tier Architecture

model-view-controller-03a (5K)

As each user transaction (use case) accesses one or more database tables in some way this led me to the following implementation decisions:

When a Controller calls one of the insertRecord(), getData(), updateRecord() or deleteRecord() methods you will see that it actually steps through a series of internal methods in a pre-defined sequence. Those with a "_dml_" prefix will pass control to the select DAO in order to generate and execute the relevant SQL query. Those with a "_cm_" prefix are customisable methods which provide the hooks. These methods are defined in the abstract table class but do not contain any code, which means that when called they do nothing at all.

Hook methods

All the developer has to do in order to have some custom code executed at a point in the processing flow is to identify which customisable method will be called at that point, then copy the empty method from the abstract class into the relevant concrete class, then fill it with code. At run-time the software will execute the method in the concrete class (if it exists), otherwise it will execute the empty method in the abstract class.

There may be occasions where you have a different user transaction which needs different code in a particular customisable method. Instead of populating the same method with different sets of code which are executed depending on different conditions, I prefer to isolate the different code in its own subclass with their own naming convention.

If you find pretty pictures more interesting than boring text, then perhaps this series of UML diagrams will help you understand the interactions between the controller, abstract class, concrete class (the table subclass) and DAO (the DML class).

In the RADICORE framework all the hook methods follow the same basic pattern:

function _cm_whatever ($fieldarray)
// interrupt standard processing with custom code
// if anything is placed in $this->errors the operation will be terminated.
{
    // customisable code goes here

    return $fieldarray;

} // _cm_whatever

You will notice here all the application data is being passed around in a single array instead of a separate class property for each column. This is because it promotes loose coupling instead of tight coupling as it avoids the need for getters and setters. It also means that the same code will work with any database table regardless of its structure.

Even though these hook methods are called every time, the default implementation in the abstract class does absolutely nothing. The developer can supply an alternative implementation simply by copying the default method into his concrete class, and the adding what ever code is necessary.

Note that the developer never has to write any code to call a hook method as all calls are made within the abstract table class at the appropriate points in the processing cycle. This is how the Hollywood Principle (Don't call us, we'll call you) is implemented, and it is by following this principle that RADICORE is elevated from a library into a framework.

Here is an example from one of my applications:

function _cm_commonValidation ($fieldarray, $originaldata)
// perform validation that is common to INSERT and UPDATE.
{
    if ($fieldarray['start_date'] > $fieldarray['end_date']) {
        // 'Start Date cannot be later than End Date'
        $this->errors['start_date'] = getLanguageText('e0001');
        // 'End Date cannot be earlier than Start Date'
        $this->errors['end_date']   = getLanguageText('e0002');
    } // if
      
    return $fieldarray;
}

Note that some hook methods contain additional arguments.

The Template Method Pattern

My critics, of whom there are many, take great delight in telling me that virtually everything I do is wrong, that I am not implementing the "correct" design patterns, or not implementing them "properly". Phooey. My use of an abstract table class from which every concrete table class is derived, plus my use of these hook methods, fits the description of the Template Method Pattern which was described in the Gang of Four book as follows:

Intent
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

Applicability

The Template Method pattern should be used
Consequences
Template methods are a fundamental technique for code reuse. They are particularly important in class libraries because they are the means for factoring out common behaviour in library classes.

Template methods lead to an inverted control structure that's sometimes referred to as "the Hollywood Principle", that is "Don't call us, we'll call you". This refers to how a parent class calls the operations of a subclass and not the other way around.

The Template Methods call the following kinds of operations:

Notice that it refers to concrete (non-abstract) methods in an AbstractClass which are generally useful to subclasses. This means that my use of a single abstract table class which provides all the possible operations which may be performed on an unspecified database table, then having this inherited by every concrete table class which changes unspecified to a specific database table, is not as crazy as some people would have you believe.

In its overview of the Template Method Pattern this wikipedia article contains the following:

This pattern has two main parts, and typically uses object-oriented programming: At run-time, a concrete class is instantiated. A main method inherited from the base class is called, which then may call other methods defined by both the base class and subclasses. This performs the overall algorithm in the same steps every time, but the details of some steps depend on which subclass was instantiated.

This pattern is an example of inversion of control because the high-level code no longer determines what algorithms to run; a lower-level algorithm is instead selected at run-time.

In The Art of Separation of Concerns it states the following:

One example where inversion of concerns can be seen is in the use of the Template Method Pattern. This pattern is used to generalize the behavior of a process in order to allow variation through inheritance. When applying this pattern to an existing component, the steps performed by the desired process would be generalized and encapsulated in a base class. The existing component would then inherit from the new base class, maintaining only specialized behavior. Other types would then be free to inherit from the base class to provide variant implementations. This would be an example of inverting the concerns of an algorithm sequence.

In the book Applying UML and Patterns the author Craig Larman states the following:

38.11 Framework Design - Template Method Pattern

This pattern is at the heart of framework design. The idea is to define a method (the Template Method) in a superclass that defines the skeleton of an algorithm, with its varying and unvarying parts. The Template Methods invokes other methods, some of which are operations which may be overridden in a subclass.

Thus, subclasses can override the varying methods in order to add their own unique behavior at points of variability.

The Template Method Pattern illustrates the Hollywood Principle - Don't call us, we'll call you.

If you look at this article under the paragraph heading "Naming Conventions" you will see the following:

Naming Conventions

In order to identify the primitive methods is it better to use a specific naming convention. For example the prefix "do" can be used for primitive methods. In a similar way the customizations hooks can have prefixes like "pre" and "post".

This means that my naming convention of "_cm_pre_XXX" and "_cm_post_XXX" for these hook methods is not so strange after all.

If what I have done is implemented a pattern that was recognised and published by acknowledged experts as far back as 1995, then how can I possibly be wrong? No design pattern ever comes with a single definitive implementation, it is merely a description of a recurring problem which identifies the requirements of an implementation, and it is up to the individual programmer to write code which fulfills those requirements. I may not have implemented this pattern in exactly the same way as my critics, but that is irrelevant. Perhaps it is the efficacy of their implementation which should be questioned instead of mine.

Summary

As you should be able to see the system of hooks provided in the RADICORE framework does not require any configuration file or complicated processing like the methods provided in other frameworks. Instead it provides a well-documented processing flow with a series of pre-defined yet empty methods which can be used as hooks for custom code. All the developer has to do is fill in the blanks where necessary. This technique relies on nothing except inheritance. Could it be any simpler?

For additional details please take a look at The Template Method Pattern as a Framework.

Note that in the RADICORE framework there are no deep inheritance hierarchies as each concrete table class inherits from the same abstract table class. No concrete table class is ever extended to create a different concrete table class, but there are circumstances where a concrete table class is extended into a subclass in order to provide a different implementation for certain "hook" methods.

Here endeth the lesson. Don't applaud, just throw money.

Amendment History

01 Sept 2018 Added The Template Method Pattern

counter