Menu
flag Ar iconالعربيةdown icon

المولّدات في PHP: yield وyield from واستهلاك الذاكرة

المولّد دالة تستخدم yield لتعيد القيم واحدة واحدة، فتستطيع foreach المرور على مليون عنصر دون بناء مصفوفة. تعلّم yield، والمفاتيح، وتوفير الذاكرة، والتسلسلات اللانهائية، وyield from، وsend() وgetReturn().

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

المولّد دالة تحتوي yield. استدعاؤها لا ينفّذ الجسم؛ بل يعيد كائن Generator تستطيع foreach المرور عليه. كل yield $value تعطي الحلقة قيمة واحدة وتوقف الدالة مؤقتًا حتى تطلب الحلقة القيمة التالية، فتُنتج القيم واحدة واحدة بدل أن تُجمع في مصفوفة أولًا.

تتناوب المخرجات بين المولّد والحلقة: كل قيمة لا تُصنع إلا عندما تطلبها الحلقة.

المولّدات توفّر الذاكرة

تبني range(1, 1000000) مصفوفة من مليون عدد صحيح قبل أن تبدأ الحلقة. أما المولّد الذي يعطي الأعداد نفسها فلا يحمل أكثر من واحد منها أبدًا. شغّله وقارن الذاكرة التي تحتاجها كل نسخة:

تطبع الحلقتان المجموع نفسه، 500000500000، لكن نسخة المصفوفة تبلّغ عن extra memory 16.0 MB والمولّد عن extra memory 0.0 MB. وتكبر الفجوة مع البيانات. والفكرة نفسها هي ما يجعل المولّدات مفيدة للملفات الكبيرة ونتائج قواعد البيانات وصفحات الواجهات البرمجية: عالج عنصرًا، ثم تخلّص منه، وخذ التالي.

إعطاء المفاتيح والقيم

تضبط yield $key => $value المفتاح الذي تتلقاه foreach ($gen as $key => $value). ودون مفتاح، يرقّم المولّد قيمه من 0، مثل القائمة. ومن الاستخدامات الشائعة قراءة ملف سطرًا سطرًا مع أرقام الأسطر:

لا يكون في الذاكرة إلا سطر واحد في كل مرة، لذا تتعامل الدالة نفسها مع ملف سجل حجمه 2 GB. وتشرح fopen() وfgets() دوال الملفات المستخدمة هنا.

التسلسلات اللانهائية والسلاسل الكسولة

يمكن أن يدور المولّد إلى الأبد، لأنه لا يعمل إلا بقدر ما يطلب المستهلك. توقف بـ break، أو ضعه داخل مولّد آخر يأخذ أول بضع قيم. وربط مولّدات صغيرة يعطي سلسلة معالجة كل خطوة فيها كسولة:

لا تنتهي fibonacci() بنفسها أبدًا، ومع ذلك ينتهي البرنامج: تتوقف take() عن الطلب بعد العدد المطلوب، ولا يُحسب شيء بعده أبدًا.

yield from: التفويض إلى مولّد آخر

تمرر yield from كل قيمة من مصفوفة أو مكرِّر أو مولّد آخر. وهكذا تقسّم مولّدًا إلى مولّدات أصغر، أو تمشي على بنية متداخلة تعاوديًا:

تحتفظ yield from بالمفاتيح الداخلية، لذا تعطي المصفوفتان المفتاحين 0 و1. ثم تستبدل iterator_to_array() القيمتين a وb. مرّر false وسيطًا ثانيًا كلما كانت القيم وحدها هي المهمة.

send() وgetReturn()

يستطيع المولّد أيضًا تلقي القيم. تتوقف $x = yield; مؤقتًا وتنتظر؛ وتستأنف $gen->send($value) المولّد مع $value نتيجةً لتلك yield. ويستطيع المولّد أن ينفّذ return بقيمة نهائية، تقرؤها بـ getReturn() بعد انتهائه.

لا تحتاج معظم الشيفرات إلى send() أبدًا؛ فالقراءة الكسولة بـ foreach هي الاستخدام اليومي. وهي اللبنة التي تقوم عليها مكتبات الروتينات المشتركة.

لا يمكن المرور على المولّد إلا مرة واحدة

تعمل المولّدات إلى الأمام ولا يمكن إرجاعها. فبعد أن تستهلك foreach مولّدًا، يرمي المرور الثاني على الكائن نفسه خطأً:

الحل أن تستدعي دالة المولّد من جديد لكل مرور، foreach (colors() as $c)، أو أن تجمع القيم مرة واحدة بـ iterator_to_array(colors(), false) عندما تحتاج إليها مرتين فعلًا. وإذا وجدت نفسك تفعل ذلك كثيرًا، فالمصفوفة العادية كانت الخيار الصحيح لتلك البيانات.

الأسئلة الشائعة

ماذا تفعل yield في PHP؟

تعطي yield $value قيمة واحدة للشيفرة التي تمر على الدالة وتوقف الدالة مؤقتًا في ذلك الموضع. ويستأنفها تكرار الحلقة التالي بعد yield. وأي دالة تحتوي yield تعيد كائن Generator بدل أن تعمل عند استدعائها.

لماذا أستخدم مولّدًا بدل مصفوفة؟

تحمل المصفوفة كل عنصر في الذاكرة معًا؛ وينتج المولّد عنصرًا واحدًا في كل مرة. المرور على range(1, 1000000) يحجز المليون عدد كلها، بينما يُبقي المولّد الذي يعطيها الذاكرة ثابتة. وتتيح المولّدات أيضًا تسلسلات لانهائية وتوقف العمل مبكرًا عندما تنكسر الحلقة.

ما هي yield from في PHP؟

تمرر yield from $source كل قيمة من مولّد آخر أو مصفوفة أو Traversable، كأن المولّد الحالي أعطاها بنفسه. وتكون قيمتها أيضًا القيمة المعادة من المولّد الداخلي.

هل أستطيع المرور على مولّد مرتين؟

لا. يعمل المولّد إلى الأمام فقط. بعد انتهاء foreach، يرمي المرور مرة أخرى Exception: Cannot traverse an already closed generator. استدعِ دالة المولّد مرة أخرى لتحصل على مولّد جديد، أو خزّن القيم بـ iterator_to_array() إذا احتجت إليها مرتين.

كيف أحوّل مولّدًا إلى مصفوفة؟

تجمع iterator_to_array($gen) كل القيم، محتفظة بمفاتيح المولّد. وإذا تكررت المفاتيح، مثلًا بعد yield from، تستبدل القيم اللاحقة السابقة، لذا مرّر false وسيطًا ثانيًا لتحصل على قائمة عادية: iterator_to_array($gen, false).

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن