PHP 8.5 array_first and array_last — Clean Up Your Collection Fallbacks

4 min read

Getting the first or last element of a PHP array has always involved choosing between bad options. The reset()/end() approach works but has hidden side effects. The array_values() route wastes memory. PHP 8.5 fixes this with two dedicated functions that do exactly one thing cleanly.

The old way and why it's ugly

There's no shortage of ways to grab the first element of an array in PHP. That's the problem.

$items = ['apple', 'banana', 'cherry'];

// Option 1: reset() — moves the internal array pointer (side effect!)
$first = reset($items);

// Option 2: array_values() — creates a full copy of the array just to access index 0
$first = array_values($items)[0];

// Option 3: array_key_first() — verbose, plus a null coalesce
$first = $items[array_key_first($items)] ?? null;

// For the last element, end() — also moves the internal pointer
$last = end($items);

reset() and end() are the most common choices, but both modify the array's internal pointer. If anything else in your call stack uses current() to iterate that array, you've just broken it. It's the kind of bug that surfaces in production after two hours of var_dump.

array_values($items)[0] is safe but wasteful — it allocates a full copy of the array just to read index zero. On large arrays that matters.

array_key_first() and array_key_last() were the right building blocks when PHP 7.3 added them, but $items[array_key_first($items)] ?? null is too much ceremony for a simple read. If you find yourself chaining functions like this to extract values, PHP 8.5's pipe operator is worth a look — it helps clean up exactly this kind of multi-step expression.

array_first() and array_last() in PHP 8.5

PHP 8.5 ships two dedicated functions with clean, unambiguous signatures:

function array_first(array $array): mixed {}
function array_last(array $array): mixed {}

Usage is as straightforward as the name suggests:

$items = ['apple', 'banana', 'cherry'];

$first = array_first($items); // 'apple'
$last  = array_last($items);  // 'cherry'

No side effects. No pointer movement. No memory copy. These functions read the first or last element by insertion order and return the value directly.

The RFC passed with 35 votes to 0 — not a lot of controversy when a proposal is this clearly overdue. PHP 8.5 has been a steady stream of these quality-of-life fixes. If you haven't seen it yet, the new Uri extension that replaces parse_url() follows the same philosophy: give you a clean, dedicated API where there used to be a footgun.

Edge cases: empty arrays and associative arrays

Empty arrays return null, not false:

$empty = [];

$first = array_first($empty); // null
$last  = array_last($empty);  // null

No warning, no exception. The null return is intentional — it's consistent with how array_find() (also PHP 8.5) handles misses, and matches array key access on a missing index.

One edge case to be aware of: if null is a legitimate value in your array, you can't distinguish "first element is null" from "array is empty" from the return value alone.

$values = [null, 'second', 'third'];

array_first($values); // null — but the array isn't empty

Guard with a count check if you need the distinction:

$first = count($values) > 0 ? array_first($values) : 'default';

Associative arrays use insertion order, not key order:

$prices = [
    'banana' => 0.50,
    'apple'  => 1.20,
    'cherry' => 2.00,
];

array_first($prices); // 0.50 — 'banana' was inserted first
array_last($prices);  // 2.00 — 'cherry' was inserted last

The functions return the value, not the key. If you need the key, array_key_first() and array_key_last() are still the right choice.

There's no hidden overhead here — no full array copy, no iteration. For most workloads this is a non-issue, but if you're regularly dealing with large in-memory datasets, processing large files with Laravel's lazy collections covers the broader topic of memory-efficient data handling in PHP and Laravel.

Gotchas and Edge Cases

References are dereferenced automatically. If your array contains reference values, array_first() returns the referenced value, not the reference itself. This is almost always what you want.

Not the same as array_shift(). array_first() is a pure read — it doesn't modify the array. To remove and return the first element, array_shift() is still your function.

null values in arrays. As shown above, array_first([null, 'b', 'c']) returns null and you cannot tell from the return value alone whether the array is empty or starts with null. If your arrays can contain null as a meaningful value, add an emptiness check before calling.

Rector doesn't auto-upgrade yet. As of April 2026 there's no Rector rule to automatically replace reset() calls with array_first(). You'll need to migrate these by hand or write a custom rule — a fairly mechanical find-and-replace once you know which callsites are safe to convert (i.e. where the pointer side effect wasn't intentional).

Wrapping Up

array_first() and array_last() are small additions, but the kind that quietly improve readability across an entire codebase once you adopt them. If you're on PHP 8.5, there's no reason to reach for reset() or end() for a simple first/last read.

PHP 8.5 has more worth adopting alongside these — updating readonly objects without boilerplate using clone with is particularly useful if you're working with immutable value objects or DTOs, and pairs well with the kind of clean, functional-style PHP that these new array functions encourage.

php
php-8.5
arrays
Steven Richardson

Steven is a software engineer with a passion for building scalable web applications. He enjoys sharing his knowledge through articles and tutorials.