Here is what you will read about in this article: SOLID principles with a focus on Open/Closed principle, inheritance, design patterns.

Please let me first talk a bit about me. It will not be long, I promise (to try 😉).

I have been working at Believe as a Software Engineer for 8 years now. Before that, I had many professional experiences in very different types of structures.

I used the title “Software Engineer”, but it is not quite right, actually.

I am no engineer. I did not graduate from an Engineering School. Instead, I learnt programming by attending efficient but relatively short training courses, and by having fun programming.

And one of my most significant learning experiences was spending 5 years working as an independent developer for my own clients, bringing tailored software solutions to their specific problems and needs, for their small businesses.

You may think that this kind of scale does not require to give too much thought into reusability and clean code or architecture compliance. Or that being a modest code crafter on my own would have kept me away from industrialization logics and long-term maintenance considerations.

Well… You would be wrong 😉 I will try to tell you why and provide examples.

The most obvious argument is that scaling is precisely the ability to transition smoothly from tiny to big. And no one could say at the time how these small businesses would grow. In the eventuality of a consequent growth, if another developer (or team of developers) had to extend the features of the solution, things had to be clean and standard enough for them to easily plug new code in, with the least risk of breaking existing behaviors.

“Clean” and “standard”. Let’s keep these keywords in mind for later.

Another argument relates to how I had to ensure a decent outcome from my little business. Creating generic components that could be reused from one project to another, was a way to achieve this. And how do you create components that can adapt to different contexts? By sticking to principles.

“Principles”. Another key concept to keep on the side for now.

The last argument resides in the mere beauty of it. Beauty of what, exactly? Hang on, we’re getting to it.

1. Clean, standard, principles and beauty

Well, the beauty of code that can be trustingly tested and extended because it respects good practices.

The beauty of code that experienced developers will be able to dive in in no time because it will have a low cognitive cost. And because the intention behind it will be crystal clear to them.

The beauty of code that could be used in school books.

The beauty of code that we would like AIs to be fed with.

The beauty of… clean code, as defined by Martin C. Fowler.

Such code is always built upon the principles behind SOLID acronym, follows standards (such as PHP-FIG PSRs for PHP), and generally heavily relies on proven design patterns.

As you probably already know it, SOLID advocates for isolation of responsibilities (Single Responsibility Principle), securing existing responsibilities (Open/Closed Principle), keeping responsibilities stable no matter the implementation details (Liskov Substitution Principle), getting the right granularity of responsibilities splitting (Interface Segregation Principle), and making sure ties between responsibilities are abstracted into descending control flows (Dependency Inversion Principle).

But “there is no need to care about SOLID to extend existing code!” one could object. “There is inheritance for that!”. Except that…

2. What could go wrong with inheritance?

Let’s take an example.

In a team, a generic class is created to meet a business request:

<?php

class MyGenericClass
{
public function sayHello(): void
{
echo 'Hello';
}
}

A concrete class is set up by Developer A:

<?php

class MyConcreteClassA extends MyGenericClass
{
}

// Calling sayHello() will print:
> Hello

But for specific use cases that rise up a little later, Developer B and Developer C create some other concrete classes.

Here is the class of Developer B:

<?php

class MyConcreteClassB extends MyGenericClass
{
public function sayHello(): void
{
echo 'Hi';
}
}

// Calling sayHello() will print:
> Hi

And here is the class of Developer C:

<?php

class MyConcreteClassC extends MyGenericClass
{
public function sayHello(): void
{
echo 'Hey';
}
}

// Calling sayHello() will print:
> Hey

This a very simple illustration of how a class could extend another, and comfortably tweak a behavior. At this point, everything is still quite fine.

After a little while, the business folks request a few improvements:

<?php

class MyGenericClass
{
private bool $hasAlreadySaidHello = false;

public function sayHello(): void
{
echo($this->hasAlreadySaidHello ? 'Hello again' : 'Hello');
$this->hasAlreadySaidHello = true;
}

public function sayGoodbye(): void
{
echo 'Bye';
$this->hasAlreadySaidHello = false;
}
}

