PHP Strings and Arrays

AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.

PHP 8.4

A PHP array is an ordered map: it serves as both list and dictionary, and the function library around it is enormous. This chapter covers the part you actually reach for.

Indexed and associative arrays

$colors = ["red", "green", "blue"];              // keys 0, 1, 2
$user   = ["name" => "ann", "age" => 30];        // string keys

Appending:

$colors[] = "yellow";        // next free integer key
$user["city"] = "Shenzhen";  // explicit key

Arrays preserve insertion order, and you can rely on it:

$user["role"] = "admin";
foreach ($user as $key => $value) {
    echo "$key: $value\n";
}
// name: ann
// age: 30
// city: Shenzhen
// role: admin

Iteration

$colors = ["red", "green", "blue"];

foreach ($colors as $color) {
    echo $color . "\n";
}

foreach ($colors as $index => $color) {
    echo "$index => $color\n";
}

foreach iterates over a copy: modifying $colors inside the loop does not change the sequence being walked.

$nums = [1, 2, 3];
foreach ($nums as $n) {
    $nums[] = $n * 10;   // still exactly 3 iterations
}

To modify values, iterate by reference — and always unset afterwards:

foreach ($nums as &$n) {
    $n *= 2;
}
unset($n);   // required, or reusing $n later corrupts the last element

Array functions you use daily

$nums = [3, 1, 2];

count($nums);              // 3
in_array(2, $nums);        // true — loose comparison by default
in_array(2, $nums, true);  // strict, prefer this
array_keys($nums);         // [0, 1, 2]
array_values($nums);       // [3, 1, 2]
sort($nums);               // sorts in place, returns true → [1, 2, 3]
rsort($nums);              // descending, in place

sort() modifies the array and returns a bool. Never write $sorted = sort($nums) — you get true. Copy the array first if you need the original.

array_map / array_filter / array_reduce

$nums = [1, 2, 3];

$doubled = array_map(fn($n) => $n * 2, $nums);                        // [2, 4, 6]
$evens   = array_filter($nums, fn($n) => $n % 2 === 0);                // [1 => 2]
$total   = array_reduce($nums, fn($carry, $n) => $carry + $n, 0);      // 6

Two things to remember:

  1. array_filter preserves keys, so the result may not be a list. Reindex with array_values():
$evens = array_values(array_filter($nums, fn($n) => $n % 2 === 0));   // [2]
  1. Arrow functions (fn()) capture outer variables automatically; anonymous function () closures need use:
$factor = 10;
array_map(function ($n) use ($factor) {
    return $n * $factor;
}, $nums);

Merging: + versus array_merge

They behave differently, and picking the wrong one silently drops data:

$a = ["name" => "ann", "age" => 30];
$b = ["age" => 31, "city" => "Shenzhen"];

array_merge($a, $b);
// ["name" => "ann", "age" => 31, "city" => "Shenzhen"] — later wins

$a + $b;
// ["name" => "ann", "age" => 30, "city" => "Shenzhen"] — left wins

On indexed arrays: array_merge appends and renumbers, while + keeps the left side and discards overlapping keys.

[1, 2] + [3, 4];             // [1, 2]
array_merge([1, 2], [3, 4]); // [1, 2, 3, 4]

Use array_merge to append lists, + to merge configuration where existing values win.

String functions

$s = "hello world";

strlen($s);                  // 11 — bytes
str_contains($s, "world");   // true (PHP 8+)
str_starts_with($s, "he");   // true
str_ends_with($s, "ld");     // true
strpos($s, "world");         // 6, or false when not found
strtoupper($s);              // "HELLO WORLD"
strtolower($s);              // "hello world"
trim("  hi  ");              // "hi"
str_replace("world", "PHP", $s);   // "hello PHP"
substr($s, 0, 5);            // "hello"
explode(" ", $s);            // ["hello", "world"]
implode("-", ["a", "b"]);    // "a-b"

Two rules that come up constantly:

  1. strpos returns false when not found and 0 when found at position 0. Compare with !== false:
if (strpos($s, "hello") !== false) {
    echo "found";   // "hello" is at 0 — an == check would wrongly say "not found"
}
  1. strlen counts bytes, so UTF-8 text is longer than it looks:
strlen("你好");                      // 6
mb_strlen("你好", "UTF-8");           // 2
mb_substr("你好世界", 0, 2, "UTF-8");  // "你好"

Use the mb_* family for anything user-facing (the mbstring extension is enabled in mainstream distributions).

Converting between strings and arrays

$csv = "a,b,c";
$parts = explode(",", $csv);        // ["a", "b", "c"]
$back  = implode(",", $parts);      // "a,b,c"

// trim and drop empty entries
$tags = array_values(array_filter(
    array_map("trim", explode(",", "php, , js, "))
));
// ["php", "js"]

Worked example: filter, total, format

<?php
declare(strict_types=1);

$orders = [
    ["id" => 1, "amount" => 120, "paid" => true],
    ["id" => 2, "amount" => 80,  "paid" => false],
    ["id" => 3, "amount" => 200, "paid" => true],
];

$paid = array_filter($orders, fn($o) => $o["paid"]);
$total = array_reduce($paid, fn($carry, $o) => $carry + $o["amount"], 0);
$ids = implode(", ", array_map(fn($o) => "#" . $o["id"], $paid));

echo "Paid orders: {$ids}" . PHP_EOL;
echo "Total: {$total}" . PHP_EOL;
// Paid orders: #1, #3
// Total: 320

Note that $paid still has keys 0 and 2 — fine for array_reduce and array_map, but reindex with array_values() if you need a JSON list.

Common mistakes

1. Treating sort()’s return value as the sorted array

It returns true and sorts in place.

2. Checking strpos() with == false

Position 0 is misread as “not found”. Use !== false.

3. Using strlen() for visible length

It counts bytes. Use mb_strlen().

4. Assuming array_filter reindexes

Wrap it in array_values() when the result must be a list.

5. Expecting edits inside foreach to affect iteration

You are walking a copy. Use & to modify values, then unset.

6. Expecting + to let the right side override

+ keeps the left operand; use array_merge for override semantics.

Summary

  • PHP arrays are ordered maps; foreach walks a copy.
  • Transform with array_map / array_filter / array_reduce; reindex after filtering.
  • Merge lists with array_merge, keep existing keys with +.
  • Search with str_contains (PHP 8+), and always compare strpos against !== false.
  • Use mb_* for any multibyte text.

Next: conditionals and loops, including match and the alternative template syntax.