شرح كيف تعمل PHP 'foreach' بالفعل؟




foreach php شرح (5)

بعض النقاط لملاحظة عند العمل مع foreach() :

أ) foreach يعمل على نسخة محتملة من الصفيف الأصلي. يعني أن foreach () سيكون لديك تخزين بيانات مشترك حتى أو ما لم يتم إنشاء prospected copy manual .

ب) ما الذي يطلق نسخة محتملة ؟ يتم إنشاء نسخة مستنيرة استناداً إلى سياسة copy-on-write ، أي ، عندما يتم تغيير مصفوفة تمرير إلى foreach () ، يتم إنشاء نسخة من الصفيف الأصلي.

c) سيكون للمصفوفة الأصلية و foreach () it DISTINCT SENTINEL VARIABLES ، أي واحد للمصفوفة الأصلية والأخرى foreach. انظر رمز الاختبار أدناه. SPL و Iterators و Array Iterator .

Stack Overflow question كيفية التأكد من إعادة ضبط القيمة في حلقة "foreach" في PHP؟ يعالج الحالات (3 ، 4 ، 5) من سؤالك.

يوضح المثال التالي أن كل () وإعادة التعيين () لا تؤثر على متغيرات SENTINEL (for example, the current index variable) من مكرر foreach ().

$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

https://code.i-harness.com

اسمحوا لي بادئة هذا بالقول إنني أعرف ما هو 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 أيضًا:

كما يعتمد foreach على مؤشر الصفيف الداخلي تغييره داخل الحلقة قد يؤدي إلى سلوك غير متوقع.

حسنًا ، لنكتشف ما هو "السلوك غير المتوقع" (من الناحية الفنية ، أي سلوك غير متوقع لأنني لم أعد أعرف ما هو متوقع).

حالة الاختبار 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 */

... لا شيء غير متوقع هناك ، في الواقع يبدو أنه يدعم نظرية "نسخة المصدر".

السؤال

ما الذي يجري هنا؟ إن C-fu ليس جيدًا بما يكفي لي كي أستطيع استخلاص استنتاج سليم بمجرد النظر إلى شفرة المصدر PHP ، وسأكون ممتنا لو أن أحدا يستطيع ترجمته إلى الإنجليزية بالنسبة لي.

يبدو لي أن foreach يعمل مع نسخة من الصفيف ، ولكن يحدد مؤشر الصفيف للمصفوفة المصدر إلى نهاية الصفيف بعد الحلقة.

  • هل هذا صحيح وكل القصة؟
  • إذا لم يكن كذلك ، فما الذي تفعله حقًا؟
  • هل هناك أي موقف حيث أن استخدام الدوال التي ضبط مؤشر الصفيف ( each() ، reset() وآخرون) أثناء foreach يمكن أن يؤثر على نتائج الحلقة؟

في المثال 3 ، لا تقوم بتعديل المصفوفة. في جميع الأمثلة الأخرى تقوم بتعديل إما محتويات أو مؤشر الصفيف الداخلي. هذا أمر مهم عندما يتعلق الأمر صفائف PHP بسبب دلالات مشغل الاحالة.

يعمل عامل التعيين للصفائف في 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 الثلاثي: سريع أم لا؟


شرح (اقتباس من php.net ):

الحلقات النموذجية الأولى عبر الصفيف المعطى بواسطة array_expression. في كل تكرار ، يتم تعيين قيمة العنصر الحالي إلى قيمة $ ويتم تطوير مؤشر الصفيف الداخلي بواسطة واحد (حتى في التكرار التالي ، سوف تبحث في العنصر التالي).

لذلك ، في المثال الأول لديك عنصر واحد فقط في الصفيف ، وعندما يتم تحريك المؤشر ، لا يكون العنصر التالي موجودًا ، لذلك بعد إضافة عنصر جديد foreach ينتهي لأنه "قرر" بالفعل أنه بمثابة العنصر الأخير.

في المثال الثاني ، تبدأ بعنصرين ، ولا تكون حلقة foreach في العنصر الأخير ، لذا فهي تقوم بتقييم المصفوفة في التكرار التالي ، وبالتالي تحقق من وجود عنصر جديد في المصفوفة.

وأعتقد أن هذا كله نتيجة لكل جزء من التكرار في التفسير في الوثائق ، وهو ما يعني على الأرجح أن 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