Procedural programming in PHP

Published on 2021-03-30. Modified on 2023-11-03.

In this article we'll take a look at some of the benefits of procedural programming in PHP, and in general, and what you need to do in order to do it well.

Table of content

Introduction

If you come from an object-oriented background and have never studied procedural programming you have most likely only heard bad things about procedural programming, but procedural programming is not bad.

Let's take a small trip down the memory lane of programming paradigms. It will help us firmly grasp what it's all about.

A small trip down the memory lane of programming paradigms

Non-structered programming

Non-structered programming is the earliest way of programming. Assembly language and early versions of FOCAL, BASIC, Fortran, and COBOL, among others, are examples of programming languages that follow a non-structured way in coding.

This means that code is written in a sequential manner with unstructured jumps to labels or addresses of instruction. The lines are typically numbered or labeled.

This is a simple example in BASIC.

10 INPUT "What is your name: "; N$
20 INPUT "How old are you: "; A$
30 PRINT "Hello "; N$
40 IF A$ > 80 THEN GOTO 60
50 PRINT "You are still young!"
60 PRINT "You are getting old!"
70 END

Non-structured programming is not suitable for the development of large programs and it does not allow re-usability of code. You cannot avoid breaking the rule of DRY. Non-structured programming became famous for creating spaghetti code and as a result "structured programming" was invented.

Structured programming

Structured programming, in contrast to non-structured programming, tries to improve the clarity, quality, and development time by making more extensive use of control flow constructs such as loops and conditions with if, then, else, endif statements, and iterations of blocks with for, while, do..until statements.

Blocks are also used to group statements together.

This is a simple example of a conditional statement in C:

if (age < 80) {
    printf("You are still young!\n");
} else {
    printf("You are getting old!\n");
}

Procedural programming

Procedual programming takes structured programming one step further by adding functions. Functions are also called "procedures" based on the concept of a "procedure call".

Sometimes people call procedural programming "structured procedural programming" because they want to emphasize that "procedures", i.e. functions, was added to the structured way of programming.

With functions it becomes possible to organize code into unique blocks that can be reused and it becomes much easier to avoid the problems of DRY. This makes the overall organization of code much better.

This is a very simple example of the usage of a function in PHP:

function add_integers(int $a, int $b): int
{
    return $a + $b;
}

echo add_integers(3, 5);

In procedural programming all functions are available to any part of a program as global functions. In small programs this isn't a problem, but as the size of a program grows, a small change to one function can affect many other functions if they are depending on that particular function. This can particularly be a problem when huge teams of people work together on the same code base.

Functional programming

Functional programming takes the concept of functions a step further. In functional programming, functions are treated as "first-class citizens", meaning that they can be assigned to variables, passed as arguments to other functions, and returned from other functions. Functional programming encourages that programs are written mostly using functions.

