PHP 'foreach' वास्तव में कैसे काम करता है?




loops iteration (5)

मुझे यह कहकर उपसर्ग करना है कि मुझे पता है कि क्या foreach है, करता है और इसका उपयोग कैसे करें। यह सवाल इस बात से चिंतित है कि यह बोननेट के तहत कैसे काम करता है, और मुझे "इस तरह के बारे में कोई जवाब नहीं चाहिए कि आप foreach साथ एक सरणी कैसे लूप करते हैं"।

लंबे समय तक मैंने माना कि foreach सरणी के साथ ही काम किया। तब मुझे इस तथ्य के कई संदर्भ मिले कि यह सरणी की एक प्रति के साथ काम करता है, और मैंने तब से यह कहानी का अंत माना है। लेकिन मैंने हाल ही में इस मामले पर चर्चा की, और थोड़ा प्रयोग के बाद पाया कि यह वास्तव में 100% सच नहीं था।

मुझे दिखाएं कि मेरा क्या मतलब है। निम्नलिखित परीक्षण मामलों के लिए, हम निम्नलिखित सरणी के साथ काम करेंगे:

$array = array(1, 2, 3, 4, 5);

टेस्ट केस 1 :

foreach ($array as $item) {
  echo "$item\n";
  $array[] = $item;
}
print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 2 3 4 5 1 2 3 4 5 */

यह स्पष्ट रूप से दिखाता है कि हम सीधे स्रोत सरणी के साथ काम नहीं कर रहे हैं - अन्यथा लूप हमेशा के लिए जारी रहेगा, क्योंकि हम लगातार लूप के दौरान सरणी पर वस्तुओं को दबा रहे हैं। लेकिन यह सुनिश्चित करने के लिए कि यह मामला है:

टेस्ट केस 2 :

foreach ($array as $key => $item) {
  $array[$key + 1] = $item + 2;
  echo "$item\n";
}

print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 3 4 5 6 7 */

यह हमारे प्रारंभिक निष्कर्ष का समर्थन करता है, हम लूप के दौरान स्रोत सरणी की एक प्रति के साथ काम कर रहे हैं, अन्यथा हम लूप के दौरान संशोधित मान देखेंगे। परंतु...

यदि हम manual में देखते हैं, तो हमें यह कथन मिल जाता है:

जब foreach पहले निष्पादन शुरू होता है, आंतरिक सरणी सूचक स्वचालित रूप से सरणी के पहले तत्व पर रीसेट हो जाता है।

ठीक है ... ऐसा लगता है कि foreach स्रोत सरणी के सरणी सूचक पर निर्भर करता है। लेकिन हमने अभी साबित कर दिया है कि हम स्रोत सरणी के साथ काम नहीं कर रहे हैं, है ना? खैर, पूरी तरह से नहीं।

टेस्ट केस 3 :

// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));

foreach ($array as $item) {
  echo "$item\n";
}

var_dump(each($array));

/* Output
  array(4) {
    [1]=>
    int(1)
    ["value"]=>
    int(1)
    [0]=>
    int(0)
    ["key"]=>
    int(0)
  }
  1
  2
  3
  4
  5
  bool(false)
*/

इसलिए, इस तथ्य के बावजूद कि हम सीधे स्रोत सरणी के साथ काम नहीं कर रहे हैं, हम सीधे स्रोत सरणी सूचक के साथ काम कर रहे हैं - तथ्य यह है कि लूप के अंत में सूचक सरणी के अंत में है, यह दिखाता है। इसके अलावा यह सच नहीं हो सकता है - यदि यह था, तो परीक्षण केस 1 हमेशा के लिए लूप होगा।

PHP मैनुअल यह भी कहता है:

जैसा कि फोरच लूप के भीतर बदलते आंतरिक सरणी सूचक पर निर्भर करता है, अप्रत्याशित व्यवहार का कारण बन सकता है।

खैर, आइए पता करें कि "अप्रत्याशित व्यवहार" क्या है (तकनीकी रूप से, कोई व्यवहार अप्रत्याशित है क्योंकि अब मुझे पता नहीं है कि क्या उम्मीद करनी है)।

टेस्ट केस 4 :

foreach ($array as $key => $item) {
  echo "$item\n";
  each($array);
}

/* Output: 1 2 3 4 5 */

टेस्ट केस 5 :

foreach ($array as $key => $item) {
  echo "$item\n";
  reset($array);
}

/* Output: 1 2 3 4 5 */

... वहां कुछ भी अप्रत्याशित नहीं है, वास्तव में ऐसा लगता है कि यह "स्रोत की प्रति" सिद्धांत का समर्थन करता है।

प्रश्न

