# Array functions

Source: https://codewiki.com/php/array-functions/

> - **what**: PHP array functions package common transformations, filters, aggregations, merges, and sorts into standard APIs. Choose among them by checking how each function treats keys and whether it changes its input.
> - **trap**: `array_filter()` preserves old keys, `array_merge()` renumbers integer keys, and `usort()` modifies its array in place while discarding every original key.
> - **fix**: Decide whether the result is a list or a map, give `array_reduce()` a correctly typed initial value, and return a negative integer, `0`, or a positive integer from sort comparators.

## What it is and why it exists

A PHP array acts as both a list and an ordered map. Array functions perform standard operations on that container: `array_map()` transforms values, `array_filter()` selects elements, `array_reduce()` accumulates state, `array_merge()` combines inputs, `array_keys()` extracts keys, and `usort()` applies a custom order.

These functions replace repeated traversal code, not every loop. Their names state the operation and PHP handles iteration, but each API still defines its own rules for keys, empty values, and mutation. If you guess from `map`, `filter`, or `reduce` in another language, you can get valid PHP that quietly changes the shape of your data.

`array_map()`, `array_filter()`, `array_reduce()`, and `usort()` accept a callback. A callback is a function passed into another function, which invokes it while processing data. Arrow functions suit one-expression callbacks; an ordinary anonymous function is usually clearer when validation or state updates take several steps.

The result contract matters more than the syntax:

| Function | Main operation | Key behavior | Changes input |
| --- | --- | --- | --- |
| `array_map()` | Transform every value | Preserves keys with one input array | No |
| `array_filter()` | Retain matching elements | Preserves keys and may leave gaps | No |
| `array_reduce()` | Fold all values into one result | Does not pass keys to the callback | No |
| `array_merge()` | Combine arrays in order | Overwrites string keys; renumbers integer keys | No |
| `array_keys()` | Get all keys or keys for matching values | Returns a list starting at `0` | No |
| `usort()` | Sort values with a comparator | Discards original keys and renumbers | Yes |

These functions fit data that is already in memory and has a clear processing boundary. A `foreach` loop or generator is often more direct when you need early exit, several pieces of state, streaming input, or careful key handling. Array functions don't guarantee readability; three or four nested callbacks are already beyond their comfortable range.

## How it works

All six functions traverse arrays, but they produce different kinds of results. Track values, keys, callback arguments, return values, and mutation separately instead of looking only at the final element values.

### `array_map()` transforms values

`array_map($callback, $array)` calls the callback once for each value and builds a new array from its return values. With exactly one input array, the new array preserves the original keys. With two or more arrays, PHP combines values by position and gives the result sequential integer keys.

If the input arrays have different lengths, missing positions in shorter arrays are filled with `null`. The callback's parameter count should match the number of arrays; PHP throws `ArgumentCountError` when the callback requires more arguments than the function supplies. If a transformation needs keys, combine `array_keys()` and `array_values()` explicitly or use `foreach`.

### `array_filter()` selects elements

`array_filter($array, $callback)` retains elements whose callback result converts to `true`. The default mode passes only the value, `ARRAY_FILTER_USE_KEY` passes only the key, and `ARRAY_FILTER_USE_BOTH` passes the value followed by the key. Every mode preserves input keys in the returned array.

Without a callback, `array_filter()` removes values PHP considers empty, including `false`, `null`, integer `0`, string `"0"`, the empty string, and an empty array. That shortcut is safe only when all of those values are invalid in your domain. A zero quantity, form value, or status code can be legitimate, so write an explicit predicate for such data.

### `array_reduce()` accumulates a result

`array_reduce($array, $callback, $initial)` starts with `$initial` as the accumulator, then passes the accumulator and each value to the callback in array iteration order. Each callback result becomes the next accumulator. The last result is the function result, and keys are never passed to the callback.

PHP differs from JavaScript here: omitting `$initial` doesn't use the first element as the starting value. PHP starts with `null` and still processes the first element. An empty array without an initial value also returns `null`. Use `0` for a sum, `''` for string concatenation, or `[]` when building a map; the initial value defines both the empty result and accumulator type.

### `array_merge()` combines arrays

`array_merge(...$arrays)` processes its inputs from left to right. When the same string key occurs more than once, the value on the right overwrites the value on the left. Values with integer keys are appended instead of overwritten, then numbered from `0` in the result. Calling the function without arguments returns an empty array.

The array union operator `+` uses different rules. It retains every key already present on the left, ignores a right-side value with the same key, and doesn't renumber integer keys. Use `array_merge()` to concatenate lists or let later configuration override earlier configuration; use `+` to preserve left-side keys and values. Neither operation discovers the right deep-merge policy for your domain.

### `array_keys()` extracts keys

