---
title: 'Array Functions'
source: 'https://academia.sh/en/courses/php-fundamentals/array-functions'
course: 'PHP Fundamentals'
language: en
updated: '2026-10-21T05:03:33+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Filters, maps and reduces an array to a single value with array_filter, array_map and array_reduce'
    - 'Measures that array_search returns false when it finds nothing and records it under the return channel'
    - 'Shows that a match at index zero counts as not found in a loose comparison and that strict equality tells the two apart'
---

# Array Functions

The filter, map and reduce functions; array_search returning false when it finds nothing; a match at index zero being confused with false in a loose comparison; array_combine with mismatched lengths and a negative str_repeat call throwing ValueError.

In the Multidimensional Arrays lesson, the break of a path was heard as `null` and `E_WARNING`. With array functions the channel changes: when the sought element is not found, no level is reported; the function returns a special value, and interpreting that value becomes the job of the calling code. The lesson uses the channel names in the sense they had in the previous lessons: **silent**, **return**, **warning**, **halts**.

PHP's array functions are a broad family, and the lesson does not walk through all of them. Two groups are measured: the trio of filter, map and reduce, which transforms an array, and the functions that search an array for an element. The first is the data flow itself, and the second is the plainest place to see where a problem is heard from.

## Filter, Map and Reduce

The three functions ask three separate questions. Filter answers "which elements stay", map answers "what does each element become", and reduce answers "how do they all come down to a single value". The file below runs all three over the same list of numbers.

```php
<?php
// transform.php — filter, map and reduce
$numbers = [1, 2, 3, 4, 5, 6];

$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
$squares = array_map(fn($n) => $n * $n, $numbers);
$total = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);

echo 'evens: ', json_encode($evens), "\n";
echo 'squares: ', json_encode($squares), "\n";
echo 'total: ', $total, "\n";
echo 'keys of evens: ', implode(',', array_keys($evens)), "\n";
echo 'renumbered: ', json_encode(array_values($evens)), "\n";
```

```
evens: {"1":2,"3":4,"5":6}
squares: [1,4,9,16,25,36]
total: 21
keys of evens: 1,3,5
renumbered: [2,4,6]
```

`array_filter` keeps the keys of the retained elements. Because the keys of the `evens` list are `1, 3, 5`, `json_encode` writes it as an object and not as a list; `array_values` renumbers the keys. This is the same behavior as `unset` in the Indexed and Associative Arrays lesson: an array that looks indexed can stop being indexed after filtering. `array_map`, in turn, carries the keys along with the elements; when a single array is given, the result uses the same set of keys.

**AS10.** `array_reduce` binds the first parameter of the callback to an accumulator value, and the initial value is the third argument. If no initial value is given, the accumulator starts as `null`; when an empty array is reduced, the result in that case is `null`. The lesson always gives the initial value explicitly and does not separately measure the empty-array behavior.

## An Element That Is Not Found

Functions that search for an element report a problem not with a warning but with a return value. `array_search` returns the key of the sought value if it finds it, and `false` if it does not. The listener classifies this behavior with the channel order; `channel.php` is given below in full, and since it has no output block, it is only written to disk.

