簡單介紹 PHP 的生成器 (Generators)

PHP 的生成器 (Generators) 提供了一個簡單的方式去實現迭代器 (Iterrators)。

如官方文件所說:

A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate.

生成器可以幫助你寫出一個可以使用 foreach() 迭代一組數據的程式碼,使用在構建可能超出記憶體限制或者需要大量時間生成的數組很方便。

以下方的程式碼為例,range(1, 10_000_000) 會生成一組資料量龐大的陣列並儲存在記憶體中,這會導致程式執行過程中因為超出記憶體限制而出現錯誤。

<?php

foreach (range(1, 10_000_000) as $number) {
    echo $number;
}

執行上述的程式碼,大概率會得到下方的錯誤訊息。

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 536870920 bytes) in ...

在 PHP 中,我們可以使用 yield 關鍵字來生成一個生成器 (Generator) 物件。

<?php

// yield 關鍵字會返回一個生成器物件
function lazyRange(int $start, int $end): Generator
{
    echo 'start' . PHP_EOL;

    for ($num = $start; $num <= $end; $num++) {
        echo 'number-';

        yield $num;
    }
}

// 生成器可以被 foreach 迭代,現在可以順暢的印出所有結果而不會導致記憶體爆掉
foreach (lazyRange(start: 1, end: 10_000_000) as $number) {
    echo $number;
}

// 印出的結果
// start
// number-1
// number-2
// number-3
// number-4
// number-5
// ...

除了 foreach,你也可以使用 generator 提供的一些方法來進行迭代。

$numbers = lazyRange(start: 1, end: 10);
// 返回第一個 yield 的值
// 如果 generator 已經開始迭代,這裡會拋出錯誤
$numbers->rewind();

// 返回第一個 yield 的內容,即 1
// 此時畫面上的輸出為
// start
// number-1
echo $numbers->current();

// 繼續執行到下一個 yield 之前停止
// 此時畫面上的輸出為
// start
// number-1
// number-
echo $numbers->next();

// 返回第二個 yield 值的內容,即 2
// 此時畫面上的輸出為
// start
// number-1
// number-2
echo $numbers->current();

// 繼續執行到下一個 yield 之前停止
// 此時畫面上的輸出為
// start
// number-1
// number-2
// number-
echo $numbers->next();

生成器在讀取擁有大量資料的檔案 (如好幾百萬行的 csv) 時非常方便,可以多加利用。

參考資料


sharkHead
written by
sharkHead

後端工程師, PHP 基金會每月 5 鎂小額贊助人 稍微擅長 PHP、Python 與 Google Search,偶爾寫寫 TypeScript 對於逗號後面必須加空格有著絕對的堅持