In functional programming a pure function is one that relies only on its inputs to generate its result, it cannot have access to any global state, and given the same input, it will always produce the same result. Furthermore, it must not produce any side effects (any change outside the function's own local scope).

Functional programming also relies on the idea that code modularity and the absence of side effects makes it easier to identify and separate responsibilities within the codebase. This therefore improves the code maintainability.

Functional programming has its roots in academia and it has historically been less popular, but many functional languages are seeing use today in industry.

Object-oriented programming

Object-oriented programming takes procedural programming a couple of steps further.

Object-oriented programming started out as a new technique which allowed data to be divided into separated scopes called "objects". Only specific functions belonging to the same scope could access the same data. This is called encapsulation.

In the beginning objects where not called objects, they where just viewed upon as separate scopes. Later when dependencies were reduced and connections between functions and variables inside these scopes where viewed upon as isolated segments, the result gave birth to the concepts of "objects" and "object-oriented programming".

An object is in essence simply a collection of functions and variables now referred to as "methods and attributes".

Encapsulation is the process of putting data, such as variables, constant, arrays, lists, and other types of data, and functions into a single unit. Encapsulation means to "encase" or "enclose" something, and it is achieved in, for example, Java, C++, and PHP by the class concept. By putting related data and functions into a single unit, we can better organize our code into separate blocks with related features and functionality.

Abstraction is the process of hiding and securing data from other functions. In Java, C++, and PHP, classes are used to encapsulate and keywords such as private and public determine exactly how the scope of classes and methods work, they hide or expose the attributes and methods of the class. Abstraction focuses on just the relevant data of an object and hides all the details which may or may not be for generic or specialized behavior. It hides the background details and emphasizes on the essential points to reduce complexity and increase efficiency.

Inheritance is the process of obtaining the attributes and methods from one class to another class. With inheritance you avoid DRY as you can avoid duplicating code across multiple classes and functions. Instead classes can inherit functions of other classes and even expand their functionality. Inheritance is a way to reuse code and allow independent extension of the software via public classes and interfaces. The relationships of objects give rise to a hierarchy. Inheritance was invented in 1967 for the programming language Simula 67.

Polymorphism is the ability in programming to present the same interface for different functionality. Poly means "many" and "morph" means change or form. It's the ability to have multiple functions with the same name, yet with different functionality or implementation. With polymorphism we can have 3 different functions all called printOut, depending on the context one function could output HTML, another JSON, and the last XML.

All-in-all we can say that object-oriented programming is all about organizing code. It is an extension of procedural programming, and it is about avoiding a global scope and about sharing resources between classes and functions. You extend functions by "borrowing" their blueprints without actually affecting the original code. And you override functions without affecting the original code. And you isolate scope by putting methods (functions) and attributes (variables) into classes.

So object-oriented programming relies on these four characteristics: Encapsulation, abstraction, inheritance, and polymorphism.

How these different ideas are implemented vary from programming language to programming language.

In my humble opinion, object-oriented programming has some good ideas, but in general its usefulness is highly exaggerated. Often object-oriented code is very verbose and complex whereas procedural code seems to be the sweet spot.

I cannot avoid to think about a Danish proverb that says:

Too little and too much spoils everything.

Confusion in PHP land

In PHP land someone was either drunk or confused because they thought that when PHP introduced the object-oriented paradigm into PHP the MVC pattern was a good fit for PHP web applications - even though the fact is that you cannot truly do MVC with PHP.

Just like everyone believes that they are making RESTful APIs, even though an API cannot be RESTful, the idea that you should do MVC with PHP became so widespread that almost all frameworks and content management systems still boasts about how they use the MVC pattern.

Image of a drunk smiley

Perhaps these people were coming from Java Swing or Ruby on Rails or perhaps they where infatuated with Martin Fowlers book "Patterns of Enterprise Application Architecture".

In any case, what they were aiming for was separation of concern (SoC) in code, but that has nothing to do with MVC. MVC is related to developing user interfaces, such as GUIs.

Then someone thought that because you're doing MVC you now need a single front controller to control the views and fetch data from the model.

Despite the fact that the very architecture that runs web applications are shouting very loudly that the web server is the front controller, people decided to invent yet another layer of much less efficient abstraction code on top of that.

Then began all the object-oriented dreck!

What belongs in the controller? Hmm. What should go into the view? You cannot stuff this into that! Oh, wait, the "Gang of Four" (GoF) has a design pattern for that! Hmm. It doesn't quite fit. Hmmm. It must fit! Let's take it apart and MAKE IT FIT!

A man sits exhausted at his computer

The result was that a large part of PHP websites on the interweb suddenly became much slower.

Good and correct principles

The things that constitutes the good parts of object-oriented programming theory, like avoiding global mutable state, and declaring and using properties correctly (because of the protection it provides), etc., are basically just plain and simple good programming style in any programming language. You don't need object-oriented programming in order to achieve that. An experienced procedural programmer avoids global mutable state too, etc.

Best practice always has a context and no rule should be followed just for the principle of following it.

What about functional programming?

Functional programming is sometimes treated as synonymous to pure functional programming, a subset of functional programming which treats all functions as deterministic mathematical functions, or "pure functions".

When a pure function is called with some given arguments, it will always return the same result and cannot be affected by any mutable state or other side effects. Proponents of purely functional programming claim that by restricting side effects, programs can have fewer bugs, be easier to debug and test, and be more suited to formal verification.

The fact is that restricting side effects is just good procedural programming style! There is absolutely nothing special or new about that.

Impure functions can have side effects, such as modifying the program's state, and this is not something a function normally should do. A function should take in some data as input, work locally on the data, and then output the resulting data. This is good procedural programming practice. However, exceptions exist, again it depends on the context.

The functional paradigm was mainly invented in academia and didn't have a huge hype machine behind it at first (like the object-oriented paradigm had). Many of the ideas that originates from this paradigm are old and have been refined multiple times in different programming languages and some of them are really good. This is why programmers such as Ken Thompson and Rob Pike decided to implement some of these ideas into the Go programming language.

However, since the functional paradigm has gained in popularity, the corporate hype machine has slowly awakened again and have created new and shiny programming languages advertised as purely functional. Again we see a new fanatical group of frantic people claiming that if you're not programming in the functional programming paradigm, you're doing everything wrong and that is the cause of all the evil in the world of software.

The result of this is very similar to the problems in object-oriented circles. Rather than taking a moderate approach, now every single iteration in code MUST be done with a recursive function ONLY, and if you do not do this, then you're a heretic that shouldn't be allowed near a computer.

The fact is that sometimes a simple for loop is just many times faster and much more easy to read and understand than a function that takes an anonymous function as a parameter, which then calls itself recursively a number of times.

Understand how the computer works

The best way to really "get" the procedural programming paradigm is by understanding how the computer works. Not in all the hardware details, but how it handles instructions (at least in a theoretical way).

When the computer is running it is following a preset path of instructions, one instruction at a time. Even when you do multitasking, the computer is still doing one instruction at a time, but it is just doing it so fast that it looks like it is doing several things at once.

No matter what programming language you're using, whether it is a compiled language like C or Go, or whether it is an interpreted language like PHP or Python, your code is eventually translated into binary code that the CPU can understand and run - one instruction at a time.

The binary code represents the flipping of "on" and "off" switches in the CPU. Some of these "switches" represents data, i.e. the values you store in variables, while others represent instructions to the CPU about what it needs to do with the data.

From this perspective it is very clear that programming paradigms are trying to solve people problems, not computer problems.

Feeding the computer only binary instructions is very time consuming, difficult and error prone hence clever people invented the assembly programming language, then later higher level programming languages were invented, such as the C programming language, in order to solve different programming problems.

Some programming languages where invented by people dealing mostly with theory, like in academic circles, while other programming languages where invented by people dealing mostly with real-life problems.

As mentioned, the BASIC programming language is a structured language that executes one instruction at a time.

10 PRINT "Hello!"
20 INPUT "Are you happy today (y/n)?"; ANSWER$
30 IF ANSWER$ = "y" THEN GOTO 60
40 PRINT "It's sad that you're not happy!"
50 END
60 PRINT "I'm happy that you're happy!"
70 END

The BASIC program follows a strict set of steps, interpreting one instruction at a time.

  1. It prints "Hello!" to the screen.
  2. Then it prints a question and waits for input from the keyboard. The input from the keyboard is put into the variable "ANSWER" and stored in memory.
  3. Then it evaluates the value of the variable "ANSWER", which represents the binary address of the memory location where the value is stored, and it then compares that to the value "y".
  4. If the value located at the memory location is equal to "y" it then jumps ahead to the instruction located at line 60 and prints the message "I'm happy that you're happy" to the screen and ends the program.
  5. If the value is not equal to "y" it continues to the next step and prints the message "It's sad that you're not happy" and ends the program.

This is a simple set of instructions that is easy to follow as long as the program is small. However, when a program gets big and contains many GOTO instructions, it becomes extremely difficult to build and navigate the code.

With BASIC we only have a global mutable state, but with a procedural programming language, we also have functions and local scope, and we have function parameters. We still have variables that represent memory addresses, and we also still have loops, even though we don't use line numbers to perform them.

Procedures, i.e. functions, give us the advantage that we can combine multiple lines of instructions into a single block and then name that block and reuse that block. Then rather than jumping around using line numbers we simple issue instructions one at a time by declaring variables and functions and then by calling these functions and passing in the variables. One could argue, though it is not correct because it is more than that, that a procedural programming language is BASIC on steroids.

When you program in a procedural programming language you don't think about theory, you focus on the problem you need to solve and on the path of execution, solving one problem at a time. This often makes programming in a procedural language much more effective than programming in an object-oriented language.

Pragmatic programming

Pragmatic programming is about keeping focus on the problem, not on theory.

The Go programming language is a really good example of a modern pragmatic programming language. Go is not a pure procedural language as it has borrowed a few principles from both the object-oriented paradigm and the functional paradigm, but it is still mainly a procedural language.

I mention Go because it was developed by some of masters in the field of programming, like Ken Thompson, Rob Pike, and Robert Griesemer. The interesting part is that when they designed Go they avoided a lot of the common modern ideas about object-oriented programming and only incorporated a few basic principles. This was by design because the usefulness of object-oriented programming is highly exaggerated.

I believe that most of the hype surrounding object-oriented programming has settled. However, in some areas of the tech industry, it is still preached as "the modern way" of developing software and if you prefer the procedural paradigm, then clearly you are hopelessly outdated and wrong.

Go has a splendid documentation on how to write effective and idiomatic Go code.

Procedural programming in PHP

You don't need to give up classes and autoloading if you do procedural programming in PHP. Classes is just another way to organize your code. Whether you put related functions into a single file and give it a relevant and descriptive name, or you do the same, yet put a class around the functions and call them "methods", doesn't really matter. That is still just procedural programming.

Some people believe they are doing object-oriented programming just because they are using classes, but that is not the case. Unless you incorporate the Three Pillars of object-oriented programming, then you're still just doing procedural programming.

Still, you don't need classes and you don't need autoloading and there are several benefits to avoiding this.

Without autoloading you only include files where they are actually needed, and you do this manually and conditional. In some cases this makes the code execution much faster. Another advantage is that it makes dependencies very explicit and the added benefit is control. It makes you a more responsible programmer because this will force you to think more deeply about how you structure your code and how you organize your files.

As long as you're not using the front controller pattern with PHP, and you structure your code wisely by grouping all related functions together in single files, then you'll end up with a few inclusions in each file and a very performant web application.