`array_keys($array)` returns every integer and string key in iteration order. With a second argument, it returns only keys whose values match `$filterValue`; a third argument of `true` selects `===`, while the default uses loose comparison.

Use `array_key_exists()` to check whether one key exists instead of allocating the full key list and then calling `in_array()`. `isset($array[$key])` asks a different question: it returns `false` when the key exists but its value is `null`. Pick the function according to whether you mean “the key exists” or “the value isn't `null`.”

### `usort()` defines an order

`usort(&$array, $callback)` rearranges values in place and replaces every key with a sequential integer. A comparator returns a negative integer, `0`, or a positive integer when the left value belongs before, equal to, or after the right value. The `<=>` operator has exactly that contract.

Since PHP 8, elements that compare equal retain their original relative order, so `usort()` is a stable sort. Stability cannot repair an inconsistent comparator: if the callback contradicts itself or returns only a Boolean, the order is still unreliable. Since PHP 8.2, the function itself has return type `true`; the useful result is in the array it modified.

## Examples

The four examples cover transformation and filtering, reduction, merging, and sorting. Every output below was produced with the local PHP 8.3.33 runtime.

### Filter orders and transform display values

The orders use business identifiers as keys. Filtering and mapping both preserve those keys, so the first JSON encoding produces an object. Reindex with `array_values()` only when a JSON list is actually required.

<!-- quick -->

```php
// file: transform_orders.php
<?php

$orders = [
    104 => ['customer' => 'Mina', 'paid' => true, 'cents' => 1599],
    207 => ['customer' => 'Omar', 'paid' => false, 'cents' => 2400],
    310 => ['customer' => 'Liu', 'paid' => true, 'cents' => 800],
];

$paid = array_filter(
    $orders,
    fn(array $order): bool => $order['paid'],
);
$labels = array_map(
    fn(array $order): string => sprintf('%s:%.2f', $order['customer'], $order['cents'] / 100),
    $paid,
);

echo json_encode($labels, JSON_UNESCAPED_UNICODE), PHP_EOL;
echo json_encode(array_values($labels), JSON_UNESCAPED_UNICODE), PHP_EOL;
```

```text
{"104":"Mina:15.99","310":"Liu:8.00"}
["Mina:15.99","Liu:8.00"]
```

<!-- /quick -->

Only keys `104` and `310` remain in `$paid`, and `$labels` keeps both keys. PHP's `json_encode()` emits a JSON array only for arrays with sequential keys starting at `0`, which is why the first line has object shape.

The second line deliberately calls `array_values()` at the serialization boundary. That placement is easy to review: processing retains the order identifiers, and only the external list format discards them.

### Reduce order amounts by region

`array_reduce()` can return an array rather than a number. The empty initial array handles an empty order list and declares that the accumulator is a map from region to amount.

```php
// file: summarize_orders.php
<?php

$orders = [
    ['region' => 'east', 'cents' => 1200],
    ['region' => 'west', 'cents' => 750],
    ['region' => 'east', 'cents' => 750],
];

$totals = array_reduce(
    $orders,
    function (array $carry, array $order): array {
        $region = $order['region'];
        $carry[$region] = ($carry[$region] ?? 0) + $order['cents'];
        return $carry;
    },
    [],
);

foreach (array_keys($totals) as $region) {
    printf("%s=%d\n", $region, $totals[$region]);
}
```

```text
east=1950
west=750
```

The callback returns the updated `$carry` on every iteration. If it changes a local variable but forgets to return it, the next iteration receives `null`; the declared array type exposes that mistake quickly.

The first appearance of each region determines iteration order in `$totals`. `array_keys()` extracts the region-name list, while the loop still reads amounts from the original map. It doesn't copy the amounts into the key list.

### Distinguish merge from union

The user settings appear on the right of `array_merge()`, so they override the defaults. The nested `features` array is replaced as one value because this function handles only top-level keys.

```php
// file: merge_settings.php
<?php

$defaults = [
    'theme' => 'light',
    'page_size' => 20,
    'features' => ['search'],
];
$user = ['theme' => 'dark', 'features' => ['export']];

$settings = array_merge($defaults, $user);
$states = [10 => 'draft', 20 => 'review']
    + [20 => 'ignored', 30 => 'published'];

echo json_encode($settings), PHP_EOL;
echo json_encode($states), PHP_EOL;
```

```text
{"theme":"dark","page_size":20,"features":["export"]}
{"10":"draft","20":"review","30":"published"}
```

The second expression uses `+`, so the left-side `review` value wins at key `20`, and integer keys `10`, `20`, and `30` remain intact. Those keys aren't sequential list indexes, so the JSON result is still an object.

Swapping the two operations would still produce valid code, but it would implement a different data policy. Configuration merge code should make override direction clear through names or tests rather than forcing readers to infer it from argument position.

