rfc:array_first_last

PHP RFC: array_first() and array_last()

Introduction

In PHP 7.3, we got array_key_first() and array_key_last() to get the first and last keys from an array. What we don't have yet is a way to get the first and last values of an array. This is harder than you may think because:

  • The array keys are not necessarily integers, not necessarily starting with 0, etc...
  • Existing “tricks” like reset() and end() are semantically the wrong approach because they modify the “internal iterator” of the array. Furthermore, it also does not work properly on all types of expressions (e.g. an array returned from a function / plain array can cause a notice due to the by-ref argument).
  • Using $array[array_key_first($array)] is cumbersome

Proposal

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

These functions return the first/last value of the array, respectively. I.e. these functions are equivalent to $array[array_key_first($array)] and similarly for the last element. Alternatively, for array_first(), this could be written as a foreach loop where the first iteration returns.

If the array value to be returned is a reference, it is dereferenced automatically.

Behaviour on empty arrays

The RFC introducing the key functions also proposed to implement these functions, but the vote for those failed. The main complaint seemed to be the 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 therefore returning NULL does not allow distinguishing between an empty array and a NULL value. However, there are still strong arguments in favor of using NULL:

  • Consistent with $array[array_key_first($array)], i.e. accessing a non-existent key gives NULL
  • Consistent with array_find($arr, fn () => true) returning NULL on no match
  • Consistent with common framework helpers like Arr::first()
  • NULL is a rare legitimate value, so the potential for clashing is low
  • Works nicely with “??” operator
  • If it were to throw an exception instead, then you would need to check the size of the array upfront. Similarly, if NULL is a legit value then you can just check the array size upfront too.

Another interesting idea is to add an optional “$default” argument (i.e. if provided return the default; otherwise throw). However, since PHP arrays are untyped it's often hard to define something meaningful as a sentinel value. There is also no precedent for this design choice for the array functions. Furthermore, when a programmer reads array_first($somevar, $someothervar), it looks weird unless you already know that $somevar is an array and $someothervar is a fallback value. It just looks unintuitive.

Naming

Why array_first/array_last instead of array_value_first/array_value_last? First, this is consistent with how (most) array functions are named: the ones that work on keys have “key” in the name, and the ones that work on values don't have “value” in the name. Think about array_find, array_find_key, etc. Second, the shorter name is less verbose and intuitively at least I understood this is about the value.

Fibers

In the discussion thread from 2023 there was some confusion on how this interacted with Fibers. TL;DR: The concern was that if you have a call for array_key_first() followed by array_first() that the result could be inconsistent if a Fiber interrupted the execution. This is however based on the false premise that Fibers are threads, in fact they are not. This situation cannot happen.

Why include this in the engine rather than userland?

Three main reasons:

  1. Consistency with array_key_first()/array_key_last().
  2. The cost/maintenance burden is practically zero, the implementation is stupidly simple (4 lines trivial argument parsing boilerplate, 3 lines actual code; per function).
  3. Very fast implementation.

Backward Incompatible Changes

If anyone defined array_first() or array_last() in their global scope, then they need to guard it now with function_exists or throw it away because of the name clash. Although it should be noted that the global namespace is normally reserved for PHP's usage.

Proposed PHP Version(s)

Next PHP 8.x, i.e. PHP 8.5 at the time of writing.

RFC Impact

To Existing Extensions

These two new functions are added to ext-standard, where array_key_first() and array_key_last() also live.

Unaffected PHP Functionality

No changes to existing PHP functionality.

Proposed Voting Choices

2/3rd yes/no vote.

Add array_first() and array_last()?
Real name Yes No
Final result: 0 0
This poll has been closed.

Patches and Tests

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

Current discussion: TODO

Prior Work

Rejected Features

None yet.

rfc/array_first_last.txt · Last modified: 2025/04/01 20:18 by nielsdos