الفرق بين المراجعتين ل"PHP/generators"

من موسوعة حسوب
< PHP
اذهب إلى التنقل اذهب إلى البحث
ط (استبدال النص - 'PHP/Generators/syntax' ب'PHP/generators')
ط (نقل عبد اللطيف ايمش صفحة PHP/Generators إلى PHP/generators: إعادة هيكلة التوثيق)
 
(مراجعتان متوسطتان بواسطة نفس المستخدم غير معروضتين)
سطر 3: سطر 3:
 
تتيح المولِّدات كتابة شيفرة تستخدم <code>[[PHP/foreach|foreach]]</code> لمرور على مجموعة من البيانات دون الحاجة إلى بناء مصفوفة في الذاكرة مما قد يؤدي إلى تجاوز حدود الذاكرة، أو يحتاج إلى وقت معالجة طويل لإنشائه. بدلًا من ذلك، يمكن كتابة دالة مولِّدة وهي دالة مشابهة للدوال الاعتيادية باستثناء أنّها لا تعيد النتيجة مرة واحدة بل تعيد العدد المطلوب من النتائج لغرض تقديم القيم التي يتم المرور عليها.
 
تتيح المولِّدات كتابة شيفرة تستخدم <code>[[PHP/foreach|foreach]]</code> لمرور على مجموعة من البيانات دون الحاجة إلى بناء مصفوفة في الذاكرة مما قد يؤدي إلى تجاوز حدود الذاكرة، أو يحتاج إلى وقت معالجة طويل لإنشائه. بدلًا من ذلك، يمكن كتابة دالة مولِّدة وهي دالة مشابهة للدوال الاعتيادية باستثناء أنّها لا تعيد النتيجة مرة واحدة بل تعيد العدد المطلوب من النتائج لغرض تقديم القيم التي يتم المرور عليها.
  
ومن أبسط الأمثلة على المولِّدات هو إعادة استخدام الدالة <code>[[PHP/Function/range|range()‎]]</code> كمولِّد. تنشئ الدالة <code>[[PHP/Function/range|range()‎]]</code> الاعتيادية مصفوفة تتضمن جميع القيم وتعيدها وقد ينتج عن ذلك مصفوفة كبيرة جدًّا، فعلى سبيل المثال استدعاء الدالة <code>range(0, 1000000)</code>‎ يستهلك ما يقارب 100 MB من الذاكرة.
+
ومن أبسط الأمثلة على المولِّدات هو إعادة استخدام الدالة <code>[[PHP/range|range()‎]]</code> كمولِّد. تنشئ الدالة <code>[[PHP/range|range()‎]]</code> الاعتيادية مصفوفة تتضمن جميع القيم وتعيدها وقد ينتج عن ذلك مصفوفة كبيرة جدًّا، فعلى سبيل المثال استدعاء الدالة <code>range(0, 1000000)</code>‎ يستهلك ما يقارب 100 MB من الذاكرة.
  
يمكن استبدال الدالة الاعتيادية بالدالة المولدة <code>[[PHP/Function/xrange|xrange()‎‎]]</code>، والتي تحتاج إلى كمية مقبولة من الذاكرة لإنشاء كائن Iterator وتتبع حالة المولّد الحالية داخليًا، وكلّ ذلك يستهل أقل من 1 كيلوبايت.
+
يمكن استبدال الدالة الاعتيادية بالدالة المولدة <code>[[PHP/xrange|xrange()‎‎]]</code>، والتي تحتاج إلى كمية مقبولة من الذاكرة لإنشاء كائن Iterator وتتبع حالة المولّد الحالية داخليًا، وكلّ ذلك يستهل أقل من 1 كيلوبايت.
  
 
المثال 1: استخدام الدالة <code>range()‎</code> كدالة مولّدة
 
المثال 1: استخدام الدالة <code>range()‎</code> كدالة مولّدة
  
لاحظ أنّ كلتا الدالتين <code>[[PHP/Function/range|range()‎]]</code> و <code>[[PHP/Function/xrange|xrange()‎]]</code> تعطيان المخرجات ذاتها.
+
لاحظ أنّ كلتا الدالتين <code>[[PHP/range|range()‎]]</code> و <code>[[PHP/xrange|xrange()‎]]</code> تعطيان المخرجات ذاتها.
 
<syntaxhighlight lang="php">
 
<syntaxhighlight lang="php">
  
سطر 60: سطر 60:
 
