PHP Language Basics

Arrays

An array is a value that can hold many values. In PHP, the same array type is used for simple lists, key-value maps, and nested records. That flexibility is useful, but it also means you must be clear about the shape of the data.

Arrays appear everywhere in PHP: decoded JSON, request data, configuration, database rows, form errors, route parameters, grouped records, and small scripts. A junior developer needs to do more than create an array. You need to read values safely, check whether keys exist, add and remove items, loop over records, transform values, and sort data without losing track of the keys.

Lists And Keyed Arrays

A list uses numeric keys. PHP assigns the keys automatically when you append values.

PHP example
<?php

$tags = ['php', 'beginner'];
$tags[] = 'arrays';

print_r($tags);

// Prints:
// Array
// (
//     [0] => php
//     [1] => beginner
//     [2] => arrays
// )

A keyed array uses meaningful keys.

PHP example
<?php

$product = [
    'sku' => 'BOOK-001',
    'name' => 'PHP Workbook',
    'price_cents' => 2499,
    'in_stock' => true,
];

echo $product['name'] . " costs {$product['price_cents']} cents\n";

// Prints:
// PHP Workbook costs 2499 cents

Use keyed arrays when each value has a name. Use lists when order matters and each item has the same meaning.

Reading Optional Keys

Reading a missing key causes a warning. Decide what should happen when a key is missing.

PHP example
<?php

$product = [
    'name' => 'PHP Workbook',
    'price_cents' => 2499,
];

$currency = $product['currency'] ?? 'GBP';

echo $currency . "\n";

// Prints:
// GBP

Use ?? when a simple fallback is correct. Use array_key_exists() when you need to distinguish "the key is missing" from "the key exists but contains null".

PHP example
<?php

$product = ['discount_cents' => null];

var_dump(array_key_exists('discount_cents', $product));
var_dump(isset($product['discount_cents']));

// Prints:
// bool(true)
// bool(false)

isset() is false for null. array_key_exists() only asks whether the key exists.

Nested Arrays

Nested arrays let you represent records inside records.

PHP example
<?php

$order = [
    'id' => 1001,
    'customer' => [
        'name' => 'Amo',
        'email' => 'amo@example.com',
    ],
    'items' => [
        ['sku' => 'BOOK-001', 'quantity' => 2],
        ['sku' => 'PEN-002', 'quantity' => 1],
    ],
];

echo $order['customer']['email'] . "\n";
echo $order['items'][0]['sku'] . "\n";

// Prints:
// amo@example.com
// BOOK-001

Nested arrays are common after decoding JSON or fetching rows grouped by another value. The risk is assuming every nested key exists. Check at the boundary before relying on external data.

Adding, Updating, Removing, And Reindexing

Arrays are often changed in small steps.

PHP example
<?php

$tags = ['php', 'arrays'];

$tags[] = 'practice';
$tags[1] = 'array-basics';
unset($tags[0]);

print_r($tags);
print_r(array_values($tags));

// Prints:
// Array
// (
//     [1] => array-basics
//     [2] => practice
// )
// Array
// (
//     [0] => array-basics
//     [1] => practice
// )

unset() removes an item but does not renumber numeric keys. Use array_values() when you need a clean zero-based list afterward.

Checking Membership And Searching

Use in_array() to check whether a value appears in an array. Pass true as the third argument for strict comparison.

PHP example
<?php

$allowedStatuses = ['pending', 'paid', 'failed'];

var_dump(in_array('paid', $allowedStatuses, true));
var_dump(in_array('0', [0, 1, 2], true));

// Prints:
// bool(true)
// bool(false)

Use array_search() when you need the key where a value was found.

PHP example
<?php

$statuses = ['pending', 'paid', 'failed'];
$position = array_search('paid', $statuses, true);

var_dump($position);

// Prints:
// int(1)

Be careful: array_search() can return 0, which is a valid key but falsey. Compare the result with !== false.

