PHP 8.5’s array_first() and array_last() — Small Functions, Big Impact in Laravel

You’ve been writing 3-4 lines of code for something that should take one. PHP 8.5 finally fixes it.

Raise your hand if you’d have bet money that PHP already had array_first() and array_last(). Most developers assume these have always existed. They haven’t — and the workarounds we’ve been reaching for instead are everywhere in Laravel codebases.

Perhaps a bit overdue — array_key_first() and array_key_last() were added in PHP 7.3 — but PHP 8.5 finally gives us built-in functions to get the first and last values from arrays.

That gap — keys without values — led to years of clunky workarounds. This article covers every pattern these two functions replace, how they behave with edge cases, and ten real Laravel scenarios where they simplify your code immediately.


The Problem They Solve

PHP 7.3 added array_key_first() and array_key_last(), which simplified how we grab the first and last keys, respectively, of an array. However, getting the values associated is fairly cumbersome.

Here’s what every developer has been writing:

Pattern 1: reset() — the pointer manipulation hack

// Works but mutates the array's internal pointer
$first = reset($collection->toArray());
$last  = end($collection->toArray());

Pattern 2: array_values()[0] — the reindex and grab

// Verbose. Creates a copy of the entire array just to grab index 0
$first = array_values($items)[0] ?? null;
$last  = array_values($items)[count($items) - 1] ?? null;

Pattern 3: array_key_first() + lookup — the two-step

// Requires knowing the key first, then fetching the value
$firstKey = array_key_first($items);
$first    = $firstKey !== null ? $items[$firstKey] : null;

Pattern 4: Collection workaround — when you shouldn’t need a Collection

// Full Collection instantiation just to call first() or last()
$first = collect($items)->first();
$last  = collect($items)->last();

All four patterns work. All four are more code than the task deserves.


The Solution: Clean, Readable, Obvious

The array_first() and array_last() functions return the first or last value of an array, respectively. If the array is empty, null is returned — making it easy to compose with the ?? operator.

$users = ['Alice', 'Bob', 'Charlie'];

$first = array_first($users); // 'Alice'
$last  = array_last($users);  // 'Charlie'

// Empty array returns null — safe to use with ??
$fallback = array_first([]) ?? 'default'; // 'default'

Works exactly the same with associative arrays — it returns the value, not the key:

$data = ['name' => 'Sadique', 'role' => 'developer', 'city' => 'Mumbai'];

array_first($data); // 'Sadique'
array_last($data);  // 'Mumbai'

Edge Cases Worth Knowing

Before reaching for these in production, here are four behaviours to understand:

Null values in the array don’t mean empty:

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

array_first($items); // null — but the array is NOT empty
array_last($items);  // 'third'

// To distinguish "empty array" from "first value is null", check count()
if (count($items) > 0) {
    $first = array_first($items); // Could legitimately be null
}

Array order matters — especially after sorting:

$prices = [49.99, 19.99, 99.99, 9.99];

sort($prices); // [9.99, 19.99, 49.99, 99.99]

array_first($prices); // 9.99 — cheapest after sort
array_last($prices);  // 99.99 — most expensive after sort

No callback support (unlike Collection::first()):

// This is NOT how array_first() works — no callable
// $activeUser = array_first($users, fn($u) => $u->isActive()); ← WRONG

// For filtered first/last, use array_filter() first:
$active = array_filter($users, fn($u) => $u->isActive());
$firstActive = array_first($active); // Correct

Keys are NOT reset:

$filtered = array_filter([1, 2, 3, 4, 5], fn($n) => $n > 2);
// $filtered is [2 => 3, 3 => 4, 4 => 5] — keys preserved

array_first($filtered); // 3 — correct, grabs the first value
array_last($filtered);  // 5 — correct, grabs the last value
// No need to re-index with array_values() first

10 Real Laravel Use Cases

1. Latest Migration Version Check

// Get the most recently run migration
$latestMigration = array_last(
    DB::table('migrations')->orderBy('batch')->pluck('migration')->toArray()
);

// Before PHP 8.5
$migrations = DB::table('migrations')->orderBy('batch')->pluck('migration')->toArray();
$latestMigration = end($migrations);

2. First Validation Error Message

// In a custom exception handler
$firstError = array_first($validator->errors()->all());

return response()->json(['message' => $firstError], 422);