الدالة المولدة مشابهة تمامًا للدالة الاعتيادية، باستثناء أنّ الدالة المولدة تنتج (<code>yield</code>) قيمًا حسب الحاجة بدلًا من إعادة قيمة واحدة.
 
الدالة المولدة مشابهة تمامًا للدالة الاعتيادية، باستثناء أنّ الدالة المولدة تنتج (<code>yield</code>) قيمًا حسب الحاجة بدلًا من إعادة قيمة واحدة.
  
تعيد الدالة المولّدة عند استدعائها كائنًا يمكن المرور على عناصره، وعند القيام بذلك (باستخدام حلقة <code>[[PHP/foreach|foreach]]</code> مثلًا) فإنّ PHP ستستدعي الدالة المولدة في كل مرة تحتاج فيها إلى قيمة، ثم تحفظ حالة الدالة المولدة عندما تنتج قيمة وهكذا يمكن الرجوع إلى تلك الحالة عند الحاجة إلى القيمة اللاحقة.
+
تعيد الدالة المولّدة عند استدعائها كائنًا يمكن المرور على عناصره، وعند القيام بذلك (باستخدام حلقة <code>[[PHP/foreach|foreach]]</code> مثلًا) فإنّ PHP ستستدعي الدالة المولدة في كل مرة تحتاج فيها إلى قيمة، ثم تحفظ حالة الدالة المولدة عندما تنتج قيمة وهكذا يمكن الرجوع إلى تلك الحالة عند الحاجة إلى القيمة اللاحقة.
  
== [[PHP/generators|مقارنة المولّدات بكائنات Iterator]] ==
+
وعند انتهاء القيمة المنتجة من الدالة المولدة فإنّها تتوقف عن العمل وتستمر الشيفرة في عملها كما لو أنّ مصفوفة قد وصلت إلى نهاية القيم المتوفرة فيها.
إن الميزة الأساسية التي تتمتّع بها المولّدات هي بساطتها، إذ أنّ الشيفرة المطلوبة أقل بكثير مقارنة باستخدام صنف <code>Iterator</code>، وتكون ذات مقروئية أعلى.
+
 
 +
ملاحظة: لا يمكن للمولدات أن تُعيد قيمة في PHP 5، والقيام بذلك يؤدي إلى إطلاق خطأ في التصريف. كانت عبارة return الفارغة تعدّ صيغة صحيحة في المولدات وبإمكانها إيقاف عمل الدالة المولّدة، ولكن في الإصدار 7.0 من PHP أصبح بإمكان المولِّدات إعادة القيم، ويمكن الحصول عليها باستخدام <code>Generator::getReturn()‎‎</code>.
 +
===الكلمة المفتاحية <code>yield</code>===
 +
الكلمة المفتاحية <code>yield</code> هي أساس عمل الدوال المولِّدة، وفي صيغتها البسيطة تشبه عبارة <code>yield</code> عبارة <code>[[PHP/return|return]]</code> كثيرًا، باستثناء أنّ عبارة <code>[[PHP/return|return]]</code> توقف تنفيذ الشيفرة في الدالة وتعيد قيمة معينة، أما عبارة <code>yield</code> فتقدّم قيمة إلى الشيفرة التي تدور حول المولّد وتوقف تنفيذ شيفرة المولِّد بصورة مؤقتة.
 +
 
 +
المثال 2: مثال بسيط عن القيم المنتجة
 +
 
 +
لاحظ كيف أنّ قيمة المتغيّر <code>‎‎$i</code> تبقى محفوظة بعد تنفيذ عبارة <code>yield</code><syntaxhighlight lang="php">
 +
 
 +
<?php
 +
function gen_one_to_three() {
 +
    for ($i = 1; $i <= 3; $i++) {
 +
        yield $i;
 +
    }
 +
}
 +
 
 +
$generator = gen_one_to_three();
 +
foreach ($generator as $value) {
 +
    echo "$value\n";
 +
}
 +
?>
 +
</syntaxhighlight>تعطي الشيفرة السابقة المخرجات التالية:<syntaxhighlight lang="php">
 +
 
 +
1
 +
2
 +
3
 +
</syntaxhighlight>ملاحظة: تجمع اللغة مفاتيح الأعداد الصحيحة المتتالية على هيئة مزدوجات مع القيم المنتجة، كما هو الحال مع المصفوفات غير الترابطية.
 +
 
 +
تنبيه: في حال استخدام الكلمة المفتاحية yield ضمن تعبير معين (على سبيل المثال، على يمين عامل الإسناد) يجب إحاطة العبارة بالأقواس في الإصدار الخامس من PHP. فعلى سبيل المثال هذه الصيغة صحيحة:<syntaxhighlight lang="php">
 +
 
 +
