مقدمة: لماذا foreach ليست مجرد "لكل"
إذا كنت تكتب بلغة PHP لمدة أسبوع على الأقل، فمن المؤكد أنك استخدمت foreach مئات المرات. إنها بلا شك الحلقة المفضلة لكل مطور: قصيرة، أنيقة، بدون عدادات زائدة. ولكن هل تساءلت يومًا عما يحدث بالفعل تحت الغطاء عندما تكتب foreach ($array as $value)؟
سؤال "How does PHP 'foreach' actually work?" حصد آلاف المشاهدات على Stack Overflow. وهذا ليس من قبيل الصدفة. سلوك foreach يخفي بعض المفاجآت، خاصة عندما يتعلق الأمر بالمراجع والحلقات المتداخلة والمصفوفات غير التقليدية.
في هذا الدليل التعليمي، لن نكرر فقط بناء الجملة. سننظر داخل محرك PHP، ونتعرف على المؤشر الداخلي للمصفوفة، ونرى كيف تعمل المكررات، ونتعلم كيفية تجنب المخاطر الخفية. هيا بنا!
1. تشريح foreach: ما يقوله التوثيق
لنبدأ بالأساسيات. foreach مصمم للتكرار على المصفوفات والكائنات. بناء الجملة بسيط:
<?php$fruits = ['apple', 'banana', 'cherry'];
foreach ($fruits as $fruit) { echo $fruit . "";}// الإخراج: apple banana cherry?>ولكن هناك شكل ثانٍ - مع المفتاح:
<?php$prices = ['apple' => 100, 'banana' => 50];
foreach ($prices as $name => $price) { echo "$name يكلف $price روبل";}?>يبدو كل شيء واضحًا. لكن دعنا نتعمق أكثر. PHP تعالج foreach بشكل مختلف عن for أو while. داخليًا، تستخدم المؤشر الداخلي للمصفوفة (internal array pointer) وآلية تكرار خاصة.
2. المؤشر الداخلي للمصفوفة: العميل السري
كل مصفوفة في PHP لديها "مؤشر" غير مرئي - المؤشر الداخلي. يشير إلى العنصر الحالي. الدوال current() و next() و prev() و end() و reset() تتحكم فيه.
عند تشغيل foreach، تقوم PHP بما يلي:
- إعادة تعيين المؤشر الداخلي إلى العنصر الأول (مثل
reset()). - في كل تكرار، تأخذ العنصر الذي يشير إليه المؤشر.
- تحريك المؤشر إلى العنصر التالي (مثل
next()). - التكرار حتى تصل إلى النهاية.
لنرى هذا عمليًا:
<?php$array = [10, 20, 30];
// محاكاة يدوية لـ foreachreset($array); // المؤشر على العنصر الأولwhile ($value = current($array)) { echo $value . " "; next($array); // تحريك المؤشر}// الإخراج: 10 20 30?>ولكن هناك فارق بسيط: إذا كانت المصفوفة فارغة أو تحتوي على false، فستفشل هذه الخدعة. أما foreach فتعمل بشكل صحيح دائمًا، لأنها تستخدم آلية داخلية أكثر موثوقية - المكررات.
3. المكررات و foreach: النهج الحديث
بدءًا من PHP 5، يمكن لـ foreach العمل ليس فقط مع المصفوفات، ولكن أيضًا مع أي كائنات تطبق واجهة Traversable (أو وريثتها Iterator).
عند تمرير كائن إلى foreach، تستدعي PHP دوال المكرر:
rewind()- إعادة التعيين إلى البدايةvalid()- التحقق من وجود عنصرcurrent()- الحصول على القيمةkey()- الحصول على المفتاحnext()- الانتقال إلى التالي
مثال على مكرر مخصص:
<?phpclass NumbersIterator implements Iterator { private $position = 0; private $numbers = [1, 2, 3, 4, 5];
public function rewind() { $this->position = 0; }
public function current() { return $this->numbers[$this->position]; }
public function key() { return $this->position;