DEV Community

Cover image for Functional Programming in PHP: What, Why, and How
Mauro Chojrin
Mauro Chojrin

Posted on

Functional Programming in PHP: What, Why, and How

If you’ve been around software development for a while, you have probably heard about functional programming. You’re probably wondering whether it is as interesting as people say and if you should go through the effort of learning about it.

Let’s explore what functional programming (FP) is, why you should master it, and how to apply all of these concepts without leaving your language of choice: PHP.

What Is Functional Programming?

Functional programming is a paradigm (a way of thinking about solving a problem and structuring programs). Usually, when people think of programming, they think about the most common paradigm: imperative programming.

In imperative programming, your job is to answer the question of how to solve a problem. You produce a set of instructions to tell a computer what it should do (and in what order) to create a specific output given an input.

We’re not telling you anything you didn’t already know, right? (Other than putting a name to something you’ve been doing your entire life). Bear with us for a minute.

Functional programming's basis is the principle of declarative programming, where your job is no longer to define the how but what and let the computer fill the gaps.

It sounds like magic, doesn’t it? Well, I bet you’re already using some of it. Think SQL.

When you write SELECT * FROM users, you’re not telling the database engine how to get the user information from the disk, build the record set, and more. You’re simply stating your wishes out loud and letting the genie do its work.

Functional programming is a similar idea.

In imperative programming, a function is another name, a routine (a chunk of code you can call any time without re-writing it repeatedly). In functional programming, a function is more closely related to the mathematical concept, as in mapping. This concept creates a more abstract view where the actual mapping computation for a particular input is a secondary concern.

Let’s look at a simple example. Say you have an array of N elements, and you want to find the biggest. Using imperative programming, you’d express the solution somewhat like:

Function GetMaxValue(a): 

Max = a[0] 

For i = 1 to len(a): 
  If a[i] > max: 
    max = a[i] 

Return max 
Enter fullscreen mode Exit fullscreen mode

In FP, you’d write the solution like:

Function GetMaxValue(a): 

If len(a) == 1: 
  return a[0] 
Else: 
  return max(a[0], GetMaxValue(a[1..len(a)])) 
Enter fullscreen mode Exit fullscreen mode

Of course, this on its own doesn’t constitute a whole paradigm. One feature of every functional language is that functions are first-class values. The distinction between function and variable is somewhat blurry.

You can think about functions like a particular data type that happens to be callable. In that sense, there’d be nothing weird about an assignment such as F = GetMaxValue (note the difference between this assignment and F = GetMaxValue([1, 2, 3])).

Also, since a function is nothing but a datatype, nothing prevents you from returning a function from a function or receiving one as a parameter. That’s where it gets interesting.

However, before going deeper, we need to address an important question: why should you care?

Why You Should Learn About Functional Programming

FP creates code that is easier to read, maintain, re-use, and test. It also allows for elegant meta-programming, as you’ll see in the examples below.

Now, let’s jump into the nitty-gritty of PHP implementation.

How to Use Functional Programming in PHP

While PHP is not a functional language, it enables you to take advantage of many exciting functional features. PHP uses tools to implement FP: the callable type and the Closure Class.

Let’s examine some simple examples to see those tools in action.

Say you have an array of Person objects with a method getAge(), and you want to create a list of Persons ordered by age. Using non-functional programming, your code looks somewhat like:

<?php 

class Person 
{ 
  private int $age; 

  public function __construct() 
  {
     $this->age = rand(5, 50); 
  } 

  public function getAge(): int 
  {
    return $this->age;
  } 

  public function isOlderThan(Person $otherPerson): bool 
  { 
     return $this->getAge() > $otherPerson->getAge(); 
  }
}

$persons = []; 

for ( $i = 0; $i < 5; $i++ ) {
  $persons[] = new Person();     
} 

echo "Before: ".PHP_EOL; 
print_r($persons); 