$data = (yield $value);
 +
</syntaxhighlight>ولكن هذه الصيغة غير صحيحة، وستؤدي إلى حدوث خطأ من نوع parse في الإصدار الخامس من اللغة:<syntaxhighlight lang="php">
 +
 
 +
$data = yield $value;
 +
</syntaxhighlight>لا حاجة لاستخدام الأقواس في الإصدار السابع من اللغة.
 +
 
 +
يمكن استخدام هذه الصيغة إلى جانب التابع Generator::send()‎.
 +
====إنتاج القيم مع المفاتيح====
 +
تدعم PHP المصفوفات الترابطية، ولا تختلف المولّدات في ذلك عن المصفوفات، فبالإضافة إلى إنتاج القيم البسيطة كما وضحنا أعلاه، يمكن أيضاً إنتاج مفتاح في الوقت نفسه.
 +
 
 +
صيغة إنتاج زوج من المفتاح والقيمة مشابهة جدًّا للصيغة المستخدمة في تعريف المصفوفة الترابطية، كما هو موضح أدناه.
 +
 
 +
المثال 3: إنتاج زوج مفتاح/قيمة
 +
 
 +
المدخلات في المثال التالي هي حقول مفصولة بالفاصلة المنقوطة، والحقل الأول هو معرّف لاستخدام المفتاح.<syntaxhighlight lang="php">
 +
 
 +
<?php
 +
 
 +
 
 +
$input = <<<'EOF'
 +
1;PHP;Likes dollar signs
 +
2;Python;Likes whitespace
 +
3;Ruby;Likes blocks
 +
EOF;
 +
 
 +
function input_parser($input) {
 +
    foreach (explode("\n", $input) as $line) {
 +
        $fields = explode(';', $line);
 +
        $id = array_shift($fields);
 +
 
 +
        yield $id => $fields;
 +
    }
 +
}
 +
 
 +
foreach (input_parser($input) as $id => $fields) {
 +
    echo "$id:\n";
 +
    echo "    $fields[0]\n";
 +
    echo "    $fields[1]\n";
 +
}
 +
?>
 +
</syntaxhighlight>تعطي الشيفرة السابقة المخرجات التالية:<syntaxhighlight lang="php">
 +
 
 +
1:
 +
    PHP
 +
    Likes dollar signs
 +
2:
 +
    Python
 +
    Likes whitespace
 +
3:
 +
    Ruby
 +
    Likes blocks
 +
</syntaxhighlight>تنبيه: كما هو الحال مع إنتاج القيم البسيطة، يجب أن تحاط عبارة إنتاج الزوج مفتاح/قيمة بالأقواس في حال استخدامها ضمن سياق معين.<syntaxhighlight lang="php">
 +
$data = (yield $key => $value);
 +
</syntaxhighlight>
 +
====إنتاج قيم null====
 +
يمكن استدعاء yield دون تقديم أي معاملات لإنتاج القيمة NULL مع مفتاح تلقائي.
 +
 
 +
المثال 4: إنتاج القيمة NULL<syntaxhighlight lang="php">
 +
 
 +
<?php
 +
function gen_three_nulls() {
 +
    foreach (range(1, 3) as $i) {
 +
        yield;
 +
    }
 +
}
 +
 
 +
var_dump(iterator_to_array(gen_three_nulls()));
 +
?>
 +
</syntaxhighlight>تعطي الشيفرة السابقة المخرجات التالية:<syntaxhighlight lang="php">
 +
 
 +
array(3) {
 +
  [0]=>
 +
  NULL
 +
  [1]=>
 +
  NULL
 +
  [2]=>
 +
  NULL
 +
}
 +
</syntaxhighlight>
 +
====إنتاج القيم بالمرجعية====
 +
يمكن للدوال المولِّدة أن تنتج قيمًا بالمرجعية، ويمكن القيام بذلك بنفس الطريقة المتّبعة [[PHP/returning values|لإعادة المراجع من الدوال]] وذلك بإضافة العلامة & إلى بداية اسم الدالة.
 +
 
 +
المثال 5: إنتاج القيم بالمرجعية
 +
 
 +
لاحظ أنّ بالإمكان تغيير قيمة المتغيّر <code>‎‎$number</code> من داخل الحلقة، ولما كانت الدالة المولّدة تنتج قيمًا بالمرجعية، فإنّ قيمة المتغير ‎<code>‎$value</code> ضمن الدالة <code>gen_reference()‎</code> ستتغيّر.<syntaxhighlight lang="php">
 +
 
 +
<?php
 +
function &gen_reference() {
 +
    $value = 3;
 +
 
 +
    while ($value > 0) {
 +
        yield $value;
 +
    }
 +
}
 +
 
 +
foreach (gen_reference() as &$number) {
 +
    echo (--$number).'... ';
 +
}
 +
?>
 +
</syntaxhighlight>يعطي المثال السابق المخرجات التالية:<syntaxhighlight lang="php">
 +
 
 +
2... 1... 0...
 +
</syntaxhighlight>
 +
====تفويض المولِّدات بواسطة <code>yield from</code>====
 +
يتيح تفويض المولِّد (generator delegation) في الإصدار السابع من اللغة إنتاج قيم من مولِّد آخر أو كائن يمكن المرور على عناصره أو مصفوفة، وذلك باستخدام الكلمة المفتاحية <code>yield from</code>، حيث سينتج المولّد الخارجي جميع القيم من المولّد أو الكائن أو المصفوفة الداخلية، إلى أن يصل إلى نهاية القيم ليستمر بعدها تنفيذ الشيفرة في المولّد الخارجي.
 +
 
 +
في حال استخدام مولّد مع الكلمة المفتاحية yield from فإنّ هذه العبارة ستعيد أيضًا جميع القيم المعادة من قبل المولِّد الداخلي.
 +
===التخزين في مصفوفة (مثال: باستخدام <code>[[PHP/iterator to array|iterator_to_array()‎]]</code>)===
 +
لا تعيد الكلمة المفتاحية <code>yield from</code> تعيين المفاتيح، بل تحفظ المفاتيح المعادة من المصفوفات أو الكائنات التي يمكن المرور على عناصرها؛ لهذا يمكن أن تمتلك بعض القيم مفاتيح مشتركة مع عبارة <code>yield</code> أو <code>yield from</code> أخرى، والتي ستحلّ محل القيم السابقة في حال تخزين القيم في مصفوفة.
 +
 
 +
من الحالات الشائعة أن تعيد الدالة <code>[[PHP/iterator to array|iterator_to_array()]]</code>‎ مصفوفة ذات مفاتيح بصورة افتراضية، مما يؤدّي إلى الحصول على نتائج غير متوقعة. تمتلك الدالة <code>[[PHP/iterator to array|iterator_to_array()‎]]</code> معاملًا ثانيًا هو <code>use_keys</code> والذي يمكن إعطاؤه القيمة <code>FALSE</code> لجمع كلّ القيم مع تجاهل المفاتيح المعادة بواسطة الصنف <code>Generator</code>.
 +
 
 +
المثال 6: استخدام <code>yield from</code> مع الدالة <code>iterator_to_array()‎</code><syntaxhighlight lang="php">
 +
 
 +
<?php
 +
function from() {
 +
    yield 1; // key 0
 +
    yield 2; // key 1
 +
    yield 3; // key 2
 +
}
 +
function gen() {
 +
    yield 0; // key 0
 +
    yield from from(); // keys 0-2
 +
    yield 4; // key 1
 +
}
 +
// pass false as second parameter to get an array [0, 1, 2, 3, 4]
 +
var_dump(iterator_to_array(gen()));
 +
?>
 +
</syntaxhighlight>يعطي المثال السابق المخرجات التالية:<syntaxhighlight lang="php">
 +
 
 +
array(3) {
 +
  [0]=>
 +
  int(1)
 +
  [1]=>
 +
  int(4)
 +
  [2]=>
 +
  int(3)
 +
}
 +
</syntaxhighlight>المثال 7: مثال بسيط عن استخدام الكلمة المفتاحية <code>yield from</code><syntaxhighlight lang="php">
 +
 
 +
<?php
 +
function count_to_ten() {
 +
    yield 1;
 +
    yield 2;
 +
    yield from [3, 4];
 +
    yield from new ArrayIterator([5, 6]);
 +
    yield from seven_eight();
 +
    yield 9;
 +
    yield 10;
 +
}
 +
 
 +
function seven_eight() {
 +
    yield 7;
 +
    yield from eight();
 +
}
 +
 
 +
function eight() {
 +
    yield 8;
 +
}
 +
 
 +
foreach (count_to_ten() as $num) {
 +
    echo "$num ";
 +
}
 +
?>
 +
</syntaxhighlight>يعطي المثال السابق النتيجة التالية:<syntaxhighlight lang="php">
 +
 
 +
