پوریا انوری
پوریا انوری
خواندن ۵ دقیقه·۵ سال پیش

آشنایی با PHP Generators

نمونه ای از استفاده PHP Generator
نمونه ای از استفاده PHP Generator



خب PHP Generator چی هست ؟‍

این امکان رو به ما میدن، بدون اینکه کل داده ها رو داخل حافظه نگه داریم به کمک یک Loop پیمایش کنیم. در واقع به کمک Generator ها میتونیم توابعی بنویسیم که چندین مقدار مختلف رو به عنوان خروجی برگردونن، برخلاف return که فقط یک مقدار رو برمیگردونه.بخوام توی یک جمله خلاصه کنم این میشه :

تابعی که خروجی iterable ارائه میده بدون اینکه کل داده ها رو یکجا برگردونه.


خب Generator وجود نداره!

بیاین اول اینجوری پیش بریم که انگار همچین امکانی وجود نداره.

ما میخوایم ابتدا آرایه ای از 0 تا بزرگترین عدد php یعنی PHP_INT_MAX داشته باشیم و بعد همه اعضای این آرایه رو چاپ کنیم. بریم ببینیم کدش چطوریه


اگر این کد رو اجرا کنیم چی میشه؟ بله با این اررور مواجه می شیم :

Allowed memory size of 134217728 bytes exhausted (tried to allocate 134217736 bytes)

مشخصه که حافظه ای که در اختیار PHP بوده کاملا پر شده و دلیلش هم اینه که تابع makeArray کل اعداد بین 0 تا PHP_INT_MAX رو داخل حافظه نگه میداره.

پس راه حل چیه؟ بله Generator ها

همونطور که گفتم، Generator ها این امکان رو میدن تا بتونیم داده رو پیمایش کنیم بدون اینکه کل اون رو داخل حافظه ذخیر کنیم . دقیقا چطوری میشه؟


کد چه تغییری کرده؟ توجهتون رو به این قسمت جلب میکنم :

بجای اینکه بیایم هر عدد رو داخل ارایه بریزیم اومدیم yield کردیمش. همونطور که بالاتر گفتم Generatorها به کمک yield این امکان رو میدن تا یک تابع چند خروجی داشته باشه. حالا اینجا چه اتفاقی افتاده؟
توی این کد وقتی وارد حلقه for میشیم، مقدار اولیه i$ که 0 هست yield میشه و به عنوان یکی از خروجی ها برگردونده و کد متوقف میشه، دیگه ادامه پیدا نمیکنه تا زمانی که دوباره اجرای Generator ادامه پیدا کنه.


چون داخل foreach به پیمایش Generator داریم ادامه میدیم پس مقادیر بعدی هم به ترتیب yield میشن.
خروجی کد ما این میشه :

result => 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 ...

راه حل ما جواب داد، حالا بریم ببینیم چه اتفاقی افتاده و yield چه کاری انجام داده.



معجزه yield

همون قطعه کد بالا رو با عدد کمتر، مثلا 5 پیش میبریم تا بشه خوب نشون داد چه اتفاقی میوفته

خب اگر ما تابع makeArray رو var_dump کنیم خروجیمون این میشه :

object(Generator)#507 (0) { }

میبینیم که یک object از نوع Generator هست و هیچ عددی داخلش نیست .

حالا اگر خروجی MakeArray رو به کمک foreach پیمایش کنیم چه اتفاقی میوفته؟

خروجی کد بالا اعداد 0 تا 4 رو چاپ کرده،حالا سوال پیش میاد که چرا موقعی که که var_dump کردیم هیچ عددی رو نشون نداده و فقط یک object بود؟ اینجا مشخص میشه که yield مقادیر رو به ترتیب و یکی یکی برمیگردونه و تا زمانی که به پیمایش object خروجی که یک Generator هست ادامه ندیم ، مقادیر بعدی رو برنمیگردونه و اینجوری دیگه کل داده داخل حافظه قرار داده نمیشه و در هر فراخوانی یکی یکی تولید و برگردونده میشن . Generatorها چند متد دارن که اینجا باهاشون آشنا میشیم.

متد Current

با کمک این متد به مقدار فعلی که yield شده دسترسی پیدا میکنیم، اینطوری :


