Functional PHP: How to Do Functional Programming in PHP?

by jay patel




While PHP may traditionally be an object oriented language, one developer offers a looks at how to work with PHP in a more functional way.

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 rarely do we think of 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 ScalaKotlin, and 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 and Functional PHP

In a nutshell, functional programming:

  • Avoids state mutation: Once an object is created, never changes. There aren’t variables like 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 “parsing time,” but it’s actually similar, if not a synonym, especially when using tools like the OpCache to cache the parsing result. It's worth mentioning that types in current versions of PHP are somewhat “weaker” than their counterparts 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 we know in PHP. They are like mathematical functions: the same input always produces the same output (no matter how many times it’s being called). These are called pure functions.

Pure vs Impure Functions

  • Pure Functions

    • There aren’t globals; values are not passed by reference.

    • They don’t have side effects (which is a very interesting property!).

    • They 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, networks, file systems, etc.

    • May produce different results even with the same input parameters.

    • May have side effects.

Be Functional, My Friend

Let’s look at some practical examples of functional PHP :

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 that are directly returning the status:

 

function isPositive(int $number) {

if ($number > 0) {

returntrue;

}

returnfalse;

}

 

The last refactor: remove the if. Finally, this is done in a functional way:

 

function isPositive(int $number) {

return $number > 0;

}

 

 

Functions in PHP

The first important thing is that functions are, more or less, first class citizen in the PHP world :

 

// Function as a variable

$func = function() {

return42;

};

// Function as a return type

functioncreateFunction(){

return function(){return"Thanks for all the fish" ; };

};

$func2 =createFunction();

// Function as a parameter

functiondisplay($func) {

echo$func()."\n\n" ;

}

// Call a function by name

call_user_func_array('strtoupper',$someString);

// objects as functions

classSomeClass{

public function__invoke($param1,$param2) {

[...]

}

}

$instance = new SomeClass();

$instance('First','Second');// call the __invoke() method

 

 

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.

 

function productImperative(array $data) {

if (empty($data)) {

return0;

}

$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)) {

return0;

}

if(count($data) == 1) {

return $data[0];

}

returnarray_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) {

returnfile($file);

}

function formatLines($lines) {

returnarray_map(function($line) {

returnhtmlspecialchars($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 returns the same output and it’s easy to unit test.

 

Don’t Use Loops to Iterate Over Arrays

Loops are imperative and they use some temporary variables that aren’t very readable.

Map

We need to iterate over an array to modify 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 for-each 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)) {

returnfalse;

}

$usersDTO = [];

foreach ($users as $user) {

$usersDTO[] = newUserDTO($user);

}

return $usersDTO;

}

 

In a more functional style we could do the following:

 

function findUsersMap()

{

return array_map("convertUser", getUsers());

}

function convertUser(array $user) {

returnnew UserDTO($user);

}

 

We use array_map instead of for-each and create a pure function whose 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 their city one-by-one.

 

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 simpler and easier to test without temporary variables or for-each + 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 get the sum of the number of pets:

function getAvgPets(array $users) {

returnarray_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 for using pipelines. 

 

Laravel Collection

The Laravel framework has a great library to work with arrays, which are called collections.
It can be used outside Laravel, and is installable via composer:

 

composer require tightenco/collect

Using this library, the objects are immutable and the code is readable and 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

1. Convert the users.

2. Filter only those from Barcelona.

3. Get the list of pets per user.

4. Get the average (method of the library which makes the reduce + calculate the average). 

Conclusion: Functional PHP

We know that 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 and testable, and gives our code 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.



Leave a Reply

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

   Confirm you are not a spammer
   Notify me of follow-up comments by email.