```php
<?php
// channel.php — helpers that classify which channel a problem is heard through

// Level names of caught warnings collect here; the message text is not kept.
$GLOBALS['heard_warnings'] = [];

set_error_handler(function (int $level): bool {
    $names = [E_WARNING => 'E_WARNING', E_NOTICE => 'E_NOTICE', E_DEPRECATED => 'E_DEPRECATED'];
    $GLOBALS['heard_warnings'][] = $names[$level] ?? 'E_' . $level;
    return true;
});

// Turns a value into a short text together with its type.
function show(mixed $value): string
{
    return match (true) {
        $value === null => 'null',
        is_bool($value) => $value ? 'true' : 'false',
        is_array($value) => 'array(' . count($value) . ')',
        default => get_debug_type($value) . ' ' . $value,
    };
}

// Runs the job and asks, in order, which channel the problem is heard through:
// first a throw, then a warning, then a false/null return; if none of them, silent.
function listen(callable $job): array
{
    $GLOBALS['heard_warnings'] = [];
    try {
        $result = $job();
    } catch (Throwable $error) {
        return ['halts', get_class($error)];
    }
    if ($GLOBALS['heard_warnings']) {
        $levels = implode(',', array_unique($GLOBALS['heard_warnings']));
        return ['warning', $levels . ', result ' . show($result)];
    }
    if ($result === false || $result === null) {
        return ['return', 'result ' . show($result)];
    }
    return ['silent', 'result ' . show($result)];
}

// Runs the code in a separate PHP execution; exit code 255 means execution halted.
function run_separately(string $code): array
{
    exec('php -d display_errors=0 -r ' . escapeshellarg($code) . ' 2>&1', $lines, $exit);
    $channel = $exit === 255 ? 'halts' : 'silent';
    return [$channel, 'lines out ' . count($lines) . ', exit ' . $exit];
}
```

```php
<?php
// search.php — an element not found and ValueError
require 'channel.php';

$observations = [
    'element not found' => fn() => array_search('apple', ['pear', 'quince']),
    'element found'     => fn() => array_search('quince', ['pear', 'quince']),
    'first element'     => fn() => array_search('pear', ['pear', 'quince']),
    'negative repeat'   => fn() => str_repeat('a', -1),
    'combine mismatch'  => fn() => array_combine(['a', 'b'], [1]),
];

foreach ($observations as $name => $observe) {
    [$channel, $evidence] = listen($observe);
    printf("%-20s%-9s%s\n", $name, $channel, $evidence);
}
```

```
element not found   return   result false
element found       silent   result int 1
first element       silent   result int 0
negative repeat     halts    ValueError
combine mismatch    halts    ValueError
```

The first line is the course's `element not found` observation and belongs to the **return** channel: `array_search('apple', ['pear', 'quince'])` returned `false`, no level was reported, and execution continued. The problem is heard only by code that looks at the return value. The second line shows a successful search: `1`, the key of the element found, is a silent result. The third line prepares the source of the problem: when the first element is found, the key returned is `0`.

The last two lines show another channel of the same family of functions. `str_repeat('a', -1)` and an `array_combine` call with two arrays of different lengths throw `ValueError`; the channel is **halts**. When the value of an argument has the right type but lies outside the acceptable range, the function does not quietly produce a wrong result; it stops. Two different channels exist in the same family of functions: "not found" is a return value, and "invalid call" is an exception.

**AS11.** `array_combine` and `str_repeat` are examples of the `ValueError` channel only. Which function gives `ValueError` or `false` under which condition is written in the documentation of each function, and the lesson does not list it; what is measured is that "not found" comes from the return channel and "invalid call" from the exception channel.

## Sorting and Slicing

All sorting functions modify the array in place and treat keys differently: `sort` discards the keys and renumbers, `asort` sorts by value while keeping the keys, and `ksort` sorts by key. `array_slice`, in turn, renumbers integer keys by default.

```php
<?php
// sorting.php — the effect of sorting and slicing on keys
$raw = [3 => 'c', 1 => 'a', 2 => 'b'];

$a = $raw;
sort($a);
echo 'sort: ', json_encode($a), "\n";

$b = $raw;
asort($b);
echo 'asort: ', json_encode($b), "\n";

$c = $raw;
ksort($c);
echo 'ksort: ', json_encode($c), "\n";

$d = $raw;
rsort($d);
echo 'rsort: ', json_encode($d), "\n";

echo 'slice: ', json_encode(array_slice($raw, 1)), "\n";
echo 'slice, keys preserved: ', json_encode(array_slice($raw, 1, null, true)), "\n";
echo 'sort return: ', var_export(sort($a), true), "\n";
```