Bang! That is where problems begin. To make sure this new request is applied in every implementation, you have three choices.

1️⃣ Choice 1: go into every override of sayHello() and apply the new behavior (⚠️ and this is tedious work)

<?php

// choice 1: implement the new behavior

class MyConcreteClassB extends MyGenericClass
{
private bool $hasAlreadySaidHello = false;

public function sayHello(): void
{
echo ($this->hasAlreadySaidHello ? 'Hi again' : 'Hi');
$this->hasAlreadySaidHello = true;
}

public function sayGoodbye(): void
{
parent::sayGoodbye();
$this->hasAlreadySaidHello = false;
}
}

2️⃣ Choice 2: trigger a cascading effect and control output from child (⚠️ and this is a risky work)

<?php

// choice 2: trigger a cascading effect and control output from child

class MyConcreteClassB extends MyGenericClass
{
public function sayHello(): void
{
// Because our example is about printing stuff,
// we need to use buffering
ob_start();
parent::sayHello();
$str = ob_get_contents();
ob_end_clean();

echo str_replace('Hello', 'Hi', $str);
}
}

3️⃣ Choice 3: change parent for our convenience (⚠️ and this is risky and (should be) ❌ forbidden)

<?php

// choice 3: change parent

class MyGenericClass
{
// Nobody will see it, I will change this visibility LOL
protected bool $hasAlreadySaidHello = false;

public function sayHello(): void
{
echo ($this->hasAlreadySaidHello ? 'Hello again' : 'Hello');
$this->hasAlreadySaidHello = true;
}
}
<?php

// choice 3: now that parent has been changed I can do what I want

class MyConcreteClassC extends MyGenericClass
{
public function sayHello(): void
{
echo ($this->hasAlreadySaidHello ? 'Hey again' : 'Hey');
$this->hasAlreadySaidHello = true;
}
}

If you choose the third option expect Developer B to come after you soon… 😉 (because you just caused her class to crash).

As you can see, even a small “hello world” example succeeds in showing how using inheritance can quickly become a nightmare (and yet we did not introduce grandchildren… 🤯).

And it all stands in one simple explanation: it is because the O in SOLID was ignored.

3. The O in SOLID

We said earlier that the “O” was for Open/Closed Principle. And that it ensured a role of securing existing responsibilities.

What does it mean?

It means that Developer A, B and C should be able to tweak the behavior established in the parent class. But only what is considered okay to be tweaked by what should remain the authority class: the parent.

From our example, it seems that business folks, at some point, requested a variation in wording. In some use cases they want the application to say “Hello” to their users, for others they want it to say “Hi” or “Hey”.

In fact, business folks, without even knowing it, gave a precious hint on how to implement the feature in a “O-proof” way.

The responsibility of the parent class is to construct the object and describe the general behavior. The responsibility of each implementation is to provide the details. Those responsibilities should be exclusive to one another.

+=======================================+==============+==================+
| Responsibilities | Parent class | Children classes |
+=======================================+==============+==================+
| Construct the object | ✅ | ⛔ |
+---------------------------------------+--------------+------------------+
| Describe behavior (print a greeting) | ✅ | ⛔ |
+---------------------------------------+--------------+------------------+
| Provide details (which greeting word) | ⛔ | ✅ |
+---------------------------------------+--------------+------------------+

Now, this is a cute concept but… how do you do that?

Well, you need several things:

  • Find a way to prevent children classes from diverging in constructing the object
  • Find a way to prevent children classes from changing behavior
  • Find a way to oblige children classes to provide a greeting word
  • Find a way to ensure that children classes are“O-proof” themselves

_ 3a. How to prevent children classes from diverging in constructing the object

Things to do:
☑️ Make sure the constructor of the parent is declared and is final.

<?php