1 2 3 4 5 6 7 8 9 10
 +
</syntaxhighlight>المثال 8: الكلمة المفتاحية <code>yield from</code> والقيم المعادة<syntaxhighlight lang="php">
 +
 
 +
<?php
 +
function count_to_ten() {
 +
    yield 1;
 +
    yield 2;
 +
    yield from [3, 4];
 +
    yield from new ArrayIterator([5, 6]);
 +
    yield from seven_eight();
 +
    return yield from nine_ten();
 +
}
 +
 
 +
function seven_eight() {
 +
    yield 7;
 +
    yield from eight();
 +
}
 +
 
 +
function eight() {
 +
    yield 8;
 +
}
 +
 
 +
function nine_ten() {
 +
    yield 9;
 +
    return 10;
 +
}
 +
 
 +
$gen = count_to_ten();
 +
foreach ($gen as $num) {
 +
    echo "$num ";
 +
}
 +
echo $gen->getReturn();
 +
?>
 +
</syntaxhighlight>يعطي المثال السابق المخرجات التالية:<syntaxhighlight lang="php">
 +
 
 +
1 2 3 4 5 6 7 8 9 10
 +
</syntaxhighlight>
 +
 
 +
== مقارنة المولّدات بكائنات Iterator ==
 +
إن الميزة الأساسية التي تتمتّع بها المولّدات هي بساطتها، إذ أنّ الشيفرة المطلوبة أقل بكثير مقارنة باستخدام صنف <code>Iterator</code>، وتكون ذات مقروئية أعلى، فعلى سبيل المثال الدالة والصنف في المثال التالي متكافئان:<syntaxhighlight lang="php">
 +
 
 +
 
 +
<?php
 +
function getLinesFromFile($fileName) {
 +
    if (!$fileHandle = fopen($fileName, 'r')) {
 +
        return;
 +
    }
 +
 +
    while (false !== $line = fgets($fileHandle)) {
 +
        yield $line;
 +
    }
 +
 +
    fclose($fileHandle);
 +
}
 +
 
 +
// مقابل...
 +
 
 +
class LineIterator implements Iterator {
 +
    protected $fileHandle;
 +
 +
    protected $line;
 +
    protected $i;
 +
 +
    public function __construct($fileName) {
 +
        if (!$this->fileHandle = fopen($fileName, 'r')) {
 +
            throw new RuntimeException('Couldn\'t open file "' . $fileName . '"');
 +
        }
 +
    }
 +
 +
    public function rewind() {
 +
        fseek($this->fileHandle, 0);
 +
        $this->line = fgets($this->fileHandle);
 +
        $this->i = 0;
 +
    }
 +
 +
    public function valid() {
 +
        return false !== $this->line;
 +
    }
 +
 +
    public function current() {
 +
        return $this->line;
 +
    }
 +
 +
    public function key() {
 +
        return $this->i;
 +
    }
 +
 +
    public function next() {
 +
        if (false !== $this->line) {
 +
            $this->line = fgets($this->fileHandle);
 +
            $this->i++;
 +
        }
 +
    }
 +
 +
    public function __destruct() {
 +
        fclose($this->fileHandle);
 +
    }
 +
}
 +
?>
 +
</syntaxhighlight><span> </span>ولكن هناك ثمن مقابل هذه المرونة، فالمولّدات هي مكرّرات أمامية التوجيه فقط (forward-only iterators) ولا يمكن العودة إلى بدايتها بعد أن تبدأ العمل. هذا يعني أيضًا عدم إمكانية المرور على المولّد نفسه أكثر من مرة، ويجب إعادة بناء المولّد باستدعاء الدالة المولّدة مرة أخرى.
 
== مصادر ==
 
== مصادر ==
 
