(Not) looping over lists in PHP

Roller coaster train in a loop
Don’t let the awful quality of this illustration throw you for a loop

This is a lazy blog post for people who are too lazy to browse through PHP’s array function documentation. I’ll show three methods that help you create new lists or values based on an existing list: array_map(), array_reduce(), and array_filter().

Map

Let’s say we have a list of numbers that we want to multiply by 2:

$numbers = [1, 2, 3, 4, 5];

We can use array_map() to map (=convert) all values in this list:

$doubledNumbers = array_map(function ($item) {
    return 2 * $item;
}, $numbers);

Note that array_map() does not modify the original list – instead it returns a new list that contains the converted values:

print_r($numbers);        // [1, 2, 3, 4, 5]
print_r($doubledNumbers); // [2, 4, 6, 8, 10]

Here’s the corresponding looping version:

$doubledNumbers = [];
foreach ($numbers as $number) {
    $doubledNumbers[] = 2 * $number;
}

Passing functions

I used an anonymous function in the example above and will keep doing that for the sake of brevity in the remainder of this article. But you can always use other types of functions if you want, like…

Named functions:

function multiplyByTwo($number) {
    return 2 * $number;
}

$doubledNumbers = array_map('multiplyByTwo', $numbers);

Static methods:

class Multiplier {
    static function multiplyByTwo($number) {
        return 2 * $number;
    }
}

$doubledNumbers = array_map('Multiplier::multiplyByTwo', $numbers);

Or instance methods:

class Multiplier {
    function multiplyByTwo($number) {
        return 2 * $number;
    }
}

$multiplier = new Multiplier();
$doubledNumbers = array_map([$multiplier, 'multiplyByTwo'], $numbers);

Reduce

Let’s assume we have a list of $numbers again, but this time we want to reduce it to a single aggregate value, like a sum:

$numbers = [1, 2, 3, 4, 5];

Sounds like a nice job for array_reduce()!

$sum = array_reduce(
    $numbers,
    function ($carry, $item) {
        $carry += $item;
        return $carry;
    },
    0
);

Like array_map(), this calls a user-supplied function for each element. There are some important differences however:

  1. array_map() expects the function as the first and the array as the second argument. array_reduce() on the other hand, expects the array as the first and the function as the second argumentThis is one of the many reasons why PHP is often the butt of jokes.;
  2. Your function now expects two arguments: $carry and $item. $carry is a variable that “carries” the intermediate aggregation result. In this case, it’s a number that’s used to keep track of the sum;
  3. The third argument to array_reduce() can be used to initialise $carry. It’s technically optional, but I would recommend that you always provide it.

We can verify that our $sum is correct by comparing it with the output of array_sum(), which does the same in less lines of code:

print_r($sum);                // 15
print_r(array_sum($numbers)); // 15

The iterative version is much simpler, which is probably why you don’t see array_reduce() “in the wild” very often.

$sum = 0;
foreach ($numbers as $number) {
    $sum += $number;
}

Nevertheless, it’s a pretty powerful function that can be used to implement more high-level array functions.

For instance, you can use it to check if a list only contains true values:

$all = function ($carry, $item) {
    return $carry && $item;
};

$result1 = array_reduce([true, true, true ], $all, true);
$result2 = array_reduce([true, true, false], $all, true);

var_dump($result1); // bool(true)
var_dump($result2); // bool(false)

Or make sure that a list contains at least one true value:

$any = function ($carry, $item) {
    return $carry || $item;
};

$result3 = array_reduce([false, false, false], $any, false);
$result4 = array_reduce([false, true , false], $any, false);

var_dump($result3); // bool(false)
var_dump($result4); // bool(true)

Filter

Suppose that we still have that same list of $numbers, but this time we only want to filter (=keep) odd values:

$numbers = [1, 2, 3, 4, 5];

We can use array_filter() to create a new array that only contains values that we want to keep:

$oddNumbers = array_filter($numbers, function ($number) {
    return $number % 2 !== 0;
});

Here’s the looping version of that same code:

$oddNumbers = [];
foreach ($numbers as $i => $number) {
    if ($number % 2 === 0) {
        $oddNumbers[$i] = $number;
    }
}

As you can see, $oddNumbers will now be an associative array!

This will cause issues when you serialise it into JSON. The result will not be a list, but an object with numeric keys:

print_r(json_encode($oddNumbers)); // {"0":1,"2":3,"4":5}

You can easily fix this using array_values(), which creates a new, re-indexed version of your array:

$oddNumbers = array_values($oddNumbers);
print_r(json_encode($oddNumbers)); // [1,3,5]

Read PHP’s official array documentation if you want to learn more about the other seventy-something array functions!

Summary

Use…

  1. array_map() to convert all values in a list
  2. array_reduce() to turn a complete list into a single value
  3. array_filter() to only keep certain elements