Filtering, Mapping, And Walking

array_filter() keeps items that pass a check.

PHP example
<?php

$products = [
    ['name' => 'Notebook', 'in_stock' => true],
    ['name' => 'Pen', 'in_stock' => false],
    ['name' => 'Mug', 'in_stock' => true],
];

$inStock = array_filter($products, fn (array $product): bool => $product['in_stock']);

print_r(array_column($inStock, 'name'));

// Prints:
// Array
// (
//     [0] => Notebook
//     [2] => Mug
// )

array_filter() preserves keys. Use array_values() afterward if you need a reindexed list.

array_map() transforms each item and returns a new array.

PHP example
<?php

$prices = [1299, 2500, 999];

$labels = array_map(
    fn (int $cents): string => '£' . number_format($cents / 100, 2),
    $prices
);

print_r($labels);

// Prints:
// Array
// (
//     [0] => £12.99
//     [1] => £25.00
//     [2] => £9.99
// )

array_walk() is useful when you want to visit each item, often to modify values by reference.

PHP example
<?php

$names = [' ada ', ' grace '];

array_walk($names, function (string &$name): void {
    $name = trim($name);
});

print_r($names);

// Prints:
// Array
// (
//     [0] => ada
//     [1] => grace
// )

Use by-reference callbacks carefully. If returning a new array is clearer, prefer array_map().

Pulling Columns And Indexing Records

array_column() pulls one key from each nested array.

PHP example
<?php

$products = [
    ['sku' => 'BOOK-001', 'name' => 'PHP Workbook'],
    ['sku' => 'PEN-002', 'name' => 'Pen'],
];

$namesBySku = array_column($products, 'name', 'sku');

print_r($namesBySku);

// Prints:
// Array
// (
//     [BOOK-001] => PHP Workbook
//     [PEN-002] => Pen
// )

This pattern is common when you need fast lookup by ID, SKU, email, or another unique value.

Sorting

Use sort() for simple lists when you do not care about preserving keys. Use asort() when keys matter. Use usort() for custom comparison logic.

PHP example
<?php

$products = [
    ['name' => 'Notebook', 'price_cents' => 1299],
    ['name' => 'Mug', 'price_cents' => 899],
    ['name' => 'Backpack', 'price_cents' => 4200],
];

usort($products, fn (array $a, array $b): int => $a['price_cents'] <=> $b['price_cents']);

print_r(array_column($products, 'name'));

// Prints:
// Array
// (
//     [0] => Mug
//     [1] => Notebook
//     [2] => Backpack
// )

The spaceship operator <=> returns the comparison result that usort() expects.

Merging Settings

Arrays are often used for configuration. Later values can override defaults.

PHP example
<?php

$defaults = [
    'currency' => 'GBP',
    'items_per_page' => 20,
];

$userSettings = [
    'items_per_page' => 50,
];

$settings = array_merge($defaults, $userSettings);

print_r($settings);

// Prints:
// Array
// (
//     [currency] => GBP
//     [items_per_page] => 50
// )

For string keys, array_merge() lets later arrays override earlier arrays. Numeric keys behave differently because lists are reindexed.

Count Values And Check List Shape

count() returns the number of top-level elements:

PHP example
<?php

$tags = ['php', 'arrays', 'practice'];

echo count($tags), PHP_EOL;

A numeric-keyed array is not necessarily a list. PHP defines a list as an array whose keys are consecutive integers from 0:

PHP example
<?php

$tags = ['php', 'arrays', 'practice'];
unset($tags[1]);

var_dump(array_is_list($tags));
var_dump(array_is_list(array_values($tags)));

Output:

bool(false)
bool(true)

This distinction matters when encoding JSON. A zero-based PHP list becomes a JSON array, while an array with gaps can become a JSON object. Reindex filtered or partially removed lists when the receiving system expects an array shape.

Understand Array Keys