* [http://php.net/manual/en/language.generators.php صفحة Generators في توثيق PHP الرسمي.]
 
* [http://php.net/manual/en/language.generators.php صفحة Generators في توثيق PHP الرسمي.]

المراجعة الحالية بتاريخ 04:52، 4 أبريل 2018

تقدّم المولِّدات (Generators) طريقة سهلة لاستخدام المكرِّرات (Iterators) البسيطة دون الدخول في تعقيدات استخدام صنف يطبّق الواجهة Iterator.

تتيح المولِّدات كتابة شيفرة تستخدم foreach لمرور على مجموعة من البيانات دون الحاجة إلى بناء مصفوفة في الذاكرة مما قد يؤدي إلى تجاوز حدود الذاكرة، أو يحتاج إلى وقت معالجة طويل لإنشائه. بدلًا من ذلك، يمكن كتابة دالة مولِّدة وهي دالة مشابهة للدوال الاعتيادية باستثناء أنّها لا تعيد النتيجة مرة واحدة بل تعيد العدد المطلوب من النتائج لغرض تقديم القيم التي يتم المرور عليها.

ومن أبسط الأمثلة على المولِّدات هو إعادة استخدام الدالة range()‎ كمولِّد. تنشئ الدالة range()‎ الاعتيادية مصفوفة تتضمن جميع القيم وتعيدها وقد ينتج عن ذلك مصفوفة كبيرة جدًّا، فعلى سبيل المثال استدعاء الدالة range(0, 1000000)‎ يستهلك ما يقارب 100 MB من الذاكرة.

يمكن استبدال الدالة الاعتيادية بالدالة المولدة xrange()‎‎، والتي تحتاج إلى كمية مقبولة من الذاكرة لإنشاء كائن Iterator وتتبع حالة المولّد الحالية داخليًا، وكلّ ذلك يستهل أقل من 1 كيلوبايت.

المثال 1: استخدام الدالة range()‎ كدالة مولّدة

لاحظ أنّ كلتا الدالتين range()‎ و xrange()‎ تعطيان المخرجات ذاتها.

<?php
function xrange($start, $limit, $step = 1) {
    if ($start < $limit) {
        if ($step <= 0) {
            throw new LogicException('Step must be +ve');
        }

        for ($i = $start; $i <= $limit; $i += $step) {
            yield $i;
        }
    } else {
        if ($step >= 0) {
            throw new LogicException('Step must be -ve');
        }

        for ($i = $start; $i >= $limit; $i += $step) {
            yield $i;
        }
    }
}

echo 'Single digit odd numbers from range():  ';
foreach (range(1, 9, 2) as $number) {
    echo "$number ";
}
echo "\n";

echo 'Single digit odd numbers from xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
    echo "$number ";
}
?>

يعطي المثال السابق المخرجات التالية:

Single digit odd numbers from range():  1 3 5 7 9 
Single digit odd numbers from xrange(): 1 3 5 7 9

كائنات Generator

عند استدعاء الدالة المولِّدة للمرة الأولى تعيد الدالة كائنًا من الصنف الداخلي Generator. يطبّق هذا الكائن واجهة Iterator بنفس الطريقة التي يتبعها كائن iterator الموجّه للإمام فقط، ويقدّم التوابع التي يمكن استدعاءها لمعالجة حالة المولِّد، ومنها إرسال القيم إلى المولِّد واستقبالها منه.

صيغة المولدات

الدالة المولدة مشابهة تمامًا للدالة الاعتيادية، باستثناء أنّ الدالة المولدة تنتج (yield) قيمًا حسب الحاجة بدلًا من إعادة قيمة واحدة.

تعيد الدالة المولّدة عند استدعائها كائنًا يمكن المرور على عناصره، وعند القيام بذلك (باستخدام حلقة foreach مثلًا) فإنّ PHP ستستدعي الدالة المولدة في كل مرة تحتاج فيها إلى قيمة، ثم تحفظ حالة الدالة المولدة عندما تنتج قيمة وهكذا يمكن الرجوع إلى تلك الحالة عند الحاجة إلى القيمة اللاحقة.

وعند انتهاء القيمة المنتجة من الدالة المولدة فإنّها تتوقف عن العمل وتستمر الشيفرة في عملها كما لو أنّ مصفوفة قد وصلت إلى نهاية القيم المتوفرة فيها.

ملاحظة: لا يمكن للمولدات أن تُعيد قيمة في PHP 5، والقيام بذلك يؤدي إلى إطلاق خطأ في التصريف. كانت عبارة return الفارغة تعدّ صيغة صحيحة في المولدات وبإمكانها إيقاف عمل الدالة المولّدة، ولكن في الإصدار 7.0 من PHP أصبح بإمكان المولِّدات إعادة القيم، ويمكن الحصول عليها باستخدام Generator::getReturn()‎‎.

الكلمة المفتاحية yield

الكلمة المفتاحية yield هي أساس عمل الدوال المولِّدة، وفي صيغتها البسيطة تشبه عبارة yield عبارة return كثيرًا، باستثناء أنّ عبارة return توقف تنفيذ الشيفرة في الدالة وتعيد قيمة معينة، أما عبارة yield فتقدّم قيمة إلى الشيفرة التي تدور حول المولّد وتوقف تنفيذ شيفرة المولِّد بصورة مؤقتة.

المثال 2: مثال بسيط عن القيم المنتجة

لاحظ كيف أنّ قيمة المتغيّر ‎‎$i تبقى محفوظة بعد تنفيذ عبارة yield

<?php
function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        yield $i;
    }
}

