How can I update code that uses the deprecated each() function?
Asked Answered
E

13

115

With PHP 7.2, each is deprecated. The documentation says:

Warning This function has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.

How can I update my code to avoid using it? Here are some examples:

  1. $ar = $o->me;
    reset($ar);
    list($typ, $val) = each($ar);
    
  2. $out = array('me' => array(), 'mytype' => 2, '_php_class' => null);
    $expected = each($out);
    
  3. for(reset($broken);$kv = each($broken);) {...}
    
  4. list(, $this->result) = each($this->cache_data);
    
  5. // iterating to the end of an array or a limit > the length of the array
    $i = 0;
    reset($array);
    while( (list($id, $item) = each($array)) || $i < 30 ) {
        // code
        $i++;
    }
    

When I execute the code on PHP 7.2 I receive the following error:

Deprecated: The each() function is deprecated. This message will be suppressed on further calls

Earthshaker answered 29/9, 2017 at 15:52 Comment(2)
doable with a foreach()Cuprous
array_map() with a closure would also work.Corrody
T
101
  1. For your first two example cases, you could use key() and current() to assign the values you need.

    $ar = $o->me;   // reset isn't necessary, since you just created the array
    $typ = key($ar);
    $val = current($ar);
    
  2. $out = array('me' => array(), 'mytype' => 2, '_php_class' => null);
    $expected = [key($out), current($out)];
    

    In those cases, you can use next() to advance the cursor afterward, but it may not be necessary if the rest of your code doesn't depend on that.

  3. For the third case, I'd suggest just using a foreach() loop instead and assigning $kv inside the loop.

    foreach ($broken as $k => $v) {
         $kv = [$k, $v];
    }
    
  4. For the fourth case, it looks like the key is disregarded in list(), so you can assign the current value.

    $this->result = current($this->cache_data);
    

    Like the first two cases, it may be necessary to advance the cursor with next() depending on how the rest of your code interacts with $this->cache_data.

  5. Fifth can be replaced with a for() loop.

    reset($array);
    for ($i = 0; $i < 30; $i++) {
        $id = key($array);
        $item = current($array);
        // code
        next($array);
    }
    
Toneless answered 29/9, 2017 at 16:30 Comment(3)
For 4., I think it's right to replace list($a, $b) = each($arr) by list($a, $b) = array(key($arr), current($arr)); next($arr); isn't it ?Arratoon
See universal automated migration version bellow: https://mcmap.net/q/188069/-how-can-i-update-code-that-uses-the-deprecated-each-functionCross
For case 1 I believe you need to make sure internal pointer is advanced after calling current() since it doesn't move the pointer.Muoimuon
C
63

2019+ Instant Upgrade of each()

There are actually plenty of cases that each() can be replaced, that's why there are so many different upvoted answers in this question.

-while (list($key, $callback) = each($callbacks)) {
+foreach ($callbacks as $key => $callback) {
     // ...
 }

And:

-while (list($key) = each($callbacks)) {
+foreach (array_keys($callbacks) as $key) {
     // ...
 }

You can replace one by one manually. But isn't there a better way?

I help to migrate projects, where are over 150+ cases like this. I'm lazy so I made a tool called Rector, that converts the code the way above (+ there are more cases, but I don't want to spam the answer).

It's part of the PHP_72 set.


4 Steps to Upgrade your Code

1. Install it

composer require rector/rector --dev

2. Create rector.php config

vendor/bin/rector init

3. Add PHP_72 set

<?php

use Rector\Core\Configuration\Option;
use Rector\Set\ValueObject\SetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
    $parameters->set(Option::SETS, [
        Setlist::PHP_72,
    ]);
};

4. Run it on your code

vendor/bin/rector process src --set php72

I hope it helps you with your migration.


If there is some bug or anomaly, it's Rector missed case. Create an issue, so we can fix it and make it work for every case possible.