यहाँ क्या हो रहा है? मेरा सी-फू मेरे लिए PHP स्रोत कोड को देखकर उचित निष्कर्ष निकालने में सक्षम नहीं है, अगर कोई इसे मेरे लिए अंग्रेजी में अनुवाद कर सकता है तो मैं इसकी सराहना करता हूं।

ऐसा लगता है कि foreach सरणी की एक प्रति के साथ काम करता है, लेकिन लूप के बाद सरणी के अंत में स्रोत सरणी के सरणी सूचक सेट करता है।

  • क्या यह सही और पूरी कहानी है?
  • यदि नहीं, तो यह वास्तव में क्या कर रहा है?
  • क्या कोई ऐसी स्थिति है जहां फ्रेच के दौरान एरे पॉइंटर ( each() , reset() et al।) को समायोजित करने वाले फ़ंक्शंस का उपयोग करना लूप के नतीजे को प्रभावित कर सकता है?

उदाहरण 3 में आप सरणी को संशोधित नहीं करते हैं। अन्य सभी उदाहरणों में आप या तो सामग्री या आंतरिक सरणी सूचक को संशोधित करते हैं। जब यह असाइनमेंट ऑपरेटर के अर्थशास्त्र के कारण PHP arrays की बात आती है तो यह महत्वपूर्ण है।

PHP में सरणी के लिए असाइनमेंट ऑपरेटर आलसी क्लोन की तरह काम करता है। एक वैरिएबल को दूसरे में असाइन करना जिसमें एक सरणी है, अधिकांश भाषाओं के विपरीत सरणी को क्लोन कर देगी। हालांकि, वास्तविक क्लोनिंग तब तक नहीं की जाएगी जब तक इसकी आवश्यकता न हो। इसका मतलब यह है कि क्लोन तभी होगा जब किसी भी चर को संशोधित किया जाए (प्रतिलिपि लिखना)।

यहाँ एक उदाहरण है:

$a = array(1,2,3);
$b = $a;  // This is lazy cloning of $a. For the time
          // being $a and $b point to the same internal
          // data structure.

$a[] = 3; // Here $a changes, which triggers the actual
          // cloning. From now on, $a and $b are two
          // different data structures. The same would
          // happen if there were a change in $b.

अपने परीक्षण मामलों में वापस आकर, आप आसानी से कल्पना कर सकते हैं कि foreach सरणी के संदर्भ के साथ किसी प्रकार का इटरेटर बनाता है। यह संदर्भ बिल्कुल मेरे उदाहरण में परिवर्तनीय $b तरह काम करता है। हालांकि, संदर्भ के साथ इटेटरेटर केवल लूप के दौरान रहता है और फिर, दोनों को त्याग दिया जाता है। अब आप देख सकते हैं कि, सभी मामलों में लेकिन 3, लूप के दौरान सरणी संशोधित की जाती है, जबकि यह अतिरिक्त संदर्भ जीवित है। यह एक क्लोन ट्रिगर करता है, और यह बताता है कि यहां क्या हो रहा है!

इस कॉपी-ऑन-राइट व्यवहार के किसी अन्य दुष्प्रभाव के लिए यहां एक उत्कृष्ट लेख है: PHP टर्नरी ऑपरेटर: तेज़ या नहीं?


foreach() साथ काम करते समय कुछ बिंदु ध्यान दें:

ए) foreach मूल सरणी की संभावित प्रतिलिपि पर काम करता है। इसका मतलब है कि foreach () शेयर डेटा संग्रहण होगा जब तक कि एक prospected copy नहीं बनाया गया है manual

बी) क्या एक संभावित प्रतिलिपि ट्रिगर करता है? संभावित प्रतिलिपि copy-on-write की नीति के आधार पर बनाई गई है, यानी, जब भी किसी सरणी को फ़ोरैच () में पास किया जाता है, तो मूल सरणी का क्लोन बनाया जाता है।

सी) मूल सरणी और foreach () पुनरावर्तक DISTINCT SENTINEL VARIABLES , यानी मूल सरणी के लिए एक और अन्य foreach के लिए; नीचे टेस्ट कोड देखें। SPL , Iterators , और ऐरे इटरेटर

स्टैक ओवरफ़्लो प्रश्न यह सुनिश्चित करने के लिए कि PHP में 'foreach' लूप में मान रीसेट किया गया है? आपके प्रश्न के मामलों (3,4,5) को संबोधित करता है।

निम्नलिखित उदाहरण दिखाते हैं कि प्रत्येक () और रीसेट () foreach () (for example, the current index variable) SENTINEL चर (for example, the current index variable) को प्रभावित नहीं करता है।

$array = array(1, 2, 3, 4, 5);

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

foreach($array as $key => $val){
    echo "foreach: $key => $val<br/>";

    list($key2,$val2) = each($array);
    echo "each() Original(inside): $key2 => $val2<br/>";

    echo "--------Iteration--------<br/>";
    if ($key == 3){
        echo "Resetting original array pointer<br/>";
        reset($array);
    }
}

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