$generator = gen_one_to_three();
foreach ($generator as $value) {
    echo "$value\n";
}
?>

تعطي الشيفرة السابقة المخرجات التالية:

1
2
3

ملاحظة: تجمع اللغة مفاتيح الأعداد الصحيحة المتتالية على هيئة مزدوجات مع القيم المنتجة، كما هو الحال مع المصفوفات غير الترابطية. تنبيه: في حال استخدام الكلمة المفتاحية yield ضمن تعبير معين (على سبيل المثال، على يمين عامل الإسناد) يجب إحاطة العبارة بالأقواس في الإصدار الخامس من PHP. فعلى سبيل المثال هذه الصيغة صحيحة:

$data = (yield $value);

ولكن هذه الصيغة غير صحيحة، وستؤدي إلى حدوث خطأ من نوع parse في الإصدار الخامس من اللغة:

$data = yield $value;

لا حاجة لاستخدام الأقواس في الإصدار السابع من اللغة.

يمكن استخدام هذه الصيغة إلى جانب التابع Generator::send()‎.

إنتاج القيم مع المفاتيح

تدعم PHP المصفوفات الترابطية، ولا تختلف المولّدات في ذلك عن المصفوفات، فبالإضافة إلى إنتاج القيم البسيطة كما وضحنا أعلاه، يمكن أيضاً إنتاج مفتاح في الوقت نفسه.

صيغة إنتاج زوج من المفتاح والقيمة مشابهة جدًّا للصيغة المستخدمة في تعريف المصفوفة الترابطية، كما هو موضح أدناه.

المثال 3: إنتاج زوج مفتاح/قيمة

المدخلات في المثال التالي هي حقول مفصولة بالفاصلة المنقوطة، والحقل الأول هو معرّف لاستخدام المفتاح.

<?php


$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOF;

function input_parser($input) {
    foreach (explode("\n", $input) as $line) {
        $fields = explode(';', $line);
        $id = array_shift($fields);

        yield $id => $fields;
    }
}

foreach (input_parser($input) as $id => $fields) {
    echo "$id:\n";
    echo "    $fields[0]\n";
    echo "    $fields[1]\n";
}
?>

تعطي الشيفرة السابقة المخرجات التالية:

1:
    PHP
    Likes dollar signs
2:
    Python
    Likes whitespace
3:
    Ruby
    Likes blocks

تنبيه: كما هو الحال مع إنتاج القيم البسيطة، يجب أن تحاط عبارة إنتاج الزوج مفتاح/قيمة بالأقواس في حال استخدامها ضمن سياق معين.

$data = (yield $key => $value);

إنتاج قيم null

يمكن استدعاء yield دون تقديم أي معاملات لإنتاج القيمة NULL مع مفتاح تلقائي.

المثال 4: إنتاج القيمة NULL

<?php
function gen_three_nulls() {
    foreach (range(1, 3) as $i) {
        yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));
?>

تعطي الشيفرة السابقة المخرجات التالية:

array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

إنتاج القيم بالمرجعية

يمكن للدوال المولِّدة أن تنتج قيمًا بالمرجعية، ويمكن القيام بذلك بنفس الطريقة المتّبعة لإعادة المراجع من الدوال وذلك بإضافة العلامة & إلى بداية اسم الدالة.

المثال 5: إنتاج القيم بالمرجعية

لاحظ أنّ بالإمكان تغيير قيمة المتغيّر ‎‎$number من داخل الحلقة، ولما كانت الدالة المولّدة تنتج قيمًا بالمرجعية، فإنّ قيمة المتغير ‎‎$value ضمن الدالة gen_reference()‎ ستتغيّر.

<?php
function &gen_reference() {
    $value = 3;

    while ($value > 0) {
        yield $value;
    }
}

foreach (gen_reference() as &$number) {
    echo (--$number).'... ';
}
?>

يعطي المثال السابق المخرجات التالية:

2... 1... 0...

تفويض المولِّدات بواسطة yield from

يتيح تفويض المولِّد (generator delegation) في الإصدار السابع من اللغة إنتاج قيم من مولِّد آخر أو كائن يمكن المرور على عناصره أو مصفوفة، وذلك باستخدام الكلمة المفتاحية yield from، حيث سينتج المولّد الخارجي جميع القيم من المولّد أو الكائن أو المصفوفة الداخلية، إلى أن يصل إلى نهاية القيم ليستمر بعدها تنفيذ الشيفرة في المولّد الخارجي.