for ( $i = 0; $i < count($persons); $i++ ) { 
  for ($j = 0; $j < count($persons) - 1; $j++ ) { 
    if ($persons[$j]->isOlderThan($persons[$j + 1]) { 
      $aux = $persons[$j + 1]; 
      $persons[$j + 1] = $persons[$j]; 
      $persons[$j] = $aux; 
    } 
  } 
} 

echo "After:".PHP_EOL; 
print_r($persons); 
echo "After:".PHP_EOL; 
print_r($persons); 
Enter fullscreen mode Exit fullscreen mode

This code works just fine, but since usort enables you to sort an array based on any criteria you want, wouldn’t it be better to use it? See how the new version looks:

<?php 
class Person 
{
  private int $age; 

  public function __construct() 
  {
    $this->age = rand(5, 50); 
  } 

  public function getAge(): int 
  {
    return $this->age; 
  } 

  public function isOlderThan(Person $otherPerson): bool 
  {
    return $this->getAge() > $otherPerson->getAge(); 
  } 
} 

$persons = []; 

for ( $i = 0; $i < 5; $i++ ) { 
  $persons[] = new Person();
}

echo "Before: ".PHP_EOL; 
print_r($persons);
usort($persons, 'personOrder'); 

echo "After:".PHP_EOL; 
print_r($persons); 

echo "After:".PHP_EOL; 
print_r($persons); 
Enter fullscreen mode Exit fullscreen mode

You may wonder what that personOrder is that suddenly appeared. It is a good, old function defined like this:

function personOrder(Person $p1, Person $p2) 
{ 
    if ($p1->getAge() === $p2->getAge()) { 
        return 0; 
    } elseif ($p1->getAge() > $p2->getAge()) { 
        return 1; 
    } else { 
        return -1; 
    }
} 
Enter fullscreen mode Exit fullscreen mode

As you can see, the order criteria are entirely isolated from the looping code, making it easy to change if needed (for instance, if you must present the list in descending order). In this case, the string 'personOrder' constitutes the callable, which passes to the usort function.

Another common use of this kind of construct is creating a new array based on a given function’s sequential application to every element in an array, like this:

$ages = []; 

for ($i = 0; $i < count($persons); $i++ ) {
  $ages[] = $persons[$i]->getAge(); 
}

echo "Ages: ".PHP_EOL; 
print_r($ages); 
Enter fullscreen mode Exit fullscreen mode

This can translate into:

echo "Ages: ".PHP_EOL; 

print_r(array_map( 
    function(Person $p) { 
        return $p->getAge(); 
    }, 
    $persons 
)); 
Enter fullscreen mode Exit fullscreen mode

This example introduces two new concepts.

  • The function array_map, which takes a function as an argument and returns the result of applying it as a second parameter to every element in the array (this is called a higher-order function)
  • The definition of an anonymous function (a closure).

Think about this inline function definition as similar to using a literal value (string, int, float, or others) instead of a variable. It is handy if you’re using it one time. Otherwise, it can backfire.

The last general example is about getting some elements out of an array, also known as filtering.

For filtering, PHP offers you the function array_filter, which takes a function as an argument. The code evaluates this function for each element of the array. Those functions evaluating to true will be present in the resulting collection.

Let’s see how we’d go about getting a list of every person above 18 years old:

echo "Over 18: ".PHP_EOL; 

print_r(array_filter($persons,  
    function(Person $p) { 
        return $p->getAge() > 18; 
    } 
)); 
Enter fullscreen mode Exit fullscreen mode

Now let’s say you don’t want to hardcode the age threshold but receive it via a command-line interface (CLI) parameter.

To bring that context information into your anonymous function, you use the keyword use, like this:

$threshold = $argv[1]; 

echo "Over $threshold: ".PHP_EOL; 

print_r(array_filter($persons,  
    function(Person $p) use ($threshold) { 
        return $p->getAge() > $threshold; 
    } 
));
Enter fullscreen mode Exit fullscreen mode

By now, you’re probably thinking that this is a little too much work just to save yourself from writing a couple of foreach here and there, right? Well, of course, this is just the tip of the iceberg. You can do much more using FP.

Consider the following code, for example:

<?php 

function f(Closure $g, int $p1): Closure 
{
   return function($p2) use ($g, $p1) { 
     return $g($p1, $p2); 
   }; 
}

echo f(function(int $arg1, int $arg2) { 
    return $arg1 + $arg2; 
    }, 3)(5); 
Enter fullscreen mode Exit fullscreen mode

This code looks strange, right? When would you possibly need something like this?

Let's examine a couple of real-life examples using these structures.

Real-Life Functional Programming in PHP Examples

Generic Cache Mechanism Based on Callbacks

Consider a company producing and maintaining a social network for amateur travelers. At one point, they discovered some website pages were taking way too much time to render since someone had created them using many heavy SQL queries.

The developers came up with an idea to cache parts of these pages and stored this pre-generated content in in-memory key-value storage. They implemented this by creating a class responsible for serving the cached content if available or producing it otherwise.

To achieve this goal, the developers wrote a method within that class that looked somewhat like this:

function getCachedContent(string $key, Closure $generationFunction, array $generationParams): $string 
{ 
  if ( !$this->isContentAvailable($key) ) {
    $this->store($key, 
      call_user_func_array($generationFunction, 
      $generationParams)); 
  } 

  return $this->get($key); 
} 
Enter fullscreen mode Exit fullscreen mode

Note: the actual code is a little more complex due to some infrastructure concerns beyond the purpose of this post. It’s a little rusty, but you can still check it out on GitHub.

The whole idea here is that the developers completely separated the cache management code from the code to produce the cached contents by using a few FP tricks. Note how the getCachedContent() method has no clue whatsoever about the function it will eventually call nor its parameters.

Lazy Dependency Injection Container

A different project had a peculiarity: several helper classes performed a wide array of tasks. This project needed these helpers in all sorts of combinations depending on the actual request served. Since there were many simultaneous users, if the developers were not careful with memory and CPU use, the situation could deteriorate quite fast.

The project had a little dependency injection container (a simple class to store all these helpers and access them as needed). The problem is, on every request, the developers had to create all the helpers’ instances instead of just those to produce the response.

The solution is to switch from a structure such as:

$depContainer = new DependencyInjectionContainer(); 
$depContainer->addDependency(new HelperClass()); 
Enter fullscreen mode Exit fullscreen mode

To:

$depContainer = new DependencyInjectionContainer(); 

$depContainar->addDependencyFactory(HelperClass::class, function() { 

    return new HelperClass(); 

}); 
Enter fullscreen mode Exit fullscreen mode

This way, the code only creates helpers when actually needed. It’s the container’s job to check if the object is available and, if not, call the appropriate factory function and return the newly-created instance.

This method results in much more efficient memory use and reduced time to render a page since the code does not execute constructors for unused classes.

Go Functional!

In this article, you learned (a little) what functional programming is all about, why you should pay attention to it, and how to implement it using PHP. Granted, it’s not easy, but the benefits are palpable after you get past the initial friction.

To go deeper into understanding functional programming concepts, read about currying, partial application, and pure functions. For specific ways to practice functional programming within PHP, check out this Functional PHP book and this library.

Discussion (0)