PHP 8.5’s array_first() and array_last(): The Deep-Dive Guide

1553 views
#PHP #PHP85 #WebDevelopment #BackendDevelopment #Programming #PHPDeveloper #Coding #OpenSource

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.

 

Frequently Asked Questions

+

What are array_first() and array_last() in PHP 8.5?

array_first() returns the first value from an array, while array_last() returns the last value. These new functions provide a cleaner and more readable way to access array elements without using reset() or end().
+

Which PHP version introduced array_first() and array_last()?

These functions were introduced in PHP 8.5 as part of the language's ongoing improvements to array handling and developer experience.
+

What happens if the array is empty?

If the array is empty, both array_first() and array_last() return null instead of generating warnings or errors.
+

Do these functions change the array's internal pointer?

No. Unlike reset() and end(), array_first() and array_last() do not modify the array's internal pointer.
+

Can array_first() and array_last() be used with associative arrays?

Yes. They work with both indexed and associative arrays, returning the first or last value regardless of the array keys.
+

What's the difference between reset()/end() and array_first()/array_last()?

The older functions modify the array's internal pointer, whereas the new PHP 8.5 functions simply return the first or last value without affecting the array.
+

Do these functions return keys or values?

They return the values of the first and last elements. If you need the keys, you can use array_key_first() or array_key_last().
+

Are array_first() and array_last() backward compatible?

No. These functions are available only in PHP 8.5 and later. For older versions, you'll need to continue using reset(), end(), or custom helper functions.
+

Should I replace reset() and end() with these new functions?

If your project runs on PHP 8.5 or newer and you only need the first or last value, using array_first() and array_last() is recommended because they are more readable and don't alter the array pointer.
+

Are array_first() and array_last() faster than traditional methods?

Their primary benefit is improved readability and cleaner code. Any performance differences are generally negligible for most applications. These FAQs are optimized for search terms such as "PHP 8.5 array_first()", "array_last() PHP", "new PHP array functions", "PHP 8.5 features", and "PHP array helper functions".
Previous Article

PHP 8.5's Pipe Operator: The Complete, Technically Correct Guide

Next Article

AI-Powered Search Engines: The Future of Answer-First Discovery

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