```
sort: ["a","b","c"]
asort: {"1":"a","2":"b","3":"c"}
ksort: {"1":"a","2":"b","3":"c"}
rsort: ["c","b","a"]
slice: ["a","b"]
slice, keys preserved: {"1":"a","2":"b"}
sort return: true
```

`sort` and `rsort` discard the associative information; `asort` and `ksort` keep the key, and `json_encode` therefore writes them as objects. `array_slice` renumbers integer keys unless the `preserve_keys` argument is given. The last line shows a contract the sorting functions keep: they modify the array in place and return `true`; they are not functions that return a sorted copy. Code that writes `$sorted = sort($array)` sorts the array but assigns `true` to the variable `$sorted`.

**AS12.** The stability of the sorting functions (keeping the relative order of equal elements) is not measured in this lesson; the lesson tests only what happens to the keys and the return value. Sorting an array of mixed types depends on the comparison rules of the language, and no example is given here.

## The Membership Query

`in_array` asks whether a value is present in the array and uses the same loose comparison as `array_search`. When `true` is given as the third argument, the comparison becomes strict.

```php
<?php
// membership.php — in_array with loose and strict comparison
$numbers = ['10', '20'];

echo "loose, '1e1': ", var_export(in_array('1e1', $numbers), true), "\n";
echo "strict, '1e1': ", var_export(in_array('1e1', $numbers, true), true), "\n";
echo "loose, 10: ", var_export(in_array(10, $numbers), true), "\n";
echo "strict, 10: ", var_export(in_array(10, $numbers, true), true), "\n";
echo "loose null, [0]: ", var_export(in_array(null, [0]), true), "\n";
echo "strict null, [0]: ", var_export(in_array(null, [0], true), true), "\n";

$repeated = ['1', 1, 'a', 'A', 'a'];
echo 'unique: ', json_encode(array_unique($repeated)), "\n";
```

```
loose, '1e1': true
strict, '1e1': false
loose, 10: true
strict, 10: false
loose null, [0]: true
strict null, [0]: false
unique: {"0":"1","2":"a","3":"A"}
```

In a loose comparison, the string `'1e1'` counts as equal to `'10'`, because both are numeric strings and their value is `10`; the strict comparison separates the two. The same rule holds for the integer `10` and the string `'10'`. `null` and `0` are equal in a loose comparison and not in a strict one. These results are not a badly written condition; they are the rule of loose equality, and the coercion measured in the Programming Fundamentals course (M06/K01) affects the result of a search here. Because the output of `in_array` is only `true` or `false`, a false positive is heard through no channel: the channel is **silent**. On the last line, `array_unique` keeps the first value it sees and counts `'1'` and `1`, whose string representation is the same, as one; `'a'` and `'A'` stay separate.

## Silent Behaviors in Mapping and Summing

Mapping, flipping and summing functions often report no level when the shape of the input differs from what was expected. The observations below are classified with the same listener.

```php
<?php
// quiet.php — input problems in mapping, flipping and summing
require 'channel.php';

$observations = [
    'two arrays, unequal'  => fn() => array_map(fn($a, $b) => [$a, $b], [1, 2, 3], ['x']),
    'duplicate value flip' => fn() => array_flip(['a', 'b', 'a']),
    'duplicate key'        => fn() => array_combine(['a', 'a'], [1, 2]),
    'numeric string sum'   => fn() => array_sum(['3', '4']),
    'non-numeric sum'      => fn() => array_sum([1, 'abc']),
    'empty array sum'      => fn() => array_sum([]),
    'empty array max'      => fn() => max([]),
    'key filter'           => fn() => array_filter(['a' => 1, 'b' => 2], fn($k) => $k === 'a', ARRAY_FILTER_USE_KEY),
];

foreach ($observations as $name => $observe) {
    [$channel, $evidence] = listen($observe);
    printf("%-23s%-9s%s\n", $name, $channel, $evidence);
}

echo 'flip: ', json_encode(array_flip(['a', 'b', 'a'])), "\n";
echo 'combine: ', json_encode(array_combine(['a', 'a'], [1, 2])), "\n";
echo 'map: ', json_encode(array_map(fn($a, $b) => [$a, $b], [1, 2, 3], ['x'])), "\n";
```