class MyGenericClass
{
private bool $hasAlreadySaidHello = false;

final public function __construct()
{
}

public function sayHello(): void
{
echo($this->hasAlreadySaidHello ? 'Hello again' : 'Hello');
$this->hasAlreadySaidHello = true;
}

public function sayGoodbye(): void
{
echo 'Bye';
$this->hasAlreadySaidHello = false;
}
}

_ 3b. How to prevent children classes from changing behavior

Things to do:
☑️ Make sure the methods of the parent are final

<?php

class MyGenericClass
{
private bool $hasAlreadySaidHello = false;

final public function __construct()
{
}

final public function sayHello(): void
{
echo ($this->hasAlreadySaidHello ? 'Hello again' : 'Hello');
$this->hasAlreadySaidHello = true;
}

final public function sayGoodbye(): void
{
echo 'Bye';
$this->hasAlreadySaidHello = false;
}
}

_ 3c. How to oblige children classes to provide a greeting word

Things to do:
☑️ Turn the parent class to abstract
☑️ Expose abstract methods

<?php

abstract class MyGenericClass
{
private bool $hasAlreadySaidHello = false;

final public function __construct()
{

}

final public function sayHello(): void
{
$greetingWord = $this->getGreetingWord();
$greeting = $this->hasAlreadySaidHello ?
$greetingWord .' again' :
$greetingWord;

echo $greeting;
$this->hasAlreadySaidHello = true;
}

final public function sayGoodbye(): void
{
echo 'Bye';
$this->hasAlreadySaidHello = false;
}

abstract protected function getGreetingWord(): string;
}

☑️ Implement method into children classes

<?php 

class MyConcreteClassB extends MyGenericClass
{
protected function getGreetingWord(): string
{
return 'Hi';
}
}
<?php 

class MyConcreteClassC extends MyGenericClass
{
protected function getGreetingWord(): string
{
return 'Hey';
}
}

_ 3d. How to ensure that children classes are “O-proof” themselves

Things to do:
☑️ Turn the whole class into final

<?php 

final class MyConcreteClassC extends MyGenericClass
{
protected function getGreetingWord(): string
{
return 'Hey';
}
}

This is only my opinion, but as soon as you find yourself relying on grand-children classes, it tells you something about a too complex architecture that will potentially turn into a hard-to-maintain mess… 🫣No class should contest the choices that were made into another class.

4. Wait? Did we not just “close” classes? Where is the “open” in that?

You are right! It seems that we only take care of the “Closed” part of the Open/Closed principle for now.

The “O” in SOLID means Open to extension, Closed to modification. Let’s see how we can allow child classes to extend the parent’s behavior.

First, open a class to extension often means allowing some kind of initialization at creation. But we declared the parent constructor as final a little earlier. Isn’t it a bit counterintuitive?

_ 4a. How (not) to open children classes to extension

Well, from my point of view you should absolutely forbid children to implement their own constructor, here is why:

  • You would end up with some ugly and dangerous parent::__construct() all over in your sub-classes
  • You would potentially cause inconsistencies in the instantiation of your objects, depending on which concrete class they rely on (because tweaking the constructor is often done in the intent of injecting another kind of data)

Here is an example:

<?php

// Parent constructor takes one argument

abstract class AbstractDivergentConstructorExample
{
public function __construct(
private readonly int $someInteger
)
{
echo 'My integer is ' . $someInteger;
}
}

And children classes can easily go:

<?php

// Child constructor takes two arguments

final class DivergentConstructorExample1 extends AbstractDivergentConstructorExample
{
public function __construct(
private readonly int $someInteger,
private readonly string $someString
)
{
// OK, do your stuff, parent
parent::__construct($someInteger);

// But I am doing mine too
echo 'My string is ' . $someString;
}
}

Or even (worse):

<?php

// Child constructor takes no argument at all

final class DivergentConstructorExample2 extends AbstractDivergentConstructorExample
{
public function __construct()
{
// Please be silent, parent
}
}

Doing that is perfectly valid (as of PHP 8.4), although it leads to the pitfalls of:

  • a parent class that looses sight on whether or not its instantiation tasks will be achieved
  • a child class that blindly triggers whatever is done in its parent
  • some consumers that will instantiate objects differently depending on which implementation they choose

