Fetching the first or last element of a PHP array sounds like a one-liner. And it is — it’s just that the one-liner changes depending on who you ask, what the array looks like, and whether you care about a subtle bug that’s been hiding in the most popular approach for years. PHP 8.5 finally ends that conversation with two native functions. This guide covers not just how they work, but why they were designed exactly the way they are — including the design debates the RFC had to resolve, the one genuine ambiguity that remains, and a naming conflict that will cause a fatal error in Laravel projects that haven’t been updated yet.
PHP continues to evolve with a strong focus on developer experience and cleaner code. One of the most practical additions expected in PHP 8.5 is the introduction of two long-awaited helper functions:
- array_first()
- array_last()
These functions solve a very common problem in PHP development—getting the first or last element of an array safely and cleanly.
In this article, we’ll explore what these new functions do, why they matter, and how they improve everyday PHP coding.
The Problem Before PHP 8.5
Before PHP 8.5, fetching the first or last array element wasn’t straightforward.
Getting the First Element (Before)
$first = reset($array);
Getting the Last Element (Before)
$last = end($array);
Issues with This Approach
- ❌ Modifies the internal array pointer
- ❌ Not readable for beginners
- ❌ Error-prone in complex logic
- ❌ Requires extra checks for empty arrays
Developers often wrote custom helper functions just to handle this cleanly.
What’s New in PHP 8.5?
PHP 8.5 introduces two native, safe, and readable functions:
array_first(array $array): mixed array_last(array $array): mixed
These functions:
- Do not modify the internal pointer
- Return null if the array is empty
- Work with both indexed and associative arrays
- Improve code clarity and intent
array_first() Function
📌 Description
Returns the first element of an array.
✨ Example
$colors = ['red', 'green', 'blue']; echo array_first($colors); // Output: red
With Associative Arrays
$user = [
'id' => 101,
'name' => 'John',
'role' => 'Admin'
];
echo array_first($user);
// Output: 101
Empty Array Handling
$data = []; $result = array_first($data); var_dump($result); // Output: NULL
No warnings. No errors. Clean and safe.
array_last() Function
📌 Description
Returns the last element of an array.
✨ Example
$numbers = [10, 20, 30, 40]; echo array_last($numbers); // Output: 40
With Associative Arrays
$settings = [
'theme' => 'dark',
'layout' => 'grid',
'version' => 'v2'
];
echo array_last($settings);
// Output: v2
Empty Array Handling
$items = []; echo array_last($items); // Output: NULL
Comparison: Old vs New
| Task | Before PHP 8.5 | PHP 8.5 |
|---|---|---|
| First element | reset($arr) | array_first($arr) |
| Last element | end($arr) | array_last($arr) |
| Pointer safety | ❌ No | ✅ Yes |
| Readability | ❌ Low | ✅ High |
| Empty array safe | ❌ No | ✅ Yes |
Real-World Use Cases
1).API Response Handling
$response = getApiResponse(); $firstItem = array_first($response['data']); $lastItem = array_last($response['data']);
2).Pagination Logic
$pages = [1, 2, 3, 4, 5]; $startPage = array_first($pages); $endPage = array_last($pages);
3).Logs & Audit Trailsc
$logs = fetchUserLogs(); $firstLog = array_first($logs); $latestLog = array_last($logs);
Why This Feature Matters
- Cleaner Code – No pointer manipulation
- More Readable – Self-explanatory function names
- Safer – Handles empty arrays gracefully
- Developer Friendly – Fewer helper functions
- Consistency – Matches modern PHP design philosophy
This small addition eliminates thousands of custom implementations across PHP projects.
Backward Compatibility
- These functions are only available in PHP 8.5+
- For older versions, you must still use reset() / end() or custom helpers
The introduction of array_first() and array_last() in PHP 8.5 may seem minor, but it’s a huge win for code clarity and safety.
These functions reflect PHP’s ongoing commitment to:
- Cleaner APIs
- Better defaults
- Improved developer experience
If you’re planning to upgrade to PHP 8.5, these new array helpers alone will make your daily coding more enjoyable.
The Six Workarounds PHP Developers Have Used for 25 Years
Before PHP 8.5, there was no single, canonical, idiomatic way to get the first or last value of an array. Here’s the full landscape of approaches, with the problem each one carries:
1. reset() and end() — The Most Common, and the Subtle Bug
$first = reset($array); $last = end($array);
These work — until they don’t. reset() and end() are semantically the wrong approach because they modify the “internal iterator” of the array. Furthermore, they do not work properly on all types of expressions — an array returned from a function or a plain array literal can cause a notice due to the by-reference argument.
// This causes a notice in strict mode — you can't pass a // function's return value by reference $first = reset(getUsers()); // Warning: Only variables should be passed by reference
The internal pointer side effect is subtle but real. If you call reset() mid-loop or inside a function that expects the pointer at a specific position, the behavior changes. This class of bug is rare but near-impossible to reproduce in isolation.
2. array_shift() and array_pop() — Destructive
$first = array_shift($array); // Removes the first element $last = array_pop($array); // Removes the last element
These mutate the array. Fine if you intended that; catastrophic if you didn’t notice they did it.
3. $array[0] — Breaks on Associative Arrays
$first = $array[0]; // Undefined offset if keys aren't 0-based
Fails silently (PHP notice / null in PHP 8) for any array that doesn’t start at index 0 — which includes any associative array, any array built with array_filter(), and any re-indexed array where the first key happens not to be 0.
4. array_values($array)[0] — Copies the Entire Array
$first = array_values($array)[0] ?? null;
This creates a complete re-indexed copy of the array in memory just to grab one element. For small arrays it doesn’t matter; for large ones it’s an unnecessary allocation on every call.
5. array_key_first() + subscript — Verbose
$first = $array[array_key_first($array)] ?? null; $last = $array[array_key_last($array)] ?? null;
In PHP 7.3, array_key_first() and array_key_last() were added to get the first and last keys from an array. Getting the value this way is the most correct pre-8.5 approach — no pointer mutation, no array copy, works on associative arrays — but it’s verbose and non-obvious.
6. Framework Helpers — Inconsistent Signatures
Laravel ships Arr::first() and Arr::last() (and global array_first() / array_last() helper functions). Symfony, Illuminate Collections, and others have their own variants. All slightly different. None of them are native.
PHP 8.5 ended all of this.
What PHP 8.5 Adds
array_first(array $array): mixed array_last(array $array): mixed
RFC author: Niels Dossche. Status: Implemented. Target version: PHP 8.5.
// Indexed arrays array_first([10, 20, 30]); // 10 array_last([10, 20, 30]); // 30 // Associative arrays array_first(['a' => 1, 'b' => 2]); // 1 array_last(['a' => 1, 'b' => 2]); // 2 // Non-sequential integer keys — works correctly array_first([5 => 'x', 9 => 'y']); // 'x' array_last([5 => 'x', 9 => 'y']); // 'y' // Empty array — returns null, no warning array_first([]); // null array_last([]); // null // Mixed value types array_first([true, false, null]); // true array_last([false, null, 42]); // 42
Every PHP array has an invisible “cursor” that tracks the current position when looping. Functions like reset() or end() move that cursor. But array_first() and array_last() do not move it — the array stays untouched, making your code easier to reason about and safer inside other functions.
Performance-wise: array_first() and array_last() are fast because PHP doesn’t loop through the array — they go directly to the first or last hash table bucket, making them O(1) regardless of array size.
The Design Debate the RFC Had to Resolve
The RFC wasn’t straightforward. The main debate was over behaviour on failure: should it throw on an empty array, or should it return null? Theoretically null can be a valid value from an array, and returning null does not allow distinguishing between an empty array and a null value.
Three options were considered:
Option A: Throw an exception on empty — forces callers to check length first, which is explicit but verbose. Every array_first() call becomes a try/catch or an if (empty($arr)) guard.
Option B: Return null on empty (chosen) —consistent with $array[array_key_first($array)] (accessing a non-existent key gives null), consistent with array_find() returning null on no match, and consistent with array_shift() returning null on an empty array.
Option C: Optional $default parameter — array_first($arr, $default) — rejected because when a programmer reads array_first($somevar, $someothervar), it looks weird unless you already know $somevar is an array and $someothervar is a fallback value. It looks unintuitive, and there’s no precedent for this design in PHP array functions.
The null return is the right call for the common case. For the edge case where null is a legitimate value in your array, the explicit check is straightforward:
// When null is a legitimate value in the array, check length first
if (empty($users)) {
throw new \UnderflowException('No users found');
}
$first = array_first($users); // Safe — array is non-empty
The null Ambiguity Trap — and How to Handle It
This is the one genuine gotcha:
$data = [null, 'second', 'third']; $result = array_first($data); // null $empty = []; $result = array_first($empty); // also null
Both return null. If your array can legitimately contain null as its first or last element, you can’t use array_first()’s return value alone to determine whether the array was empty. Two clean solutions:
// Solution 1: Check emptiness before calling
if (!empty($data)) {
$first = array_first($data);
// Use $first, even if it's null — the array was non-empty
}
// Solution 2: Use array_key_first() to distinguish
$key = array_key_first($data);
if ($key !== null) {
$first = $data[$key]; // The actual first value, even if null
} else {
// Array was empty
}
In practice, most arrays don’t have null as a meaningful first value. But when they do — nullable foreign keys, optional config values, mixed result sets — the check above is the right pattern.
Naming Collision: The Laravel Fatal Error
Existing PHP applications that declare their own array_first and array_last functions will cause fatal errors due to the attempt to redeclare these functions. These applications must either remove their own declarations, rename the functions, or add a namespace.
Laravel is the most affected framework. The laravel/helpers package ships global array_first() and array_last() helper functions that wrap Arr::first() and Arr::last(). On PHP 8.5, this causes:
Fatal error: Cannot redeclare array_first()
The fix, if you’re using laravel/helpers and upgrading to PHP 8.5:
// Option 1: Remove the laravel/helpers package if you only used it for these functions
// composer remove laravel/helpers
// Option 2: Guard the declarations in your own helper files
if (!function_exists('array_first')) {
function array_first($array, ?callable $callback = null, $default = null) {
return \Illuminate\Support\Arr::first($array, $callback, $default);
}
}
Note that Laravel’s Arr::first() and Arr::last() have a different, more powerful signature than the native functions — they accept an optional $callback for filtered access and a $default for fallback values. The native PHP 8.5 functions don’t accept a callback. If you rely on the callback functionality, keep using Arr::first() directly rather than the global helper.
// Laravel's Arr::first() — with callback filtering (not available natively) $firstAdult = Arr::first($users, fn($u) => $u->age >= 18, 'No adults found'); // PHP 8.5 native — value access only, no callback $first = array_first($users); // first user regardless of age
The Complete Comparison: Every Approach Side by Side
$arr = ['x' => 10, 'y' => 20, 'z' => 30]; // reset() — mutates pointer, can't use on expressions $first = reset($arr); // 10, but $arr's pointer moved // end() — mutates pointer $last = end($arr); // 30, but $arr's pointer at end now // array_values()[0] — copies entire array $first = array_values($arr)[0]; // 10, but allocated a new array // array_key_first() approach — correct but verbose $first = $arr[array_key_first($arr)] ?? null; // 10 $last = $arr[array_key_last($arr)] ?? null; // 30 // PHP 8.5 — correct, readable, O(1), pointer-safe $first = array_first($arr); // 10 $last = array_last($arr); // 30
| Approach | Mutates Pointer | Copies Array | Works on Expressions | Handles Empty | Readable |
|---|---|---|---|---|---|
| reset() / end() | ✅ Yes (bug risk) | ❌ | ❌ (notice) | Returns false | Medium |
| array_shift() / array_pop() | N/A — destructive | ❌ | ✅ | Returns null | Medium |
| $array[0] | ❌ | ❌ | ✅ | Notice/null | High |
| array_values()[0] | ❌ | ✅ Yes (wasteful) | ✅ | Needs ?? | Low |
| array_key_first() + subscript | ❌ | ❌ | ✅ | Needs ?? | Low |
| array_first() / array_last() | ❌ | ❌ | ✅ | Returns null | High |
Where array_key_first() and array_key_last() Fit In
These are often confused with the new functions. They’re different:
$inventory = ['apples' => 50, 'bananas' => 30, 'cherries' => 10]; // array_key_first / array_key_last — return the KEY (since PHP 7.3) $firstKey = array_key_first($inventory); // 'apples' $lastKey = array_key_last($inventory); // 'cherries' // array_first / array_last (PHP 8.5) — return the VALUE $firstVal = array_first($inventory); // 50 $lastVal = array_last($inventory); // 10 // Together — key + value pair of the first element $key = array_key_first($inventory); // 'apples' $value = array_first($inventory); // 50
The naming convention is intentional and consistent: functions that work on keys have “key” in the name; functions that work on values don’t. This is consistent with how most array functions are named in PHP.
Real-World Patterns
Processing Ordered Query Results
// Most recent order from a pre-sorted result set
$orders = $db->query('SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC', [$userId]);
$latestOrder = array_first($orders); // newest
$earliestOrder = array_last($orders); // oldest (in DESC order)
if ($latestOrder === null) {
return ['status' => 'no_orders'];
}
Breadcrumb and Navigation
$breadcrumbs = [
['label' => 'Home', 'url' => '/'],
['label' => 'Blog', 'url' => '/blog'],
['label' => 'PHP 8.5', 'url' => '/blog/php-85'],
];
$rootCrumb = array_first($breadcrumbs); // ['label' => 'Home', 'url' => '/']
$currentCrumb = array_last($breadcrumbs); // ['label' => 'PHP 8.5', ...]
echo 'You are here: ' . $currentCrumb['label'];
Configuration Fallbacks
$preferredLocales = ['en-IN', 'en-GB', 'en']; // Use the highest-priority locale $primaryLocale = array_first($preferredLocales); // 'en-IN' $fallbackLocale = array_last($preferredLocales); // 'en'
Validation Error Display
$errors = $validator->errors()->toArray();
// Show only the first error on each field to avoid overwhelming the user
foreach ($errors as $field => $fieldErrors) {
$firstError = array_first($fieldErrors);
echo "<span class='error'>{$firstError}</span>";
}
Combined with the PHP 8.5 Pipe Operator
Both functions are standard callables, so they compose naturally with the |> pipe operator:
$result = $rawData
|> fn($d) => array_filter($d, fn($item) => $item['active'])
|> fn($d) => array_values($d)
|> array_first(...); // Gets the first active item
Polyfill for PHP 7.3–8.4
A polyfills/array-first-array-last package provides polyfills for PHP 7.3 through PHP 8.4. For projects that need to run across PHP versions, a minimal manual polyfill is also straightforward:
// Place in a bootstrap file, loaded before any code that uses these functions
if (!function_exists('array_first')) {
function array_first(array $array): mixed {
if ($array === []) {
return null;
}
return $array[array_key_first($array)];
}
}
if (!function_exists('array_last')) {
function array_last(array $array): mixed {
if ($array === []) {
return null;
}
return $array[array_key_last($array)];
}
}
This implementation matches the native behaviour exactly — no pointer mutation, null on empty, O(1) — and can be dropped in for any PHP version that has array_key_first() (PHP 7.3+). When the minimum PHP version bumps to 8.5, delete the polyfill file; nothing else changes.
Bottom Line
array_first() and array_last() are small functions solving a problem that has existed in PHP since version 3. The problem isn’t that no solution existed — it’s that there were six different solutions with six different tradeoffs, and every developer picked a different one. Two things make the native functions worth caring about beyond their own utility:
They standardize on the right tradeoffs: pointer-safe, non-destructive, O(1), null-on-empty-consistent-with-the-rest-of-PHP. And they signal something broader about PHP 8.5’s development philosophy — this is a release focused on filling long-standing quality-of-life gaps rather than introducing headline syntax features.
If you maintain any codebase with custom array_first() or array_last() helper functions in the global namespace, fix that before upgrading. For everyone else, the upgrade path is frictionless — and the code that comes out the other side is cleaner for it.