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


Answers

उदाहरण 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 टर्नरी ऑपरेटर: तेज़ या नहीं?

Question

मुझे यह कहकर उपसर्ग करना है कि मुझे पता है कि क्या 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।) को समायोजित करने वाले फ़ंक्शंस का उपयोग करना लूप के नतीजे को प्रभावित कर सकता है?



Great question, because many developers, even experienced ones, are confused by the way PHP handles arrays in foreach loops. In the standard foreach loop, PHP makes a copy of the array that is used in the loop. The copy is discarded immediately after the loop finishes. This is transparent in the operation of a simple foreach loop. उदाहरण के लिए:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    echo "{$item}\n";
}

This outputs:

apple
banana
coconut

So the copy is created but the developer doesn't notice, because the original array isn't referenced within the loop or after the loop finishes. However, when you attempt to modify the items in a loop, you find that they are unmodified when you finish:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $item = strrev ($item);
}

print_r($set);

This outputs:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
)

Any changes from the original can't be notices, actually there are no changes from the original, even though you clearly assigned a value to $item. This is because you are operating on $item as it appears in the copy of $set being worked on. You can override this by grabbing $item by reference, like so:

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $item = strrev($item);
}
print_r($set);

This outputs:

Array
(
    [0] => elppa
    [1] => ananab
    [2] => tunococ
)

So it is evident and observable, when $item is operated on by-reference, the changes made to $item are made to the members of the original $set. Using $item by reference also prevents PHP from creating the array copy. To test this, first we'll show a quick script demonstrating the copy:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $set[] = ucfirst($item);
}
print_r($set);

This outputs:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
    [3] => Apple
    [4] => Banana
    [5] => Coconut
)

As it is shown in the example, PHP copied $set and used it to loop over, but when $set was used inside the loop, PHP added the variables to the original array, not the copied array. Basically, PHP is only using the copied array for the execution of the loop and the assignment of $item. Because of this, the loop above only executes 3 times, and each time it appends another value to the end of the original $set, leaving the original $set with 6 elements, but never entering an infinite loop.

However, what if we had used $item by reference, as I mentioned before? A single character added to the above test:

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $set[] = ucfirst($item);
}
print_r($set);

Results in an infinite loop. Note this actually is an infinite loop, you'll have to either kill the script yourself or wait for your OS to run out of memory. I added the following line to my script so PHP would run out of memory very quickly, I suggest you do the same if you're going to be running these infinite loop tests:

ini_set("memory_limit","1M");

So in this previous example with the infinite loop, we see the reason why PHP was written to create a copy of the array to loop over. When a copy is created and used only by the structure of the loop construct itself, the array stays static throughout the execution of the loop, so you'll never run into issues.




स्पष्टीकरण ( 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.