PHP array keys are integers or strings. Some other key values are converted: a numeric string such as '8' becomes integer key 8, while a boolean becomes 1 or 0. Avoid relying on implicit key conversion because it can overwrite an existing entry unexpectedly.

Assigning the same key twice keeps only the later value:

PHP example
<?php

$prices = [
    'BOOK-001' => 1999,
    'BOOK-001' => 2499,
];

print_r($prices);

The array contains one key with value 2499. When building a lookup with array_column(), duplicate index values similarly cause later records to replace earlier records. Confirm that IDs, SKUs, or emails are unique before treating them as lookup keys.

Compare Merge With Array Union

For string-keyed settings, array_merge() gives later arrays precedence:

PHP example
$settings = array_merge($defaults, $userSettings);

The union operator + uses the opposite ownership rule: it keeps values from the left array when a key already exists.

PHP example
<?php

$defaults = ['currency' => 'GBP', 'page_size' => 20];
$overrides = ['page_size' => 50];

print_r($defaults + $overrides);
print_r(array_merge($defaults, $overrides));

The union keeps page_size as 20; the merge changes it to 50. Neither operation is universally correct. Choose based on which side should win, and add an exercise or test for an overlapping key.

Numeric keys need extra care. array_merge() appends and renumbers numeric entries. Array union preserves left-side numeric keys and ignores conflicting right-side keys. If the data is a list, appending may be right. If numeric keys are meaningful identifiers, neither casual merge behavior may match the intended policy.

Transform Without Losing The Original Accidentally

Functions such as array_map(), array_filter(), array_values(), array_column(), and array_merge() return arrays. They do not replace the source variable unless you assign the result:

PHP example
<?php

$rawNames = [' ada ', ' grace '];
$cleanNames = array_map('trim', $rawNames);

print_r($rawNames);
print_r($cleanNames);

Keeping raw and cleaned arrays separate can make a data-cleaning pipeline easier to inspect. Reassign deliberately when the original is no longer needed.

By contrast, sorting functions such as sort(), asort(), and usort() modify the supplied array and return a boolean success value. Do not write $sorted = sort($values) expecting $sorted to contain the array. Copy first when both orders are needed.

Common Mistakes

Do not assume a key exists just because one example has it. External data can be missing, null, duplicated, wrongly typed, or shaped differently.

Do not use loose membership checks for security or status decisions. Use strict comparison with in_array() and array_search().

Do not forget that some array functions preserve keys and others reindex. If JSON output needs a list, reindex with array_values() after filtering.

What You Should Be Able To Do

After this lesson, you should be able to:

  • create lists, keyed arrays, and nested arrays;
  • read required and optional keys deliberately;
  • add, update, remove, and reindex values;
  • check membership and search by value;
  • filter, map, and walk arrays;
  • pull columns and build lookup arrays;
  • sort records by a field;
  • merge default settings with overrides.

Practice

Task: Product Summary

Task

Create this keyed product record:

PHP example
$product = [
    'sku' => 'BOOK-001',
    'name' => 'PHP Workbook',
    'price_cents' => 2499,
    'in_stock' => true,
    'discount_cents' => null,
];

Print the summary BOOK-001: PHP Workbook costs 2499 cents, then print Availability: in stock using a ternary expression.

Read an optional currency key with fallback GBP. Finally, use both array_key_exists() and isset() to inspect discount_cents.

Expected final lines:

Currency: GBP
Discount key exists: yes
Discount is set: no

Hints

  • ?? is appropriate for the currency fallback.
  • array_key_exists() sees a key whose value is null.
  • isset() returns false for that same value.
Show solution

Solution

PHP example
<?php

$product = [
    'sku' => 'BOOK-001',
    'name' => 'PHP Workbook',
    'price_cents' => 2499,
    'in_stock' => true,
    'discount_cents' => null,
];

