Table of Contents
Functional PHP? Well, PHP is not a functional language but some functional techniques may be used to improve our code: better readability, easier to maintain => cheaper code.
For many years PHP was scripted in a procedural way, all in one file with functions everywhere.
After version 5.*, applications have been written using the Object Oriented paradigm (OO). But few times we think PHP in the functional paradigm. Of course, PHP is not a functional language, but we should be able to use the best of each paradigm. It’s not about OO against functional programming (FP), or to define which one is better.
Due to the fact that PHP is a quite “classical” OO environment, we suggest a “fusion” approach. Our approach goes in the direction of what we use in multiparadigm languages such as Scala, Kotlin, but also Typescript: the one that we call “Object-Functional”.
We still create classes and objects but using a functional mindset. Instead of telling the computer how to solve the problems (aka imperative programming), let’s start telling the computer what we want to achieve (declarative programming).
Functional programming & Functional PHP
In a nutshell, functional programming:
- Avoids state mutation
Once an object is created, never changes. There aren’t variables as we use in PHP. - Uses complex type systems
A type is a classifier for a value. It gives information about what that value will be at runtime, but it is indicated at compile time. They produce, for example, functions with more verbosity (as you can see what the function expects, what the output will be), but as a reward for that, they can detect errors at compile time, as early as in your IDE while coding.
PHP has its own “compile time”, usually labeled as “parsing time”, but it’s actually similar, if not a synonym, especially when using tools like the OpCache to cache the parsing result.
Worth to mention, types in current versions of PHP are somewhat “weaker” than their counterpart
in functional languages, or even when compared with some other “classical” OO languages. - Functions as first-class values
Functions can be used as input or outputs of other functions which allow function composition.
They are not the functions as we know in PHP. They are like mathematical functions: the same input produces always the same output (no matter how many times it’s being called). They are called pure functions.
Pure vs impure functions
- Pure functions
- There aren’t globals; values are not passed by reference
- don’t have side effects (which is a very interesting property!!)
- don’t use any kind of loops (for, for each, while…)
- Impure functions
- Mutate global state
- May modify their input parameters
- May throw exceptions
- May perform any I/O operations: they need to talk to external resources like databases, network, file system…
- May produce different results even with the same input parameters.
- May have side effects
Be functional my friend
Let’s see some functional PHP practical examples:
Avoid temporary variables
Without temporary variables in our code, we avoid having a local state which can produce unwanted results.
In the next example, we keep the status until it’s returned at the end.
function isPositive(int $number) {
if ($number > 0) {
$status = true;
} else {
$status = false;
}
return $status;
}
We can remove the temporary variables returning directly the status:
unction isPositive(int $number) {
if ($number > 0) {
return true;
}
return false;
}
The last refactor: remove the if. Finally, this is done in a functional way:
function isPositive(int $number) {
return $number > 0;
}
Small functions
- They should follow the single responsibility: to do only one thing
- That means they are easier to test
- They can be composed
function sum(int $number1, int $number2) {
return $number1 + $number2;
}
echo sum(sum(3, 4), sum(5, 5));
// 17
Pay attention that we could change sum(3, 4) for 7 and the result would be the same:
echo sum(7, sum(5, 5));
// 17
This is called referential transparency: a function can be replaced by its the result and the final result doesn’t change at all.
Remove state
This may be quite difficult when we are used to writing in an imperative way. In this example, it calculates the product of all the values in an array. To do so, we use some aggregators and a loop to iterate over it.
unction productImperative(array $data) {
if (empty($data)) {
return 0;
}
$total = 1;
$i = 0;
while ($i < count($data)) {
$total *= $data[$i];
$i++;
}
return $total;
}
In cases like this, when there is some repetitive action, the state can be removed with recursion:
function product(array $data) {
if (empty($data)) {
return 0;
}
if (count($data) == 1) {
return $data[0];
}
return array_pop($data) * product($data);
}
Of course, this example is quite simple and it would be better to solve it via reduce:
echo array_reduce([5, 3, 2], function($total, $item) {
return $total * $item;
}, 1);
// 30
Push impure functions to the boundaries
Our PHP applications quite often need some input from external resources and produce some output.
That means it may be difficult to have pure functions. In these cases, we should split our impure code from
the pure one.
For example, using the code from php.net about reading from a file:
Get a file into an array. In this example, we’ll go through HTTP to get
the HTML source of a URL.
$lines = file('https://www.google.com/');
// Loop through our array, show HTML source as HTML source
foreach ($lines as $line_num => $line) {
echo htmlspecialchars($line) . "
\n";
}
It reads the content from an external site (external input) and shows the results to the console (external output). Can we do some functional change here? Sure, let’s slice the code into different functions.
function getFileContent($file) {
return file($file);
}
function formatLines($lines) {
return array_map(function($line) {
return htmlspecialchars($line) . "\n";
}, $lines);
}
print_r(formatLines(getFileContent('https://www.google.com/')));
The result is the same but now we have:
- One impure function (getFileContent) which is easily mocked in our test
- A pure function (formatLines) which always return the same output and it’s easy to unit test
Don’t use loops to iterate over arrays
Loops are imperative, uses some temporary variables and they aren’t very readable.
Map
We need to iterate over an array to modify each of its content.
Example: We receive a list of users from the database and need to return the model that our
application expects.
In an imperative way we’d do something like this: use a foreach telling the computer what to do:
function getUsers() {
return [
["firstname" => "john", "surname1" => "doe", "location" => "Barcelona", "numpets" => 2],
["firstname" => "david", "surname1" => "ee", "location" => "Girona", "numpets" => 10],
["firstname" => "jane", "surname1" => "qwerty", "location" => "Barcelona", "numpets" => 1],
];
}
function findUsers()
{
$users = getUsers();
if (empty($users)) {
return false;
}
$usersDTO = [];
foreach ($users as $user) {
$usersDTO[] = new UserDTO($user);
}
return $usersDTO;
}
In a more functional style would be:
function findUsersMap()
{
return array_map("convertUser", getUsers());
}
function convertUser(array $user) {
return new UserDTO($user);
}
We use array_map instead of foreach and create a pure function that its only purpose is to convert the format of the user.
This version is much more readable than the previous one.
Filter
Iterating over an array but returning only those results that pass some conditions.
Now that we have the list of users, we want to show only those who live in Barcelona.
Again, we need to go through the array and check one by one their city.
function getUsersFromBcn(array $users) {
$bcnUsers = [];
foreach ($users as $user) {
if ($user->getCity() == "Barcelona") {
$bcnUsers[] = $user;
}
}
return $bcnUsers;
}
Or we could ask only for the users from Barcelona:
function getUsersFromBcn(array $users) {
return array_filter($users, "isFromBcn");
}
function isFromBcn(UserDTO $user) {
return $user->getCity() == "Barcelona";
}
This code is much more simple and easier to be tested without temporary variables nor foreach + if loops.
Reduce / fold
Reduce an array to return a value.
Now, we want to calculate the average of pets in the city of Barcelona.
Let’s iterate the users of Barcelona and calculate the number of pets:
function getAvgPets(array $users) {
$numPets = 0;
foreach ($users as $user) {
$numPets += $user->getPets();
}
return $numPets / count($users);
}
Or we could sum the number of pets:
function getAvgPets(array $users) {
return array_reduce($users, "getTotalPets", 0) / count($users);
}
function getTotalPets($total, UserDTO $user) {
return $total + $user->getPets();
}
Using pipelines
Let’s group all the conditions all together:
echo getAvgPetsReduce(getUsersFromBcn(findUsers()));
If we have multiple conditions we might end up with a very long sentence which can be very
difficult to read. Unfortunately, to solve this issue, there isn’t a native approach in PHP.
Fortunately, there are some good libraries to use pipelines.
Laravel collection
Laravel framework has a great library to work with arrays, which are called collections.
It can be used outside laravel, installable via composer
composer require tightenco/collect
Using this library, the objects are immutable and the code is readable, more declarative.
Using our example of the average of pets in Barcelona would be:
$collection = collect(getUsers());
echo $collection->map("convertUser")
->filter('isFromBcn')
->map("getListPets")
->average();
The order of the actions is very clear
- Convert the users
- Filter only those from Barcelona
- Get the list of pets per user
- Get the average (method of the library which makes the reduce + calculate the average)
Conclusion: Functional PHP
We know, PHP is not 100% functional. And changing the way we program in a functional manner is
not an easy task. But we can start applying some of these simple approaches that make our code simpler,
much more readable, testable and with fewer side effects. We can start thinking in a more
declarative way and step by step we’ll be more functional.
We’ll continue talking about Functional PHP in upcoming articles, making our PHP more and more functional.
If you are working on a PHP project and you need help with software architecture or development, let us know! We would be happy to know more about it!
If you are interested in receiving more articles about functional PHP, don’t forget to subscribe to our monthly newsletter here.
If you found this article about functional PHP interesting, you might like…
sNews – completely free, standards compliant, PHP and MySQL driven Content Management System
Scala generics I: Scala type bounds
Scala generics II: covariance and contravariance
Scala generics III: Generalized type constraints
F-bound over a generic type in Scala
Microservices vs Monolithic architecture
Author
-
Experienced Full Stack Engineer with a demonstrated history of working in the information technology and services industry. Skilled in PHP, Spring Boot, Java, Kotlin, Domain-Driven Design (DDD), TDD and Front-end Development. Strong engineering professional with a Engineer's degree focused in Computer Engineering from Universitat Oberta de Catalunya (UOC).
View all posts
7 Comments
Ferdinando Coluccelli
great job, my friend! the explanation is very clear, interesting and useful. thank you very much 🙂
callmedivs
Wonderful explanation!!
Prakash Chatterjee
Great – very useful to all my students and me. Thank you!
Frank József
Outstanding article my friend! Super useful for old Php maniacs who just started learning functional in JS and want to utilize their new knowledge in an older environment.
Rajiv
Can you please provide a github link for all the codes.
Imre
I know i am late to comment on this but i have some serious problems with this approach.
one is an annoyance so to speak (and that is the fault of php) – but the other one is really a generic programming issue i see here.
so, lets start with the simple one. Consistency in PHP (note i am working in php in the last 12+ years)
array_filter($users, “isFromBcn”);
array_map(“convertUser”, getUsers());
you see the parameters are flipped around and there is no real reason to have this back and forth and constantly
array filter -> filter the [data] by this function
array map -> map the [data] with this function
so the order could be standardized (like property_exists, array_key_exists…) it’s a nightmare.
but then we reach the real problem. and that is performance.
it’s all good and nice when you are working on 3 entries in an array
$collection->map(“convertUser”)
->filter(‘isFromBcn’)
->map(“getListPets”)
->average();
but this is looping through in each result multiple times a loop on a result of a loop on a result of a loop on a result of a loop.
while this could be all ONE loop and 3 if statements with that dreaded temporary variable.
As i said, it’s all nice when you are running this on a small array, but when you have to work on thousands if not hundreds of thousands of rows things getting pretty pretty slow (not to mention the copying of data from memory location to memory location)
this is what happens in this code
Map ($input):
$tmp = []
foreach($input as $row) {
$tmp[] = new ObjectOfWhatever($row)
}
return $tmp
Filter ($input):
$tmp = []
foreach($input as $row){
if ($row->someparameter) {
$tmp[] = $row;
}
return row
ListMap($input):
$tmp = []
foreach($input as $row) {
$tmp[]=$row->someProperty
}
return $tmp
Average($input):
$tmp = 0
$index=0
foreach($input as $row) {
$index++
$tmp+=$row
}
if ($index>0)
return $tmp/$index;
else
return 0;
and now
echo Averagte ( ListMap( Filter( Map($users) ) ) )
while we could
$tmp = 0;
$index = 0;
foreach($users as $userArray) {
$user = new SomeObject($userArray)
if($user->someProperty) {
$index++
$tmp += $user->otherProperty
}
}
if ($index>0)
return $tmp/$index
else
return 0
ONE loop instead of many, less data copy, less CPU cycles and obviously less code.
i KNOW this is an example, but the issue is that so many people are doing these “oh it’s easy just stack these commands together and done” and then the application runs like my grandma’s dead dog…
Oriol Saludes
Hi Imre,
thanks for your time and great explanation.
About the inconsistency of PHP I agree with you, nothing we can do but accept it (and check again and again the documentation to see the correct order of the things).
Regarding the loop issue, it’s true that to deal with thousands of rows can be a problem doing this way. The point here is, this is just an example of simplifying a bit the code, do it more readable and less prone to errors (we are avoiding some temporary vars for example) and that may be enough for some applications. I would follow this way first, and if the performance is really bad, then I would change it if needed.