```
two arrays, unequal    silent   result array(3)
duplicate value flip   silent   result array(2)
duplicate key          silent   result array(1)
numeric string sum     silent   result int 7
non-numeric sum        warning  E_WARNING, result int 1
empty array sum        silent   result int 0
empty array max        halts    ValueError
key filter             silent   result array(1)
flip: {"a":2,"b":1}
combine: {"a":2}
map: [[1,"x"],[2,null],[3,null]]
```

When `array_map` receives two arrays, it pads the shorter one with `null` and reports no level; the pairs `[2,null]` and `[3,null]` are produced silently. `array_flip` and `array_combine` keep the last value on a duplicate value or key and silently discard the first: `{"a":2,"b":1}` and `{"a":2}`.

In summing, numeric strings are converted to numbers (the sum of `'3'` and `'4'` is 7), whereas a non-numeric element produces `E_WARNING` and the total counts only the valid elements. On an empty array, `array_sum` gives 0 and `max` halts with `ValueError`; on an array that may be empty, `max` must be guarded before the call with `count` or `[] !== $array`. `ARRAY_FILTER_USE_KEY` gives the key to the callback instead of the value and works silently.

## The Bounding Measurement

The return channel works only if the calling code reads it correctly. The returns `0` and `false` of `array_search` are the same value in a loose equality. Two spellings interpret the same list in two different ways.

```php
<?php
// distinction.php — 0 and false are the same value in a loose comparison
$items = ['pear', 'quince'];

$loose = array_search('pear', $items) == false ? 'not found' : 'found';
$strict = array_search('pear', $items) === false ? 'not found' : 'found';

echo 'loose: ', $loose, "\n";
echo 'strict: ', $strict, "\n";
echo 'really absent, loose: ',
    (array_search('apple', $items) == false ? 'not found' : 'found'), "\n";

$mixed = array_filter([1, 0, 2, 0, 3]);
echo 'zeros dropped: ', json_encode($mixed), "\n";
echo 'keys: ', implode(',', array_keys($mixed)), "\n";
```

```
loose: not found
strict: found
really absent, loose: not found
zeros dropped: {"0":1,"2":2,"4":3}
keys: 0,2,4
```

The loose comparison that searched for the first element gave the result `not found`, and the strict comparison gave `found`; on an element that is truly absent, the loose comparison gave the correct result. The same call answers wrongly when the sought element is first in the list and correctly when it is elsewhere, and no channel reports this difference. The discriminating power of the return channel is limited by the discriminating power of the caller: if `0` and `false` are not told apart, the first element found counts as "absent".

When `array_filter` is called without a callback, every element whose value is equal to `false` is dropped; `0` is one of them. The keys in the output stay `0, 2, 4`: data is lost and the array stops being indexed, and both are silent. If filtering is what was intended, there is no problem; if the aim was only to drop empty values, `0` is dropped too, though it was an expected value.

## Summary

- `array_filter` keeps keys, `array_map` carries keys along, and `array_reduce` comes down to a single value with an initial value.
- After filtering, an array can stop being indexed; `array_values` renumbers the keys.
- `array_search` returns `false` when it finds nothing; the measuring channel of this lesson is **return** (`element not found`).
- An invalid argument value (`str_repeat('a', -1)`, `array_combine` with mismatched lengths) throws `ValueError`; the channel is **halts**.
- The `0` returned when the first element is found is the same as `false` in a loose comparison; only a strict comparison separates the two.
- `array_filter` without a callback silently drops every element equal to `false`, `0` included.

## Next Step

This lesson measured the discriminating power of the return channel and showed that, once `0` and `false` are mixed up, the distinction is left to the caller. The String Handling lesson measures a harsher case of the same channel: if two separate problems give the same return value, the value alone cannot say which of them it is.