خروج فعلی ما عدد 0 هست چرا که مقدار اولیه i$ داخل حلقه 0 هست . پس از اینکه مقدار 0 yield شد حلقه ادامه پیدا نمیکنه و اجرای Generator نگه داشته میشه.هرچقدر هم تابع current رو صدا بزنیم باز هم خروجی 0 هست.

خب اگر بخوابم مقدار بعدی رو چاپ کنیم چیکار کنیم؟ تابع بعدی جواب سوال ما هست

متد Next

به کمک این متد میتونیم اجرای Generator رو از جای قبلی که متوقف شده بود ادامه بدیم.

مقدار اولیه i$ عدد 0 بود با متد current مقدار فعلی یعنی 0 چاپ میشه و با متد next پیمایش Generator رو ادامه میدیم تا yield بعدی و زمانی که دوباره متد current رو صدا میزنیم عدد 1 چاپ میشه چون مقدار بعدی i$ هستش.

متد getReturn

اگر بخوایم پس از اتمام اجرا Generator مقدار return شده ی تابع رو برگردونیم، از این متد استفاده میکنیم.

میبینیم که حلقه foreach که وظیفه iterate کردن Generator رو برعهده داره فقط اعداد 1 و 2 رو که yield شدن چاپ کرده، پس اگر بخوایم به مقدار return شده دسترسی پیدا کنیم باید متد getReturn رو صدا بزنیم.

فقط دقت کنید اگر قبل از تموم شدن اجرای Generator، متد getReturn رو صدا بزنید با اررور مواجه می شید.

متد Send

به کمک این متد میتونیم مقداری رو به عنوان نتیجه yield به Generator بفرستیم و اجرای Generator رو ادامه بدیم.

چندتا مثال :

خروجی current خالی هست، چراکه تابع myGen تا جایی که yield هست اجرا شده و چون هیچ مقداری yield نشده ، مقدار فعلی وجود نداره تا current بخواد بهمون نشون بده. همونطور که گفتم در اینجا اجرای Generator متوقف میشه تا دوباره بخوایم اجراش رو به کمک راه های بالا ادامه بدیم.

حالا اگر ما به کمک متد send یک مقدار رو به داخل Generator بفرستیم چی میشه؟


اولین بار که به yield میرسه چون هیچ مقداری yield نشده متوقف میشه و منتظر ادامه اجرا میمونه ،با متد send اجرا رو ادامه میدیم و میبینیم که مقدار فرستاده شده ما، یعنی "hello" به عنوان مقدار فعلی yield برگردونه میشه و اجرای Generator ما یک گام ادامه پیدا میکنه و چون اخرین yield ما بود، اجراش پایان پیدا میکنه.

توی مثال بعدی Generator مقدار اول رو yield میکنه :


چون رشته "bye" در داخل تابع yield شده ، متد current مقدار اون رو به ما میده و اجرای Generator در همونجا puase میشه. وقتی با متد send رشته "hello" رو به داخل Generator میفرستیم مقدار فعلی Generator به "hello" تغییر پیدا میکنه و به کمک echo چاپ میشه. توی مرحله اول yield مقدار "bye" رو برگردونده و در مرحله دوم مثل یک متغیر عمل کرده که میتونیم مقداری رو بهش نسبت بدیم.

اگر داخل کد بالا اول متد current رو صدا نزنیم و مستقیم از متد send استفاده کنیم، "bye" برگردونه نمیشه و مستقیم مرحله که yield به عنوان یک متغییر استفاده شده اجرا میشه.


نتیجه گیری :

به کمک Generator ها میتونیم استفاده از حافظه رو بهینه کنیم ، در استفاده از داده های سنگین دیتابیس ها و یا خوندن یک فایل حجیم به ما کمک میکنن .حتی میتونیم یک تابع رو تا یک جای مشخص اجرا کنیم و بقیه اون رو در زمان دیگه ای اجرا کنیم . Generator ها توی Multitasking و Scheduling هم کارآیی دارن.


امیداورم به خوبی تونسته باشم مفهموم Generator ها رو براتون جا انداخته باشم .


phpgenerator
شاید از این پست‌ها خوشتان بیاید