framework icon indicating copy to clipboard operation
framework copied to clipboard

The array_first helper function breaks in PHP =< 8.3

Open erikverbeek opened this issue 5 months ago • 15 comments

Laravel Version

11 / 12

PHP Version

8.3.13

Database Driver & Version

No response

Description

Hello all,

The following pull request was merged yesterday: Prevent infinite loop on array_first, array_last, str_contains.

This fix uses the function array_find_key, which was introduced in PHP 8.4. This results in an Call to undefined function array_find_key error on our systems which are still running on 8.3.

Steps To Reproduce

Run the following code in PHP 8.3

 array_first([
   'en' => 'English',
   'nl' => 'Dutch',
   'it' => 'Italian',
   'fr' => 'French'
   ], fn($language, $languageCode) => $languageCode === 'nl');

erikverbeek avatar Sep 02 '25 09:09 erikverbeek

Sent PR https://github.com/laravel/helpers/pull/38 to address this issue

rodrigopedra avatar Sep 02 '25 13:09 rodrigopedra

@erikverbeek despite the fix sent to the laravel/helpers package, please consider migrating the usage of the helpers provided by that package to the ones provided by illuminate/support.

laravel/helpers is already basically a wrapper around illuminate/support. So your project already requires the illuminate/support under the hood.

In the code snippet you sent, you could use the Illuminate\Support\Arr::first helper:

Arr::first([
    'en' => 'English',
    'nl' => 'Dutch',
    'it' => 'Italian',
    'fr' => 'French'
    ], fn($language, $languageCode) => $languageCode === 'nl');

rodrigopedra avatar Sep 02 '25 13:09 rodrigopedra

Hello @erikverbeek and @crynobone

This issue may be closed in favor of: https://github.com/laravel/helpers/pull/38

AhmedAlaa4611 avatar Sep 08 '25 14:09 AhmedAlaa4611

@AhmedAlaa4611 https://github.com/laravel/helpers/pull/38 does not appear to address the issue, e.g. my application uses the laravel helpers array_first and array_last with PHP 8.3 (passing callbacks, etc) and since upgrading to 1.8.1 the polyfills for PHP 8.5 are used instead of laravel's helpers, causing multiple failures.

nicekiwi avatar Sep 15 '25 02:09 nicekiwi

@nicekiwi that is indeed an issue for projects that have both the laravel/helpers and any symfony/polyfill-php* packages that define the same function.

To avoid any hiccups, please update to use the Illuminate\Support\Arr helper class.

Mind that any of the symfony/polyfill-php* packages are required directly by laravel/helpers, so it is not laravel/helpers itself that is causing the clash.

You can run the following command to see which package is requiring the conflicting polyfill:

composer why symfony/polyfill-php85

Laravel recently started requiring this polyfill, so the impression that a new version of laravel/helpers is causing the issue might be a coincidence.

No helper's declaration nor existence check were changes in recent versions. Just implementation was changed to avoid clashes on projects running in recent PHP versions.

rodrigopedra avatar Sep 15 '25 03:09 rodrigopedra

@nicekiwi that is indeed an issue for projects that have both the laravel/helpers and any symfony/polyfill-php* packages that define the same function.

To avoid any hiccups, please update to use the Illuminate\Support\Arr helper class.

Mind that any of the symfony/polyfill-php* packages are required directly by laravel/helpers, so it is not laravel/helpers itself that is causing the clash.

You can run the following command to see which package is requiring the conflicting polyfill:

composer why symfony/polyfill-php85 Laravel recently started requiring this polyfill, so the impression that a new version of laravel/helpers is causing the issue might be a coincidence.

No helper's declaration nor existence check were changes in recent versions. Just implementation was changed to avoid clashes on projects running in recent PHP versions.

I've just dropped the laravel/helpers package and moved to the internal functions, I didn't realise it was a legacy polyfill itself. 🚀

nicekiwi avatar Sep 15 '25 03:09 nicekiwi

I've created a fix for this issue in PR #57345.