Cross answered 4/4, 2019 at 11:16 Comment(9)
The last example using key() and current() is correct value-wise but disregards the fact that each() also advances the array cursor as a side-effect. Also, you probably mean $val and not $callback in the list() call. A proper replacement would be: -list($key, $val) = each($callbacks); +$key = key($opt->option); +$val = current($opt->option); +next($callbacks);Drowsy
Could you create an issue for this so it's fixed? github.com/rectorphp/rector/issuesCross
I'm not using that library, I was just googling for an each() replacement, came across your post here and found it useful but just thought I'd point out that small omission so you could correct your post.Drowsy
I see. Still always better address that in Github repo issue. Rarely maintainers visit their old responses and the bug usually hits more peopleCross
@Drowsy I've updated the example. It's very hard to read from the inlined code as text comment, gist.github.com woudl be better. Could you check?Cross
You added next() which takes care of advancing the pointer but still need to use the same variable names throughout ($val vs $callback) if the examples should be equivalent. See 3v4l.org/DWYMt for full code examples and tests proving the results.Drowsy
I get lost in so many lines of code. Could you give me only the content to past there?Cross
I've given you the complete answer twice already. If you're not interested in spending the one minute it takes to fully understand where your answer goes wrong, I leave it up to you. If you just re-read your solution it's pretty obvious since you do not even use the same variable names...Drowsy
Sorry, it's just not clear to me and this is just example answer. Any code could be there. The point is all the changes should be automated, exactly for this confusion. I'll drop the snippet, no to lead anyone astrey.Cross
V
50

I found a way to fix it and thought to share the information. Here are also other cases about how to upgrade each() loops to foreach().

Case 1: Missing $value

