Three patterns behind most loops
Look at the loops you have written so far and most of them do one of three things:
- Transform every element (add tax to every price).
- Keep some elements (only products in stock).
- Combine all elements into one value (the cart total).
PHP has a function for each: array_map(), array_filter() and array_reduce(). With arrow functions, each becomes one short, readable line, and none of them changes the original array.
array_map: transform every element
array_map() calls a function on every element and returns a new array of the results. The callback comes first:
<?php
$prices = [89.5, 18.75, 24.99];
$withTax = array_map(fn($price) => round($price * 1.18, 2), $prices);
print_r($withTax);
$names = ["mechanical keyboard", "ceramic mug set"];
print_r(array_map('ucwords', $names));Array
(
[0] => 105.61
[1] => 22.13
[2] => 29.49
)
Array
(
[0] => Mechanical Keyboard
[1] => Ceramic Mug Set
)Compare that with the loop it replaces: create an empty array, loop, calculate, append. array_map() says "the same list, transformed" in one line.
With rows, array_map() can reshape each row, for example to prepare data for display or for JSON:
<?php
$products = [
["name" => "Yoga Mat", "price" => 24.99, "stock" => 80],
["name" => "Puzzle 1000 Pieces", "price" => 19.99, "stock" => 0],
];
$labels = array_map(
fn($p) => $p["name"] . ($p["stock"] > 0 ? "" : " (sold out)"),
$products
);
print_r($labels);Array
(
[0] => Yoga Mat
[1] => Puzzle 1000 Pieces (sold out)
)array_map() keeps string keys when you pass a single array. Passing several arrays walks them side by side: array_map(fn($a, $b) => $a * $b, $prices, $quantities) multiplies matching elements.
array_filter: keep some elements
array_filter() keeps the elements for which the callback returns true. The array comes first here:
<?php
$products = [
["name" => "Mechanical Keyboard", "price" => 89.5, "stock" => 60],
["name" => "Puzzle 1000 Pieces", "price" => 19.99, "stock" => 0],
["name" => "Headphones", "price" => 199.99, "stock" => 25],
["name" => "Water Bottle", "price" => 12.0, "stock" => 150],
];
$inStock = array_filter($products, fn($p) => $p["stock"] > 0);
$cheap = array_filter($products, fn($p) => $p["price"] < 50);
echo implode(", ", array_column($inStock, "name")) . "\n";
echo implode(", ", array_column($cheap, "name")) . "\n";
print_r(array_keys($cheap));Mechanical Keyboard, Headphones, Water Bottle
Puzzle 1000 Pieces, Water Bottle
Array
(
[0] => 1
[1] => 3
)The last line shows an important detail: array_filter() keeps the original keys, so the cheap products are still at keys 1 and 3. Wrap the result in array_values() when you need a fresh list numbered from 0, for example before encoding it as a JSON array.
Without a callback, array_filter() removes every falsy value. That is a quick way to drop empty strings from a list, but remember that it also drops 0 and "0":
<?php
$fields = ["Asha", "", "Delhi", null, "0", "India"];
print_r(array_values(array_filter($fields)));
print_r(array_values(array_filter($fields, fn($v) => $v !== null && $v !== "")));Array
(
[0] => Asha
[1] => Delhi
[2] => India
)
Array
(
[0] => Asha
[1] => Delhi
[2] => 0
[3] => India
)To filter by key as well, pass ARRAY_FILTER_USE_KEY or ARRAY_FILTER_USE_BOTH as the third argument.
array_reduce: combine into one value
array_reduce() walks the array and keeps an accumulator, a running result. The callback receives the accumulator so far and the current element, and returns the new accumulator. The third argument is the starting value:
<?php
$cart = [
["product" => "Mechanical Keyboard", "qty" => 1, "price" => 89.5],
["product" => "Ceramic Mug Set", "qty" => 2, "price" => 18.75],
["product" => "Water Bottle", "qty" => 3, "price" => 12.0],
];
$total = array_reduce($cart, fn($sum, $line) => $sum + $line["qty"] * $line["price"], 0);
$items = array_reduce($cart, fn($count, $line) => $count + $line["qty"], 0);
echo "Total: $total\n";
echo "Items: $items\n";Total: 163
Items: 6Step by step for the total: the accumulator starts at 0, becomes 89.5 after the keyboard, 127 after the mugs and 163 after the bottles. The accumulator does not have to be a number. It can be a string, or an array, for example to group or index rows:
<?php
$cart = [
["product" => "Keyboard", "qty" => 1],
["product" => "Mug", "qty" => 2],
];
$lookup = array_reduce($cart, function (array $carry, array $line): array {
$carry[$line["product"]] = $line["qty"];
return $carry;
}, []);
print_r($lookup);Array
(
[Keyboard] => 1
[Mug] => 2
)For a plain sum, array_sum(array_column(...)) is shorter; array_reduce() shines when the combining step is more than addition.
Chaining them
The three functions combine into small pipelines: filter, then map, then reduce. Read this from the inside out: keep in-stock products, work out each one's stock value, then add them up:
<?php
$products = [
["name" => "Mechanical Keyboard", "price" => 89.5, "stock" => 60],
["name" => "Puzzle 1000 Pieces", "price" => 19.99, "stock" => 0],
["name" => "Headphones", "price" => 199.99, "stock" => 25],
];
$stockValue = array_sum(
array_map(
fn($p) => $p["price"] * $p["stock"],
array_filter($products, fn($p) => $p["stock"] > 0)
)
);
echo "Value of stock on hand: $stockValue\n";Value of stock on hand: 10369.75If a chain becomes hard to read, break it into named steps with variables. A clear foreach is also perfectly good PHP; use these functions when they make the intention plainer.
Common mistakes
- Mixing up the argument order:
array_map($fn, $array)butarray_filter($array, $fn). - Forgetting that array_filter() keeps keys, then getting a JSON object instead of a list.
- Leaving out array_reduce()'s initial value, so the accumulator starts as
null. - Filtering with no callback when
0or"0"are valid values.
Exercise
From the cart below, produce the names of the lines whose line total is over 30, in capitals, as one comma-separated string. Expected: MECHANICAL KEYBOARD, WATER BOTTLE, CERAMIC MUG SET.
$cart = [
["product" => "Mechanical Keyboard", "qty" => 1, "price" => 89.5],
["product" => "Resistance Bands", "qty" => 1, "price" => 14.99],
["product" => "Water Bottle", "qty" => 3, "price" => 12.0],
["product" => "Ceramic Mug Set", "qty" => 2, "price" => 18.75],
];Show one possible answer
<?php
$cart = [
["product" => "Mechanical Keyboard", "qty" => 1, "price" => 89.5],
["product" => "Resistance Bands", "qty" => 1, "price" => 14.99],
["product" => "Water Bottle", "qty" => 3, "price" => 12.0],
["product" => "Ceramic Mug Set", "qty" => 2, "price" => 18.75],
];
$big = array_filter($cart, fn($line) => $line["qty"] * $line["price"] > 30);
$names = array_map(fn($line) => strtoupper($line["product"]), $big);
echo implode(", ", $names) . "\n";MECHANICAL KEYBOARD, WATER BOTTLE, CERAMIC MUG SETKey points
- array_map($callback, $array) returns a new array with the callback applied to every element.
- array_filter($array, $callback) keeps the elements for which the callback returns true, and keeps their keys.
- array_reduce($array, $callback, $initial) folds all elements into a single value such as a total.
- Note the argument order: array_map takes the callback first, array_filter and array_reduce take the array first.