Functional PHP: a first approach

Apiumhub - Apr 24 '18 - - Dev Community

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

  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, 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 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…

Scala generics I: Scala type bounds

Scala generics II: covariance and contravariance

Scala generics III: Generalized type constraints

BDD: user interface testing

F-bound over a generic type in Scala

Microservices vs Monolithic architecture

“Almost-infinit” scalability

The post Functional PHP: a first approach appeared first on Apiumhub.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player