reset($array);
while (list($key, ) = each($array)) {

Update to:

foreach(array_keys($array) as $key) {

Case 2: Missing $key

reset($array);
while (list(, $value) = each($array)) {

Update to:

foreach($array as $value) {

Case 3: Not missing anything

reset($array);
while (list($key, $value) = each($array)) {

Update to:

foreach($array as $key => $value) {
Visitation answered 10/2, 2018 at 13:5 Comment(0)
F
41

you could create your own each() function using key(), current() and next(). then replace your calls with that function, like this:

<?php
function myEach(&$arr) {
    $key = key($arr);
    $result = ($key === null) ? false : [$key, current($arr), 'key' => $key, 'value' => current($arr)];
    next($arr);
    return $result;
}

1.

$ar = $o->me;
reset($ar);
list($typ, $val) = myEach($ar);

2.

$out = array('me' => array(), 'mytype' => 2, '_php_class' => null);
$expected = myEach($out);

3.

for(reset($broken);$kv = myEach($broken);) {...}
Freda answered 29/9, 2017 at 16:21 Comment(2)
If you want to fully emulate each, I guess you'd need the "key" and "value" keys in the output as well as 0 and 1.Ventricle
@Don'tPanic, edited answer, this situation didn't need it but there could be cases out there that might. thanks for suggestionFreda
A
12
reset($array);
while (list($key, $value) = each($array)) {

UPDATE

reset($array);
foreach($array as $key => $value) {
Amalgamation answered 19/9, 2018 at 13:29 Comment(4)
Important to note that these are not equivalent, although in most cases a foreach will suffice – if you modify $array in the while loop it will iterate over the modified values. foreach creates a copy of the list and iterates over it, so mutations to $array will not change the loop.Carmel
@Carmel good point, that's true. Also, with foreach, reset is not necessary.Ventricle
The reset is mostly usless before foreach.Composed
That's completely different function... cannot be used in recursionsKaminsky
U
7

The way you most definitely shouldn't do is put the function "back into php" by adding it to the auto_prepend_file setting in php.ini

auto_prepend_file = "/var/www/php/auto_prepend.php"

Then make the file and enter in the function with an function_exists wrapper.

<?php
/**
 * Adds the depreciated each() function back into 7.2
 */
if (!function_exists('each')) {
    function each($arr) {
        $key = key($arr);
        $result = ($key === null) ? false : [$key, current($arr), 'key' => $key, 'value' => current($arr)];
        next($arr);
        return $result;
    }
}

This essentially declares the function before your php application runs. When your application tries to run the each function it'll use your version instead.

This is absolutely not the way you should be approaching this problem, especially in production! However you're a developer with time constraints and you just want to try arbitrary frameworks for your next project and they haven't been updated to work on your local development server without winding back your php version.

When you've committed to a code base for your project please go ahead and implement the changes in the accepted answer because they do work.

I used Wee Zel's emulation of the each function

Unilocular answered 19/2, 2019 at 13:27 Comment(1)
The replacement function runs into an endless loop in my case. probably because it doesent take into account reset() and next()Gallice
G
5

Here are some ways to do it:

The standard foreach loop (very readable):

foreach($this->contents as list($products_id)) {
    $total_items += $this->get_quantity($products_id);
}

Or, reducing:

$total_items = array_reduce($this->contents, function($acc, $item) {
    return $acc + $this->get_quantity($products_id[0]);
});

Or, in a functional expression:

$total_items = array_sum(array_map([$this, 'get_quantity'],
                         array_column($this->contents, 0)));

None of these methods need reset($this->contents); preceding it.

Glauconite answered 10/2, 2018 at 11:59 Comment(0)
A
1

To expand on Petro Mäntylä excellent correct answer for Case 3:

Here is a full example of a "Case 3" situation, because I find full examples far more informative that one line code fragments:

This is genuine code from a 3rd party old code base (TCPDF)

DEPRECATED:

while (list($id, $name) = each($attr_array)) {
      $dom[$key]['attribute'][$name] = $attr_array[$id];
      ...              
      ...             
   }

FIXED:

 // while (list($id, $name) = each($attr_array)) {
 foreach($attr_array as $feKey => $feRow){
    // $dom[$key]['attribute'][$name] = $attr_array[$id];
    $dom[$key]['attribute'][$feRow] = $attr_array[$feKey];
    ...
    ...
    }
 unset($feKey,$feRow);
Ambages answered 2/4, 2019 at 17:48 Comment(0)
H
1

If you're lazy and want to fix an old website that uses a bajillion each calls and worked just fine before PHP 8, you can repair it by placing this in some global file:

// PHP 8 compatibility hack
if (!function_exists('each')) {
    function each(&$arr) {
        $key = key($arr);
        if ($key === null) return false;
        $val = current($arr);
        next($arr);
        return [1 => $val, 'value' => $val, 0 => $key, 'key' => $key];
    }
}

This one won't cause an endless loop, unlike user840474's version.

Hewes answered 1/11, 2023 at 2:22 Comment(4)
@mickmackusa That's exactly what this built-in function did in PHP 7 and below: "each — Return the current key and value pair from an array and advance the array cursor. [...] Returns the current key and value pair from the array array. This pair is returned in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key contain the key name of the array element, and 1 and value contain the data. If the internal pointer for the array points past the end of the array contents, each() returns false."Hewes
Okay, I'll delete my comment. I'm glad I didn't dv the answer. I'm glad I never used each() in any projects. Was that the awkward ordering of the associative elements too? For PSR-12 compliance, we should see curly braces for the early return. Do you actually need to call current() if you already have $key? (I don't think I would.)Opaline
Could be: return [1 => $arr[$key], 'value' => $arr[$key], 0 => $key, 'key' => $key]; then you can avoid declaring $val.Opaline
@Opaline $arr[$key] would break the weird use-case where each is called on a non-array object to enumerate its properties, which current is actually fine with (although this is deprecated). The PHP docs strongly recommend against doing such, which implies that someone has done this in the wild somewhere.Hewes
B
0

Replace this code

while (list($_key,$_resourceTypeNode) = each($GLOBALS['config']['ResourceType'])) {
//             if ($_resourceTypeNode['name'] === $resourceTypeName) {
//                 $this->_resourceTypeConfigCache[$resourceTypeName] = new CKFinder_Connector_Core_ResourceTypeConfig($_resourceTypeNode);

//                 return $this->_resourceTypeConfigCache[$resourceTypeName];
//             }
//         }

with this one

foreach ($GLOBALS['config']['ResourceType'] as $key => $_resourceTypeNode) {
            if (isset($_resourceTypeNode['name'])) {
                if ($_resourceTypeNode['name'] === $resourceTypeName) {
                    $this->_resourceTypeConfigCache[$resourceTypeName] = new CKFinder_Connector_Core_ResourceTypeConfig($_resourceTypeNode);
                    
                    return $this->_resourceTypeConfigCache[$resourceTypeName];
                }
            }
        }
Broucek answered 2/12, 2019 at 11:57 Comment(0)
M
-1
 //  while (list($products_id, ) = each($this->contents)) {
   //  $total_items += $this->get_quantity($products_id);
 // }

Update To :

foreach(array_keys($this->contents) as $products_id) {
  $total_items += $this->get_quantity($products_id);
}

Other Condition:

foreach($this->contents as $key =>$value) {
  $total_items += $this->get_quantity($products_id);
}
Misestimate answered 25/12, 2019 at 19:38 Comment(0)
G
-1

For all commentators. Function foreach not work with dinamic arrays with changing of elements count. I think using custom function "each" John Tilley - single right way for dinamic arrays. For static arrays not use "each" nobody. "Foreach" know all.

Ginetteginevra answered 13/8, 2022 at 8:5 Comment(1)
Tried to improve the post, but like mostly always, "the edit queue is full at the moment - try again in a few minutes!"Muttonhead
C
-3

What about using this function?

function array_fetch(array $a) {
   $element = current($a);
   next($a);
   return $element;
}
Curare answered 22/2, 2019 at 9:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.