// Before: $errors = $validator->errors()->all(); $firstError = reset($errors);

3. Most Recent Queue Job Attempt

// Get the last attempt timestamp from a failed job's attempts array
$payload   = json_decode($failedJob->payload, true);
$lastAttempt = array_last($payload['attempts'] ?? []);

4. First Matching Config Value

// Get the first configured mail driver
$primaryDriver = array_first(config('mail.mailers'));

// Works cleanly with ?? for fallback
$driver = array_first(config('mail.mailers')) ?? 'smtp';

5. Breadcrumb Navigation

// First and last crumbs for SEO structured data
$breadcrumbs = $page->getBreadcrumbs();

$rootCrumb    = array_first($breadcrumbs); // Home
$currentCrumb = array_last($breadcrumbs);  // Current page

// Feed directly into structured data
$structuredData = [
    'name' => $currentCrumb['label'],
    'item' => $currentCrumb['url'],
];

6. Webhook Event Processing

// Get the first and last events from a webhook payload
$events    = $webhookPayload['events'];
$firstEvent = array_first($events); // e.g., 'payment.created'
$lastEvent  = array_last($events);  // e.g., 'payment.completed'

Log::info("Webhook: {$firstEvent['type']} through {$lastEvent['type']}");

7. API Pagination — First and Last IDs

// Cursor-based pagination: grab boundaries from a result set
$results = $query->get()->toArray();

$afterCursor  = array_last($results)['id']  ?? null;
$beforeCursor = array_first($results)['id'] ?? null;

return response()->json([
    'data'   => $results,
    'cursor' => ['before' => $beforeCursor, 'after' => $afterCursor],
]);

8. Feature Flag Priority

// Get the highest-priority feature flag for a user
$flags = FeatureFlag::forUser($user)
    ->orderByDesc('priority')
    ->pluck('name')
    ->toArray();

$topFlag = array_first($flags); // Highest priority flag

9. Database Seeder — Check First/Last Record

// In a seeder, verify the first and last records were created correctly
$users = User::orderBy('id')->get()->toArray();

$firstUser = array_first($users);
$lastUser  = array_last($users);

$this->command->info("Seeded from {$firstUser['email']} to {$lastUser['email']}");

10. Pipeline Stage Logging

// Log entry and exit points of a pipeline
$stages = $pipeline->getStages();

Log::debug('Pipeline starting at: ' . array_first($stages)::class);
Log::debug('Pipeline ending at: '   . array_last($stages)::class);

Pipe Operator Combo (PHP 8.5 Bonus)

Since array_first() and array_last() both ship in PHP 8.5 alongside the pipe operator, they compose beautifully:

// Get the most expensive active product name
$topProduct = $products
    |> array_filter(?, fn($p) => $p['active'])
    |> (fn($p) => usort($p, fn($a,$b) => $b['price'] <=> $a['price']) ?: $p)
    |> array_first(?);

// Get first error from a filtered validation result
$firstCritical = $errors
    |> array_filter(?, fn($e) => $e['severity'] === 'critical')
    |> array_first(?);

When partial function application lands in PHP 8.6, these patterns will get even cleaner with the ? placeholder syntax.


Should You Use These Over collect()->first()?

Yes — when you already have a plain PHP array and don’t need Collection’s other features. The performance difference is small for most use cases, but the intent is clearer:

// You have a plain array. Don't create a Collection just for first().
$items = SomeRepository::getAll(); // Returns array

// Before — unnecessary Collection instantiation
$first = collect($items)->first();

// After — direct, no overhead
$first = array_first($items);

Use collect()->first() when you need a callback filter in the same expression, or when you’re already working with a Collection. Use array_first() when you have a plain array and just want the first value.


The Takeaway

array_first() and array_last() won’t change the architecture of your application. But they will quietly remove a class of boilerplate that you’ve been writing — and reading — for years.

The real signal is what they represent: PHP’s continued investment in the developer experience at the small-function level. Each PHP release since 8.0 has shipped at least one “why didn’t this always exist” function. array_first() and array_last() are PHP 8.5’s entry in that category.

Update your PHP version check in composer.json, do a codebase search for reset(, end(, array_values()[0], and array_key_first( — and start replacing them.


Follow me for daily deep-dives on Laravel, PHP, Vue.js, and AI integrations. New article every day.

Leave a Reply

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