So, my opinion is that we were right to declare the parent constructor as final.

But we still haven’t resolved our opening child classes to extension 😉

Well, without further due, here is how we will do that!

_ 4b. How to really open children classes to extension

Back to our sayHello() example:

Things to do:
☑️ Add “hooks” in parent that children can exploit for their own logic

<?php 

abstract class MyGenericClass
{
private bool $hasAlreadySaidHello = false;

final public function __construct()
{
// First hook
$this->initialize();
}

final public function sayHello(): void
{
// ...

// Second hook
$this->prepareSayingHello($greeting);

echo $greeting;
// ...
}

// ...

abstract protected function initialize(): void;
abstract protected function getGreetingWord(): string;
abstract protected function prepareSayingHello(string &$greeting): void;
}

☑️ Use hooks in children to add own logic

<?php 

final class MyConcreteClassC extends MyGenericClass
{
protected function initialize(): void
{
// Whatever would be needed here...
}

protected function getGreetingWord(): string
{
return 'Hey';
}

protected function prepareSayingHello(string &$greeting): void
{
$greeting = '<p>' . $greeting . '</p>';
}
}

Of course our example is a bit silly.

But “Tadaaa! 🎉” we just opened our MyGenericClass to extension while closing it to any unwanted and uncontrolled modification.

Well… you know what? We also got very close to a beautiful pattern 😉

5. Say hello to Template Method

From refactoring.guru:

Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.

Template Method is a way to securely lock an algorithm high-level logic, while creating controlled opportunities for more specific parts of business logic to be implemented in legitimate classes.

The parent abstracts the behavior, the children implement it.

It is based on the same logic that we have been talking about since the beginning of this article 😉

_ 5a. A basic example

Let’s imagine a gaming application, consisting of several mini luck games. Each mini-game is governed by a set of specific rules, determining:

  • how many attempts you have,
  • the kind of result you expect,
  • and the time it takes for your credits to be reset.

Additionally, a specific greeting message is printed when entering the mini-game.

Depending on the mini-game, you would randomly draw a string or an integer of some sort at each attempt. Each playing attempt walks you through a simple mechanism:

  1. your number of credits is checked
  2. if you have no credits left and they cannot be reset, a message is displayed and the game stops, otherwise:
  3. a target outcome is set
  4. a waiting time is observed
  5. a random result is generated
  6. the target and your random draw are compared
  7. a win or lose message is displayed
  8. your credits get decremented

As already mentioned, the only varying elements in the above algorithm are:

  • your initial number of credits in step 1,
  • whether your credits can be reset in step 2,
  • the kind of outcome in steps 3 and 5
    * if a string, is it a colour? a famous quotation? a name? a country?
    * if an integer, is it any number between 0 and 10? a random pair number? a multiplier of 3?
  • how the randomness gets achieved in the same steps 3 and 5,
  • and the waiting time length in step 4.

Of course, using only inheritance “the old [or most spontaneous] way” would work. Default values would be set in abstract class, using protected properties. And concrete classes would override them if needed. But we want to avoid that for all the reasons we covered 😉.

Instead, here is the base class, and the algorithm representation in method play():

<?php

namespace App;