### Make a copy, then sort stably

`usort()` modifies its argument, so the code first copies `$tasks`. The two priority-`1` tasks compare equal and retain their input order on PHP 8.3.

```php
// file: sort_tasks.php
<?php

$tasks = [
    ['name' => 'write', 'priority' => 2],
    ['name' => 'test', 'priority' => 1],
    ['name' => 'deploy', 'priority' => 1],
];

$sorted = $tasks;
usort(
    $sorted,
    fn(array $left, array $right): int =>
        $left['priority'] <=> $right['priority'],
);

echo implode(',', array_column($sorted, 'name')), PHP_EOL;
echo implode(',', array_column($tasks, 'name')), PHP_EOL;
```

```text
test,deploy,write
write,test,deploy
```

The first line proves both ascending priority order and preservation of the `test`, `deploy` tie. The second line shows that the original array stayed in its original order because the PHP array passed to `usort()` was a copy.

If the domain requires names to break equal-priority ties, put that second comparison in the callback. A stable sort preserves input order for equal items; it doesn't invent a business tie-breaker.

## Pitfalls

The most dangerous array-function errors don't throw. They quietly change keys or classify valid data as empty. Tests should assert values, key order, and whether the input changed.

### Treating a filtered map as a list

> **Pitfall:** `array_filter()` preserves integer keys. Filtering `[10, 20, 30]` can produce `[1 => 20, 2 => 30]`, which encodes to a JSON object.

**Fix:** decide whether the result is a business map or a list. Call `array_values()` only when the consumer requires sequential indexes. If keys carry order numbers or database IDs, retain them and use a serialization shape that clearly represents a map.

### Deleting zero with the default filter

> **Pitfall:** Omitting the callback removes every empty value, not just `null` and empty strings. Integer `0` and string `"0"` disappear too.

**Fix:** express the domain condition in a callback, such as `fn($value) => $value !== null`. Don't use `array_filter($values)` as input validation because PHP's set of empty values is usually broader than your domain's set of invalid values.

### Assuming reduction starts with the first item

> **Pitfall:** Without an initial value, PHP passes `null` to the first callback. It doesn't adopt the first array element as JavaScript does.

**Fix:** give possibly empty input an identity value of the right type. Omitting the initial value for multiplication lets `null` enter the calculation rather than starting from the first item; use `1` to state the multiplicative identity.

### Mistaking a shallow merge for a policy

> **Pitfall:** `array_merge()` resolves conflicts only at the top level. A right-side nested array replaces the left-side value, while `array_merge_recursive()` can collect conflicting scalar values into arrays.

**Fix:** decide whether each configuration node should replace, append, or recurse before choosing an API or writing a small domain function. Test a repeated string key, an integer key, and a nested array. The word “recursive” doesn't mean “correct for configuration overrides.”

### Reversing callback arguments or return types

> **Pitfall:** `ARRAY_FILTER_USE_BOTH` passes value, then key. A `usort()` comparator receives two values and must return an integer. Generated code often reverses the filter parameters or returns a Boolean from the comparator.

**Fix:** give callback parameters domain names and types, then verify comparator signs with lesser, equal, and greater input pairs. A Boolean converted to an integer provides only `0` and `1`, so it can't express both “before” and “equal.”

### Forgetting that sorting mutates

> **Pitfall:** `usort()` takes the array by reference, changes the variable, and replaces both string and integer keys with sequential indexes.

**Fix:** assign the array to a new variable first when you need both orders. Consider `uasort()` when key-value association must survive. Don't assign the `true` return value from `usort()` to `$sorted`; the sorted data remains in the argument.

<!-- deep -->

## Edge semantics

When these six functions are composed, boundary behavior deserves more attention than the ordinary path through one function. Check the number of inputs, callback declarations, PHP key conversion, and sort ties.

### Mapping several arrays changes the key contract

The one-array form of `array_map()` preserves keys, while the multiple-array form aligns by position and returns sequential integer keys. It doesn't join on keys even when both inputs use the same string keys; only their iteration positions matter.

Shorter inputs are padded with `null`, which can make a non-nullable callback throw `TypeError`. If you need to join two maps by a business key, iterate over keys explicitly and handle missing entries instead of treating multi-array `array_map()` as a join.

### Callbacks see only what the API promises

`array_reduce()` passes the accumulator and value, but not the current key. Reducing over `array_keys()` can work, though the callback must then capture the source array. Once that indirection grows complicated, `foreach ($array as $key => $value)` is clearer.

The `array_filter()` mode changes callback arguments, not the result's key-preservation rule. With `ARRAY_FILTER_USE_KEY`, the callback cannot see the value. With `ARRAY_FILTER_USE_BOTH`, the value comes first and the key second; type declarations expose a reversal sooner.