$currency = $product['currency'] ?? 'GBP';
$availability = $product['in_stock'] ? 'in stock' : 'out of stock';
$discountKeyExists = array_key_exists('discount_cents', $product) ? 'yes' : 'no';
$discountIsSet = isset($product['discount_cents']) ? 'yes' : 'no';

echo "{$product['sku']}: {$product['name']} costs {$product['price_cents']} cents\n";
echo "Availability: $availability\n";
echo "Currency: $currency\n";
echo "Discount key exists: $discountKeyExists\n";
echo "Discount is set: $discountIsSet\n";

Explanation

Required fields are read directly from the known record shape. Currency uses a documented fallback because the key is optional.

The discount key is present, so array_key_exists() returns true. Its value is null, so isset() returns false. Those checks answer different questions and should not be substituted blindly.

Task: Predict Array Keys

Task

Before running the code, predict both printed arrays and both booleans:

PHP example
<?php

$tags = ['php', 'arrays', 'practice'];
unset($tags[1]);

var_dump(array_is_list($tags));
print_r($tags);

$tags = array_values($tags);

var_dump(array_is_list($tags));
print_r($tags);

Explain why removing one value does not immediately restore consecutive numeric keys, then run the unchanged code.

Hints

  • unset() preserves the keys of remaining entries.
  • A PHP list must use consecutive integer keys starting at 0.
  • array_values() returns a reindexed array.
Show solution

Solution

PHP example
<?php

$tags = ['php', 'arrays', 'practice'];
unset($tags[1]);

var_dump(array_is_list($tags));
print_r($tags);

$tags = array_values($tags);

var_dump(array_is_list($tags));
print_r($tags);

// First boolean: false
// First keys: 0 and 2
// Second boolean: true
// Second keys: 0 and 1

Explanation

After unset(), php keeps key 0 and practice keeps key 2. The gap means the array is not a list according to array_is_list().

array_values() creates a new array containing the same values under keys 0 and 1. That consecutive zero-based shape is a list again.

Task: Build Order List

Task

Build an $order array with ID 1001 and this nested items list:

  • BOOK-001, quantity 2;
  • PEN-002, quantity 1;
  • MUG-003, quantity 3.

Loop over the nested items, print <sku> x <quantity> for each one, and accumulate the total quantity. After the loop, print the order ID and total item count.

Expected final line:

Order 1001 contains 6 items

Hints

  • Start $totalQuantity at 0 before the loop.
  • Add each item's quantity during its iteration.
  • Print the summary after every item has been visited.
Show solution

Solution

PHP example
<?php

$order = [
    'id' => 1001,
    'items' => [
        ['sku' => 'BOOK-001', 'quantity' => 2],
        ['sku' => 'PEN-002', 'quantity' => 1],
        ['sku' => 'MUG-003', 'quantity' => 3],
    ],
];

$totalQuantity = 0;

foreach ($order['items'] as $item) {
    echo "{$item['sku']} x {$item['quantity']}\n";
    $totalQuantity += $item['quantity'];
}

echo "Order {$order['id']} contains $totalQuantity items\n";

Explanation

The order is a keyed record containing a nested list of similarly shaped item records. Each iteration reads one item and adds its quantity to a separate accumulator.

The source array remains unchanged. The final summary is printed after the loop because the complete total is not known until all three records have been processed.

Task: Update Product Tags

Task

Start with:

PHP example
$tags = ['php', 'beginner', 'php'];

Write code that:

  • adds arrays only if it is not already present;
  • attempts to add php but does not create another duplicate;
  • removes beginner only when it is found;
  • safely attempts to remove legacy, which is absent;
  • reindexes the final array and prints it.

The final values must be php, php, and arrays under keys 0, 1, and 2. Existing duplicates are not removed; the task only prevents new ones.

Hints

  • Use strict in_array() checks before appending.
  • Compare each array_search() result with !== false.
  • Call array_values() once after removals.
Show solution

Solution

PHP example
<?php