आउटपुट:

each() Original (outside): 0 => 1
foreach: 0 => 1
each() Original(inside): 1 => 2
--------Iteration--------
foreach: 1 => 2
each() Original(inside): 2 => 3
--------Iteration--------
foreach: 2 => 3
each() Original(inside): 3 => 4
--------Iteration--------
foreach: 3 => 4
each() Original(inside): 4 => 5
--------Iteration--------
Resetting original array pointer
foreach: 4 => 5
each() Original(inside): 0=>1
--------Iteration--------
each() Original (outside): 1 => 2

स्पष्टीकरण ( php.net से उद्धरण):

सरणी_एक्सप्रेस द्वारा दिए गए सरणी पर पहला फॉर्म लूप। प्रत्येक पुनरावृत्ति पर, वर्तमान तत्व का मान $ मान को सौंपा गया है और आंतरिक सरणी सूचक एक द्वारा उन्नत है (इसलिए अगली पुनरावृत्ति पर, आप अगले तत्व को देखेंगे)।

तो, आपके पहले उदाहरण में आपके पास केवल सरणी में एक तत्व है, और जब सूचक स्थानांतरित हो जाता है तो अगला तत्व मौजूद नहीं होता है, इसलिए जब आप नया तत्व फोरच जोड़ते हैं तो यह पहले से ही "तय" होता है कि यह अंतिम तत्व के रूप में होता है।

आपके दूसरे उदाहरण में, आप दो तत्वों से शुरू करते हैं, और फ़ोरैच लूप अंतिम तत्व पर नहीं है, इसलिए यह अगले पुनरावृत्ति पर सरणी का मूल्यांकन करता है और इस प्रकार यह महसूस करता है कि सरणी में नया तत्व है।

मेरा मानना ​​है कि दस्तावेज़ीकरण में स्पष्टीकरण के प्रत्येक पुनरावृत्ति भाग पर यह सब परिणाम है, जिसका अर्थ है कि foreach {} में कोड को कॉल करने से पहले सभी तर्क करता है।

परीक्षण का मामला

यदि आप इसे चलाते हैं:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

आपको यह आउटपुट मिलेगा:

1 2 3 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

जिसका अर्थ यह है कि उसने संशोधन स्वीकार कर लिया और इसके माध्यम से चला गया क्योंकि इसे "समय में" संशोधित किया गया था। लेकिन अगर आप ऐसा करते हैं:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

You will get:

1 2 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Which means that array was modified, but since we modified it when the foreach already was at the last element of the array, it "decided" not to loop anymore, and even though we added new element, we added it "too late" and it was not looped through.

Detailed explanation can be read at How does PHP 'foreach' actually work? which explains the internals behind this behaviour.


As per the documentation provided by PHP manual.

On each iteration, the value of the current element is assigned to $v and the internal
array pointer is advanced by one (so on the next iteration, you'll be looking at the next element).

So as per your first example:

$array = ['foo'=>1];
foreach($array as $k=>&$v)
{
   $array['bar']=2;
   echo($v);
}

$array have only single element, so as per the foreach execution, 1 assign to $v and it don't have any other element to move pointer

But in your second example:

$array = ['foo'=>1, 'bar'=>2];
foreach($array as $k=>&$v)
{
   $array['baz']=3;
   echo($v);
}

$array have two element, so now $array evaluate the zero indices and move the pointer by one. For first iteration of loop, added $array['baz']=3; as pass by reference.


PHP foreach loop can be used with Indexed arrays , Associative arrays and Object public variables .

In foreach loop, the first thing php does is that it creates a copy of the array which is to be iterated over. PHP then iterates over this new copy of the array rather than the original one. This is demonstrated in the below example:

<?php
$numbers = [1,2,3,4,5,6,7,8,9]; # initial values for our array
echo '<pre>', print_r($numbers, true), '</pre>', '<hr />';
foreach($numbers as $index => $number){
    $numbers[$index] = $number + 1; # this is making changes to the origial array
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # showing data from the copied array
}
echo '<hr />', '<pre>', print_r($numbers, true), '</pre>'; # shows the original values (also includes the newly added values).

Besides this, php does allow to use iterated values as a reference to the original array value as well. This is demonstrated below:

<?php
$numbers = [1,2,3,4,5,6,7,8,9];
echo '<pre>', print_r($numbers, true), '</pre>';
foreach($numbers as $index => &$number){
    ++$number; # we are incrementing the original value
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # this is showing the original value
}
echo '<hr />';
echo '<pre>', print_r($numbers, true), '</pre>'; # we are again showing the original value

Note: It does not allow original array indexes to be used as references .

Source: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples







php-internals