في حال استخدام مولّد مع الكلمة المفتاحية yield from فإنّ هذه العبارة ستعيد أيضًا جميع القيم المعادة من قبل المولِّد الداخلي.

التخزين في مصفوفة (مثال: باستخدام iterator_to_array()‎)

لا تعيد الكلمة المفتاحية yield from تعيين المفاتيح، بل تحفظ المفاتيح المعادة من المصفوفات أو الكائنات التي يمكن المرور على عناصرها؛ لهذا يمكن أن تمتلك بعض القيم مفاتيح مشتركة مع عبارة yield أو yield from أخرى، والتي ستحلّ محل القيم السابقة في حال تخزين القيم في مصفوفة.

من الحالات الشائعة أن تعيد الدالة iterator_to_array()‎ مصفوفة ذات مفاتيح بصورة افتراضية، مما يؤدّي إلى الحصول على نتائج غير متوقعة. تمتلك الدالة iterator_to_array()‎ معاملًا ثانيًا هو use_keys والذي يمكن إعطاؤه القيمة FALSE لجمع كلّ القيم مع تجاهل المفاتيح المعادة بواسطة الصنف Generator.

المثال 6: استخدام yield from مع الدالة iterator_to_array()‎

<?php
function from() {
    yield 1; // key 0
    yield 2; // key 1
    yield 3; // key 2
}
function gen() {
    yield 0; // key 0
    yield from from(); // keys 0-2
    yield 4; // key 1
}
// pass false as second parameter to get an array [0, 1, 2, 3, 4]
var_dump(iterator_to_array(gen()));
?>

يعطي المثال السابق المخرجات التالية:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(4)
  [2]=>
  int(3)
}

المثال 7: مثال بسيط عن استخدام الكلمة المفتاحية yield from

<?php
function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    yield 9;
    yield 10;
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

foreach (count_to_ten() as $num) {
    echo "$num ";
}
?>

يعطي المثال السابق النتيجة التالية:

1 2 3 4 5 6 7 8 9 10

المثال 8: الكلمة المفتاحية yield from والقيم المعادة

<?php
function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    return yield from nine_ten();
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

function nine_ten() {
    yield 9;
    return 10;
}

$gen = count_to_ten();
foreach ($gen as $num) {
    echo "$num ";
}
echo $gen->getReturn();
?>

يعطي المثال السابق المخرجات التالية:

1 2 3 4 5 6 7 8 9 10

مقارنة المولّدات بكائنات Iterator

إن الميزة الأساسية التي تتمتّع بها المولّدات هي بساطتها، إذ أنّ الشيفرة المطلوبة أقل بكثير مقارنة باستخدام صنف Iterator، وتكون ذات مقروئية أعلى، فعلى سبيل المثال الدالة والصنف في المثال التالي متكافئان:

<?php
function getLinesFromFile($fileName) {
    if (!$fileHandle = fopen($fileName, 'r')) {
        return;
    }
 
    while (false !== $line = fgets($fileHandle)) {
        yield $line;
    }
 
    fclose($fileHandle);
}

// مقابل...

class LineIterator implements Iterator {
    protected $fileHandle;
 
    protected $line;
    protected $i;
 
    public function __construct($fileName) {
        if (!$this->fileHandle = fopen($fileName, 'r')) {
            throw new RuntimeException('Couldn\'t open file "' . $fileName . '"');
        }
    }
 
    public function rewind() {
        fseek($this->fileHandle, 0);
        $this->line = fgets($this->fileHandle);
        $this->i = 0;
    }
 
    public function valid() {
        return false !== $this->line;
    }
 
    public function current() {
        return $this->line;
    }
 
    public function key() {
        return $this->i;
    }
 
    public function next() {
        if (false !== $this->line) {
            $this->line = fgets($this->fileHandle);
            $this->i++;
        }
    }
 
    public function __destruct() {
        fclose($this->fileHandle);
    }
}
?>

ولكن هناك ثمن مقابل هذه المرونة، فالمولّدات هي مكرّرات أمامية التوجيه فقط (forward-only iterators) ولا يمكن العودة إلى بدايتها بعد أن تبدأ العمل. هذا يعني أيضًا عدم إمكانية المرور على المولّد نفسه أكثر من مرة، ويجب إعادة بناء المولّد باستدعاء الدالة المولّدة مرة أخرى.

مصادر