$tags = ['php', 'beginner', 'php'];

foreach (['arrays', 'php'] as $tagToAdd) {
    if (!in_array($tagToAdd, $tags, true)) {
        $tags[] = $tagToAdd;
    }
}

foreach (['beginner', 'legacy'] as $tagToRemove) {
    $key = array_search($tagToRemove, $tags, true);

    if ($key !== false) {
        unset($tags[$key]);
    }
}

$tags = array_values($tags);

print_r($tags);

Explanation

Strict membership checks prevent arrays or php from being appended when already present. They do not deduplicate values that were already in the input.

array_search() may return key 0, so !== false is required. The missing legacy value is ignored safely. One final array_values() call repairs the numeric key gap left by removing beginner.

Task: Sort And Index Products

Task

Use four products with sku, name, and price_cents. Give MUG-002 and PEN-004 the same price of 899 cents.

Sort by price from low to high. When prices are equal, sort by SKU with strcmp(). Print each sorted line as <sku>: <name> (<price> cents).

Then build $namesBySku with array_column($products, 'name', 'sku') and print the lookup.

Hints

  • In the comparator, return the price comparison when it is not zero.
  • Use strcmp($a['sku'], $b['sku']) as the tie-breaker.
  • usort() changes $products; it does not return the sorted array.
Show solution

Solution

PHP example
<?php

$products = [
    ['sku' => 'BOOK-001', 'name' => 'PHP Workbook', 'price_cents' => 2499],
    ['sku' => 'PEN-004', 'name' => 'Pen', 'price_cents' => 899],
    ['sku' => 'MUG-002', 'name' => 'Mug', 'price_cents' => 899],
    ['sku' => 'BAG-003', 'name' => 'Backpack', 'price_cents' => 4200],
];

usort($products, function (array $a, array $b): int {
    $priceComparison = $a['price_cents'] <=> $b['price_cents'];

    if ($priceComparison !== 0) {
        return $priceComparison;
    }

    return strcmp($a['sku'], $b['sku']);
});

foreach ($products as $product) {
    echo "{$product['sku']}: {$product['name']} ({$product['price_cents']} cents)\n";
}

$namesBySku = array_column($products, 'name', 'sku');

print_r($namesBySku);

Explanation

The primary comparison orders prices numerically. The SKU tie-breaker makes equal-price ordering deterministic, placing MUG-002 before PEN-004.

usort() mutates and numerically reindexes the product list. array_column() then builds a string-keyed lookup from that sorted data, where each unique SKU points to its name.

Task: Merge And Compare Settings

Task

Create these arrays:

PHP example
$defaults = [
    'currency' => 'GBP',
    'items_per_page' => 20,
    'show_tax' => true,
];

$userSettings = [
    'currency' => 'EUR',
    'items_per_page' => 50,
];

Build $merged with array_merge($defaults, $userSettings) and $union with $defaults + $userSettings.

Print the currency and page size from each result in this exact format:

Merge: EUR, 50
Union: GBP, 20

Hints

  • Later string keys win in array_merge().
  • Left-side keys win with array union.
  • show_tax remains present in both arrays even though it is not printed.
Show solution

Solution

PHP example
<?php

$defaults = [
    'currency' => 'GBP',
    'items_per_page' => 20,
    'show_tax' => true,
];

$userSettings = [
    'currency' => 'EUR',
    'items_per_page' => 50,
];

$merged = array_merge($defaults, $userSettings);
$union = $defaults + $userSettings;

echo "Merge: {$merged['currency']}, {$merged['items_per_page']}\n";
echo "Union: {$union['currency']}, {$union['items_per_page']}\n";

Explanation

array_merge() processes the user settings later, so their overlapping string keys replace the defaults. This is the usual policy for configurable overrides.

Array union keeps values already present on the left. It adds only right-side keys that the left array lacks, so both overlapping values remain the defaults. The correct operation depends on the intended ownership rule.