abstract class AbstractLuckGame
{
private const int MAX_ATTEMPTS = 10;

private int $numberOfCredits;
private \DateTimeImmutable $resetCreditsLastDate;

final public function __construct(
protected readonly string $username
)
{
echo $this->greetings();
$this->initialize();
$this->resetCreditsLastDate = new \DateTimeImmutable();
}

private function initialize(): void
{
$numberOfCredits = $this->getInitialNbOfCredits();

if ($numberOfCredits < 1) {
throw new \InvalidArgumentException(
'Number of credits must be greater than 0'
);
}

$maxCredits = AbstractLuckGame::MAX_ATTEMPTS;
if ($numberOfCredits > $maxCredits) {
throw new \InvalidArgumentException(
'Number of credits must be smaller than ' . $maxCredits
);
}

$this->numberOfCredits = $numberOfCredits;
}

final public function play(): void
{
if ($this->numberOfCredits <= 0 && !$this->resetCredits()) {
echo 'Game over!';
return;
}

$expected = $this->getRandomResult();
echo "The expected result is: " . $expected;

echo "Shaking hands...";
sleep($this->getSuspenseTimeInSeconds());

$attempt = $this->getRandomResult();
echo "You got: " . $attempt;

echo ($expected === $attempt ? ' You won!' : ' Sorry, you lost!');

$this->numberOfCredits--;
}

private function resetCredits(): bool
{
$now = new \DateTimeImmutable();
$interval = $this->getResetCreditsInterval();

if ($this->resetCreditsLastDate->add($interval) > $now) {
return false;
}

$this->resetCreditsLastDate = $now;
$this->numberOfCredits = $this->getInitialNbOfCredits();
return true;
}

abstract protected function greetings(): string;
abstract protected function getInitialNbOfCredits(): int;
abstract protected function getRandomResult(): string|int;
abstract protected function getSuspenseTimeInSeconds(): int;
abstract protected function getResetCreditsInterval(): \DateInterval;
}

And this is a concrete mini-game class:

<?php

namespace App;

class ColourLuckyGame extends AbstractLuckGame
{
private const array COLOURS = ['blue', 'yellow', 'pink', 'orange'];

protected function greetings(): string
{
return sprintf(
'Hello user %s! Welcome in the Colour Lucky Game!',
$this->username
);
}

protected function getInitialNbOfCredits(): int
{
return 5;
}

protected function getRandomResult(): string|int
{
return self::COLOURS[array_rand(self::COLOURS)];
}

protected function getSuspenseTimeInSeconds(): int
{
return 3;
}

protected function getResetCreditsInterval(): \DateInterval
{
// 1 hour
return new \DateInterval('P1H');
}
}

Below is a summary of the main design decisions made for this example, why they comply with the Open/Closed principle and how they implement the Template Method pattern.

Design decision #1:
Made the constructor final in base class
Prevents the concrete class from altering the instance creation behavior and arguments

Design decision #2:
Made the property $username readonly with a protected visibility in the base class
Prevents the concrete class from altering the value whilst still being able to access it

Design decision #3:
Kept the properties $numberOfCredits and $resetCreditsLastDate private in the base class
Keeps the base class fully responsible for them, getting their values from concrete class with abstract getters when appropriate

Design decision #4:
Enforced setting of initial number of credits in concrete class with abstract method
Keeps data validation in base class and setting in concrete class

Design decision #5:
Described play algorithm in base class and made the method final
Prevents the concrete class from altering the steps, keeping them strictly identical between all concrete classes

Design decision #6:
Enforced random result generation in concrete class with abstract method
The base class knows what to do with result, the concrete class knows how to get it

When you have started considering this pattern, you keep encountering use cases where it can be adopted.

_ 5b. Use cases in financial domain

This pattern is perfect for calculations where taxes or country-based rules are involved. The abstract class would hold the global operation steps, but would rely on concretions for intermediate amounts calculations.

_ 5c. Notification use cases

In many situations, an application is expected to send all kinds of information to their end users.

Very often, the message construction would be constant but the sending channel would vary. And only the class that actually sends the message would know how to handle errors.

_ 5d. Engine hooks

Whenever you put together some kind of big engine doing large sets of tasks, there are always spaces between them where sub-tasks would like to sneak in for added business value or necessary data preparation of some kind, or output handling (logging, metrics, notification, etc…).

Enjoy!

I hope this gave you a good sense of what is Template Method pattern, and how to use it.

And more generally, I hope it reinforced your passion for clean and robust code 😉

++

In fact, business folks, without even knowing it, gave a precious hint on how to implement the feature in a “O-proof” way.

The responsibility of the parent class is to construct the object and describe the general behavior. The responsibility of each implementation is to provide the details. Those responsibilities should be exclusive to one another.