### PHP keys determine JSON shape

PHP has no separate runtime list type; a list is an array with keys `0` through `n - 1`. Filtering leaves gaps and union retains non-sequential integer keys, while `array_merge()` and `usort()` renumber. The same value set can therefore produce different JSON shapes.

Don't discard keys blindly just to get a JSON array. First decide whether they are business identifiers. If they are, make each key an explicit object field or deliberately return a JSON object. Serialization tests should assert the full string or decoded structure, not merely the presence of values.

### Stable sorting still needs a complete rule

PHP 8 stability constrains only elements for which the comparator returns `0`. If input comes from a source with nondeterministic order, preserving relative input order doesn't create repeatable business output. Add an explicit secondary field when the output must be deterministic across runs.

A comparator must also be consistent: its answer for one pair cannot depend on changing external state, and `compare(a, b)` should have the opposite sign from `compare(b, a)`. Don't make network calls, modify compared elements, or read a changing clock inside it.

### Strictness in value lookup

`array_keys($array, $value)` uses loose comparison by default, so values of different types can match. Numeric values and numeric strings commonly mix after form, JSON, or database input. Pass `true` as the third argument when the type itself carries meaning.

A strict value search does not make keys “strictly typed.” PHP array keys can only be integers or strings, and some valid decimal integer strings are converted to integer keys when inserted. Choosing value-comparison semantics and understanding key normalization are separate tasks.

### Choosing array functions or an explicit loop

A single transformation or filter usually fits a standard function, and reduction can express a natural accumulator. An explicit loop is often shorter when a pipeline traverses the same large array several times, must stop at the first match, or needs both keys and values while handling errors.

Judge readability by whether the data policy is obvious. A well-named `foreach` isn't inferior to nested `array_map(array_filter(...))`. Choose the form that makes keys, empty input, and mutation boundaries easiest to review.

## How to review composed operations

A pipeline can connect several individually correct functions and still produce the wrong result. Don't verify only the final values. Write down the intermediate key set, type, and ownership after each step.

A useful method is to give every step a minimal counterexample instead of testing only tidy sequential lists. Missing keys, valid zero values, and duplicate keys tend to expose a mistaken default immediately.

### Reindex as late as possible

`array_values()` permanently discards original keys, so later steps can't recover an order identifier or source position. Keep reindexing at the output boundary that truly requires a list, leaving more diagnostic information available beforehand.

If later code must use positional access, put the shape in the variable name, such as `$paidOrderList`. That name says the keys no longer carry business meaning and discourages later code from treating a position as an ID.

### Define empty input explicitly

Every aggregate needs an explainable empty result: a money total is `0`, a grouping result is `[]`, and concatenated text may be `''`. Use that value as the `array_reduce()` initial argument so ordinary and empty input share a return type.

If no sensible empty result exists, validate the input and throw a domain exception. Don't let an omitted initial argument quietly choose `null` for you. That value is an API default, not a business decision.

### Separate transformation from validation

`array_map()` suits converting each valid input, while `array_filter()` suits selecting with a defined predicate. Collapsing parse failures, missing fields, and authorization failures to `null`, then filtering without a callback, loses the failure reason and can delete legitimate zeros.

When every failure must be reported, use an explicit loop to collect results and errors or have the callback throw an exception with context. The caller should be able to distinguish “no match” from “input could not be processed.”

### Name complex stages first

Nested array functions execute from the inside out, while people tend to read the code from the outside in. Naming intermediate values `$eligibleOrders` or `$totalsByRegion` exposes the shape and policy of each stage.

Named stages are also easier to inspect in a debugger and test separately. If one callback captures several variables and contains several branches, extracting a named function or writing an ordinary loop is usually less work.

### Protect boundaries with shape assertions

For list results, assert `array_is_list($result)` as well as element order. For maps, assert exact keys and their types so a refactor cannot quietly introduce reindexing through `array_values()` or `array_merge()`.

A sort test should include at least one equal pair and keep the pre-sort array for comparison. A merge test should include a string-key collision, an integer-key collision, and a nested array. Those cases directly expose a wrong function choice.

<!-- /deep -->

[Checkpoint: php/array-functions](https://codewiki.com/php/array-functions/#checkpoint)

## Further reading

- [PHP manual: array functions](https://www.php.net/manual/en/ref.array.php)
- [PHP manual: `array_map()`](https://www.php.net/manual/en/function.array-map.php)
- [PHP manual: `array_filter()`](https://www.php.net/manual/en/function.array-filter.php)
- [PHP manual: `array_reduce()`](https://www.php.net/manual/en/function.array-reduce.php)
- [PHP manual: `array_merge()`](https://www.php.net/manual/en/function.array-merge.php)
- [PHP manual: `usort()`](https://www.php.net/manual/en/function.usort.php)