The problem was that the code was using PHP 8.4 functions (array_find_key, array_first, array_last) which are not available in PHP 8.3 and earlier.

The fix replaces these with PHP 8.3 compatible implementations:

  • array_find_key() → manual iteration
  • array_first()reset()
  • array_last()end()

I've tested the exact example from your issue and confirmed it works correctly now.

sumaiazaman avatar Oct 10 '25 19:10 sumaiazaman

The problem was that the code was using PHP 8.4 functions (array_find_key, array_first, array_last) which are not available in PHP 8.3 and earlier.

It is: Via polyfills. Which Laravel already uses. So this is not a fix

shaedrich avatar Oct 11 '25 12:10 shaedrich

I have the same problem in Arr:first() when passing a Collection and a callback, I'm not really understading why suddenly stopped working, I've tried it on PHP8.3 (using polyfills) and PHP8.4 (without using polyfills) but still has the same crash.

array_find_key(): Argument #1 ($array) must be of type array, Illuminate\Support\Collection given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Collections/Arr.php on line 268

I do have the laravel helpers package installed but should not be relevant...

gmutinel avatar Oct 23 '25 15:10 gmutinel

Hello @gmutinel you should not use the Laravel helpers package.

AhmedAlaa4611 avatar Oct 23 '25 16:10 AhmedAlaa4611

it does not change a thing (I tried), the problem lies in Polyfill84 which defines the first argument as array

if (!function_exists('array_find_key')) {
    function array_find_key(array $array, callable $callback) { return p\Php84::array_find_key($array, $callback); }
}

with this line on tinker you can replicate it

Arr::first(collect(),fn($c)=> $c)

I just don't understand why up until ~2 monthis ago was working with php8.3 and now it does not, plus I do not understand why it does not work on native php8.4 (cannot debug right now)

EDIT: I think I get it, with php8.3 it was introduced here: https://github.com/laravel/framework/pull/56619

PHP8.4 has a native support for an array type on array_find_key(), the real problem lies in the fact that this was a Breaking Change since Arr::first() parameter is defined as Iterable, which is not the case anymore when passing a callback

/**
     * Return the first element in an array passing a given truth test.
     *
     * @template TKey
     * @template TValue
     * @template TFirstDefault
     *
     * @param  iterable<TKey, TValue>  $array
     * @param  (callable(TValue, TKey): bool)|null  $callback
     * @param  TFirstDefault|(\Closure(): TFirstDefault)  $default
     * @return TValue|TFirstDefault
     */

What should the correct fix be?

gmutinel avatar Oct 23 '25 16:10 gmutinel

@gmutinel for the moment, you can call ->all() on the collection when passing it to the Arr::first() helper.

$first = Arr::first($collection->all(), $callback);

Or use the Collection@first() method, which takes a callback:

$first = $collection->first($callback);

Under the hood, Collection@first() calls Arr@first() with the collection's $item property, which is an array.

https://github.com/laravel/framework/blob/d42d6a603569a7b44744952d88b88337a2c8afed/src/Illuminate/Collections/Collection.php#L430-L433

rodrigopedra avatar Oct 23 '25 17:10 rodrigopedra

yeah, I just fixed with a ->toArray() in the couple of places I was using it, no problem, I should not be affected by any other change but I don't think it's only related to Arr::first() (did not check), imho this BC needs to be handled somehow.

gmutinel avatar Oct 23 '25 17:10 gmutinel

I'd use Collection@all() over Collection@toArray() if you are just after the first matching element.

Collection@all() just returns the collection's $item inner array property, while Collection@toArray() calls ->toArray() on every Arrayable element before returning.

https://github.com/laravel/framework/blob/d42d6a603569a7b44744952d88b88337a2c8afed/src/Illuminate/Collections/Traits/EnumeratesValues.php#L951-L954

That would, for example, convert all models to their array representation before returning if called on a collection of models.

rodrigopedra avatar Oct 23 '25 17:10 rodrigopedra

that's solid advice, thanks, I'll change to that

gmutinel avatar Oct 23 '25 17:10 gmutinel