Loop is the basic construction in each programming language. All repeatable operations are being done using loops. I think you know how the basic loops like while, do and for work and when you should use one of them. But are you sure? Maybe should you use array functions?

How many of your operations in a loop rely on building objects based on the value? Or selecting only a few items from the array using a predicate? Maybe you use array values to calculate something? Whatever you do, read why you should replacing basic loops by array operations.


Update 2019-12-11

PHP 7.4 comes with support for short closures aka arrow functions. I enriched this article with examples that use this new functionality.


Working with for loop

Take a look at this example. Looks familiar?

$tags = ["php", "javascript", "programming", "loop"];
$entities = [];

for ($i = 0; $i < count($tags); $i++) {
    $entities[] = new Tag($tags[$i]);
}

Everyone knows the for loop. It has three control statements: setup, condition, and instruction after each step. But what exactly this code means?

  1. Create an empty array called $entities.
  2. Set the counter $i to 0 (zero).
  3. Iterate until the counter is less than a number of items in $tags array and increment counter each time by one.
  4. Inside the loop, create a new instance of Tag. The counter points the name of the tag from $tags array.
  5. Add the new object to the $entities array.

There are a lot of operations which largely obfuscate the main goal. They are irrevelant. Why should you care about the number of elements, starting point, or the iteration process?

“A programming language is low level when its programs require attention to the irrelevant."Alan Perlis

Working with foreach loop

You can clean up the previous snippet replacing for in favor of foreach loop.

$tags = ["php", "javascript", "programming", "loop"];
$entities = [];

foreach ($tags as $tag) {
    $entities[] = new Tag($tag);
}

Much better. All unnecessary operations are hidden thanks to the foreach loop. It creates an abstraction over basic iteration process. Thanks to this, you can focus on your main goal instead of repeat the loop control logic again and again.

Foreach loop creates a tiny abstraction over basic iteration process. It allows you to focus on the main goal and gives more readable code in the outcome.

Look how more understandable this code is by reading it step by step:

  1. Create an empty array called $entities.
  2. Iterate over $tags array and make the current element available by $tag variable.
  3. Inside the loop, create a new instance of Tag which $tag as a name.
  4. Add the new object to $entities array.

No details about the process of iteration, incrementation or even number of elements. Great! But both of these pieces of code have hidden side effect. Can you point out where?

Side effect in computer science is a situation when function or expression modifies variables or resources outside its scope.

The problem lies in loops. Both of loops leave extra variable in execution scope – $i and $tag. The variable $tag contains the last element of $tags array. Therefore it’s recommended to unset unnecessary variable directly behind the loop.

$tags = ["php", "javascript", "programming", "loop"];
$entities = [];

foreach ($tags as $tag) {
    $entities[] = new Tag($tag);
}

echo $tag; 

unset($tag);

You can improve this code, even more, by identifying your main goal. What is the main goal in above example? I defined an array of tags and I want to transform them to an array of Tag objects. Each element from array should be mapped to the new one.

Array functions to the rescue

Introduce mapping

I revealed one of the array operations – mapping. This operation allows you to define a function which will be executed over each element in the array. They create a new array contains the mapped entries. In PHP there is a function called array_map.

We’ll change the code a bit to use the array_map function.

For PHP < 7.4:

$tags = ["php", "javascript", "programming", "loop"];

$entities = array_map(function ($tag) {
    return new Tag($tag);
}, $tags);

Since PHP 7.4:

$tags = ["php", "javascript", "programming", "loop"];

$entities = array_map(fn($tag) => new Tag($tag), $tags);

No loop control logic, no side effects. I just defined a function that should be executed on each element. The returned value of supplied function should return the new element.

graphical representation of map array function
The Map function takes an argument and returns the new value.

Now, let’s try to explain this code:

  1. Map each element of $tags array to the new one using a supplied callback function. This function defines how to map single element.

That’s it. There is no step 2. The code is easier to read and understand, and communicates proper intentions.

graphical representation of map result
The result of map function. All items were mapped to the new ones.

Some people don’t like the anonymous function. Just remember that array_map expects a callable. You can pass it by string (e.g. when you want to use the internal function), variable, array – the choice is yours.

Meet the filter function

Sometimes you have a need to select only elements from the array that satisfying defined criteria. To accomplish it you could create a loop which conditionally adds elements to the new array.

$stock = [
    ['name' => 'Phone', 'qty' => 0],
    ['name' => 'Notebook', 'qty' => 1],
    ['name' => 'SDD Drive', 'qty' => 0],
    ['name' => 'HDD Drive', 'qty' => 3]
];

$inStock = [];

foreach ($stock as $item) {
    if ($item['qty'] > 0) {
        $inStock[] = $item;
    }
}

unset($item);

You already know that basic loops have a noticeable overhead. Instead, you should use the array_filter function.

For PHP < 7.4:

$stock = [
    ['name' => 'Phone', 'qty' => 0],
    ['name' => 'Notebook', 'qty' => 1],
    ['name' => 'SDD Drive', 'qty' => 0],
    ['name' => 'HDD Drive', 'qty' => 3]
];

$inStock = array_filter($stock, function ($item) {
    return $item['qty'] > 0;
});

Since PHP 7.4:

$stock = [
    ['name' => 'Phone', 'qty' => 0],
    ['name' => 'Notebook', 'qty' => 1],
    ['name' => 'SDD Drive', 'qty' => 0],
    ['name' => 'HDD Drive', 'qty' => 3]
];

$inStock = array_filter($stock, fn($item) => $item['qty'] > 0);

You probably note that the order of arguments in array_filter function is different than e.g. in array_map function. About the PHP inconsistency may write a book but it goes beyond the boundaries of this article.

In this example, array_filter also takes a callable. This function is a predicate. They must return a boolean value which defines whether element must be included in the resulting array or not.

graphical representation of filter array function
Representation of the filter function. For “green triangles” it returns true, for anything else it returns false.

If you don’t supply the predicate function array_filter returns all items except false. Maybe is this the reason why the order of arguments is different than in other array functions?

graphical representation of filter result
The result contains only elements matched the criteria from filter function.

Reduce array to single value

Maybe you remember a few situation when you need to iterate over an array to e.g. sum any value. Take a look at below example. The goal is to get total points for the team.

$team = [
    ['name' => 'Marry', 'points' => 11],
    ['name' => 'Jacob', 'points' => 7],
    ['name' => 'Joanna', 'points' => 6],
    ['name' => 'Tony', 'points' => 8]
];

$total = 0;

foreach ($team as $person) {
    $total += $person['points'];
}

unset($person);

We’ll switch immediately to the array_reduce function:

For PHP < 7.4:

$team = [
    ['name' => 'Marry', 'points' => 11],
    ['name' => 'Jacob', 'points' => 7],
    ['name' => 'Joanna', 'points' => 6],
    ['name' => 'Tony', 'points' => 8]
];

$total = array_reduce($team, function ($carry, $person) {
   return $carry + $person['points'];
}, 0);

Since PHP 7.4:

$team = [
    ['name' => 'Marry', 'points' => 11],
    ['name' => 'Jacob', 'points' => 7],
    ['name' => 'Joanna', 'points' => 6],
    ['name' => 'Tony', 'points' => 8]
];

$total = array_reduce($team, fn($carry, $person) => $carry + $person['points'], 0);

The first impression – there is a more code than in the previous snippet!

You’re right. But the number of lines/words/characters shouldn’t be the most important criteria. Especially if you want to write clean, understandable and side-effects free code.

Sometimes is necessary to write more words to get a more understandable and side-effect free code.

I hope you have seen a difference in the callback function. There are two arguments. The former, accumulator (I call it $carry) contains the reduced value and the latter keeps the current element. The callback function handles returning the new value of $carry.

graphical representation of reduce array function
Reduce function takes two arguments – element and accumulator. In outcome, it returns a new value of the accumulator.

Value of $carry is also passed to the next callback execution. Finally returned from the array_reduce function after all. The third argument of this function is the initial value of $carry.

graphical representation of reduce result
The result of reduce function is usually a single value. In this case array of triangles was reduced to the sum of its elements.

Mixed operations during iteration

Let’s consider case when you want to do something with elements which are satisfying defined criteria, e.g.

$players = [
    ['name' => 'Mike', 'level' => 5],
    ['name' => 'Ray', 'level' => 6],
    ['name' => 'Ana', 'level' => 5],
    ['name' => 'Joe', 'level' => 2]
];

$advancedPlayersNames = [];

foreach ($players as $player) {
    if ($player['level'] > 4) {
        $advancedPlayersNames[] = "Adv." . $player['name'];
    }
}

unset($player);

Inside this loop, you could find both filter and mapping. We want to get names of players whose level is more than 4. To accomplish it using the array methods you should split this problem into small parts.

For PHP < 7.4:

$players = [
    ['name' => 'Mike', 'level' => 5],
    ['name' => 'Ray', 'level' => 6],
    ['name' => 'Ana', 'level' => 5],
    ['name' => 'Joe', 'level' => 2]
];

$advancedPlayers = array_filter($players, function ($player) {
    return $player['level'] > 4;
});

$advancedPlayerNames = array_map(function ($player) {
    return "Adv" . $player['name'];
}, $advancedPlayers);

Since PHP 7.4:

$players = [
    ['name' => 'Mike', 'level' => 5],
    ['name' => 'Ray', 'level' => 6],
    ['name' => 'Ana', 'level' => 5],
    ['name' => 'Joe', 'level' => 2]
];

$advancedPlayers = array_filter($players, fn($player) => $player['level'] > 4);

$advancedPlayerNames = array_map(fn($player) => "Adv" . $player['name'], $advancedPlayers);

So as you see there is no big deal. Instead of defining an algorithm to extract final data, think more about the process. It contains steps like mapping, filtering and reducing. Is this code excessive? Maybe. Is it easier to read and understand? I think yes.

Summary

If you don’t use the array functions yet, just try. I highly encourage you to write your loops in this way. You’ll be surprised how easy to understand will be your code particularly for your coworkers or even you after a long long time. This approach brings us closer to the next interesting things – Collections. Personally, I prefer array functions over basic loops. In my opinion, they’re more meaningful and expressive, because the code is more declarative – it means that I define what I want to reach instead of describing the way how to do it.

Of course, if you work on the critical system you should consider this advice. Array functions are a little bit slower than basic loops but I think that there is no significant impact on performance.

Resources


Featured photo by Photo Design Lab on Unsplash.