搜尋
首頁後端開發php教程淺談一下PHP生成器的使用方法

淺談一下PHP生成器的使用方法

Jul 16, 2018 am 11:10 AM
coroutinephpyield

PHP 在 5.5 版本中引入了「生成器(Generator)」特性,不過這個特性並沒有引起人們的注意。在官方的 從 PHP 5.4.x 遷移到 PHP 5.5.x 中介紹說它能以簡單的方式實作迭代器(Iterator)。但是,除此之外,生成器又可以在哪些場景下使用呢?

產生器實作透過 yield 關鍵字完成。生成器提供一種簡單的方式實作迭代器,幾乎沒有任何額外開銷或需要透過實作迭代器介面的類別這種複雜方式來實現迭代。

文件提供了一個簡單的實例示範這個簡單的迭代器,請看下面的程式碼:

function xrange($start, $limit, $step = 1) {
    for ($i = $start; $i <p>讓我們將它與無迭代器支援的陣列進行比較:</p> <pre class="brush:php;toolbar:false">foreach xrange($start, $limit, $step = 1) {
    $elements = [];
    
    for ($i = $start; $i <p>這兩個版本的函數都支援<strong>foreach</strong> 迭代取得所有元素:</p><pre class="brush:php;toolbar:false">foreach (xrange(1, 100) as $i) {
    print $i . PHP_EOL;
}

所以除了一個更短的函數定義,我們還能取得什麼呢? yield 到底做了什麼?為什麼在第一個函數定義時依然可以傳回數據,即使沒有 return 語句?

先從回傳值說起。生成器是 PHP 中的一個很特別的函數。當一個函數包含 yield,那麼這個函數即不再是一個普通函數,它永遠傳回一個「Generator(生成器)」實例。生成器實作了 Iterator 接口,這就是為何它能夠進行 foreach  遍歷的原因。

接下來我使用 Iterator 介面中的方法,對先前的 foreach 迴圈進行重寫。你可以在 3v4l.org 上查看結果。

$generator = xrange(1, 100);

while($generator->valid()) {
    print $generator->current() . PHP_EOL;

    $generator->next();
}

我們可以清楚的看到生成器是更高級的技術,現在讓我們編寫一個新的生成器範例來更好的理解到底在生成器內部是如何進行處理的吧。

function foobar() {
    print 'foobar - start' . PHP_EOL;

    for ($i = 0; $i valid()) {
    print "Getting current value from the generator..." . PHP_EOL;

    print $generator->current() . PHP_EOL;

    $generator->next();
}
Generator created
foobar - start
foobar - yielding...
Getting current value from the generator...
1
foobar - continued
foobar - yielding...
Getting current value from the generator...
2
foobar - continued
foobar - yielding...
Getting current value from the generator...
3
foobar - continued
foobar - yielding...
Getting current value from the generator...
4
foobar - continued
foobar - yielding...
Getting current value from the generator...
5
foobar - continued
foobar - end

嗯?為什麼 Generator created 最早印出來?這是因為生成器在使用之前不會執行任何操作。在上例中就是$generator->valid()** 這句程式碼才開始執行生成器。我們看到生成器一直運行到了第一個 **yield** 時,將控制流程交還給呼叫者 **$generator->valid()$generator->next() 呼叫時則恢復生成器執行,到下一個yield 再次停止運行,如此反覆直到沒有更多的yield#為止。我們現在擁有了可以在任何 yield 執行暫停和回應的終端函數。這個特性允許編寫客戶端所需的延遲函數。

你可以建立一個從 GitHub API 讀取所有使用者的功能。支援分頁處理,但是你可以隱藏這些細節並且僅當需要時再去獲取下一頁資料。你可以使用 yield 從目前頁面取得每個使用者數據,直到目前頁面所有使用者取得完成,你就可以再去取得下一頁數據。

class GitHubClient {
    function getUsers(): Iterator {
        $uri = '/users';

        do {
            $response = $this->get($uri);
            foreach ($response->items as $user) {
                yield $user;
            }

            $uri = $response->nextUri;
        } while($uri !== null);
    }
}

客戶端可以迭代出所有使用者或在任何時候停止遍歷。

把生成器當迭代器使用真是無聊

是的,你的想法是對的。以上我給的所有講解任何人都可以從 PHP 文件中取得。但是作為迭代器這些使用,連它強大功能的一半都沒用到。生成器也提供了不屬於 Iterator 介面的 send()throw() 功能。我們前面談到了暫停和恢復生成器執行功能。當需要還原生成器時,不僅可以功過Generator::next() 方法,還可以使用Generator::send()Generator::throw() 方法。

Generator::send() 允許你指定yield 的回傳值,而Generator::throw() 允許向 yield 拋出異常。透過這些方法我們不僅可以從生成器中獲取數據,還能向生成器發送新數據。

讓我們來看一個從 Cooperative multitasking using coroutines(強烈建議閱讀本文)摘取的 Logger 日誌範例。

function logger($filename) {
    $fileHandle = fopen($filename, 'a');

    while (true) {
        fwrite($fileHandle, yield . "\n");
    }
}

$logger = logger(__DIR__ . '/log');
$logger->send('Foo');
$logger->send('Bar');

yield 在這裡是作為表達式使用的。當我們發送資料時,從 yield 傳回資料然後作為參數傳入到 fwrite()

講真,這個範例在實際專案中沒什麼用。它僅用於演示 Generator::send() 的使用原理,但是僅僅能夠發送資料並沒有太大作用。如果有一個類別和普通函數支援的話就不一樣了。

使用生成器的乐趣来自于通过 yield 创建数据,然后由「生成器执行程序(generator runner)」依据这个数据来处理业务,然后再继续执行生成器。这就是「协程(coroutines)」和「状态流解析器(stateful streaming parsers)」实例。在讲解协程和状态流解析器之前,我们快速浏览一下如何在生成器中返回数据,我们还没有将接触这方面的知识。从 PHP 5.5 开始我们可以在生成器内部使用 return; 语句,但是不能返回任何值。执行 return; 语句的唯一目的是结束生成器执行。

不过从 PHP 7.0 起支持返回值。这个功能在用于迭代时可能有些奇怪,但是在其他使用场景如协程时将非常有用,例如,当我们在执行一个生成器时我们可以依据返回值处理,而无需直接对生成器进行操作。下一节我们将讲解 return 语句在协程中的使用。

异步生成器

Amp 是一款 PHP 异步编程的框架。支持异步协程功能,本质上是等待处理结果的占位符。「生成器执行程序」为 Coroutine类。它会订阅异步生成器(yielded promise),当有执行结果可用时则继续生成器处理。如果处理失败,则会抛出异常给生成器。你可以到 amphp/amp 版本库查看实现细节。在 Amp 中的 Coroutine 本身就是一个 Promise。如果这个协程抛出未经捕获的异常,这个协程就执行失败了。如果解析成功,那么就返回一个值。这个值看起来和普通函数的返回值并无二致,只不过它处于异步执行环境中。这就是需要生成器需要有返回值的意义,这也是为何我们将这个特性加入到 PHP 7.0 中的原因,我们会将最后执行的yield 值作为返回值,但这不是一个好的解决方案。

Amp 可以像编写阻塞代码一样编写非阻塞代码,同时允许在同一进程中执行其它非阻塞事件。一个使用场景是,同时对一个或多个第三方 API 并行的创建多个 HTTP 请求,但不限于此。得益于事件循环,可以同时处理多个 I/O 处理,而不仅仅是只能处理多个 HTTP请求这类操作。

Loop::run(function() {
    $uris = [
        "https://google.com/",
        "https://github.com/",
        "https://stackoverflow.com/",
    ];

    $client = new Amp\Artax\DefaultClient;
    $promises = [];

    foreach ($uris as $uri) {
        $promises[$uri] = $client->request($uri);
    }

    $responses = yield $promises;

    foreach ($responses as $uri => $response) {
        print $uri . " - " . $response->getStatus() . PHP_EOL;
    }
});

但是,拥有异步功能的协程并非只能够在 yield 右侧出现变量,还可以在它的左侧。这就是我们前面提到的解析器。

$parse = new Parser((function(){
    while (true) {
        $line = yield "\r\n";

        if (trim($line) === "") {
            continue;
        }

        print "New item: {$line}" . PHP_EOL;
    }
})());

for ($i = 0; $i push("bar\r");
    $parser->push("\nfoo");
}

解析器会缓存所有输入直到接收的是 rn。这类生成器解析器并不能简化简单协议处理(如换行分隔符协议),但是对于复杂的解析器,如在服务器解析 HTTP 请求的 Aerys。

相关推荐:

如何利用PHP实现开发中基于layUI的三级联动效果的代码

关于IIS下PHP快三平台源码的架设环境的配置过程

以上是淺談一下PHP生成器的使用方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
簡單地說明PHP會話的概念。簡單地說明PHP會話的概念。Apr 26, 2025 am 12:09 AM

phpsessionstrackuserdataacrossmultiplepagerequestsusingauniqueIdStoredInAcookie.here'showtomanageThemeffectionaly:1)startAsessionWithSessionWwithSession_start()和stordoredAtain $ _session.2)

您如何循環中存儲在PHP會話中的所有值?您如何循環中存儲在PHP會話中的所有值?Apr 26, 2025 am 12:06 AM

在PHP中,遍歷會話數據可以通過以下步驟實現:1.使用session_start()啟動會話。 2.通過foreach循環遍歷$_SESSION數組中的所有鍵值對。 3.處理複雜數據結構時,使用is_array()或is_object()函數,並用print_r()輸出詳細信息。 4.優化遍歷時,可採用分頁處理,避免一次性處理大量數據。這將幫助你在實際項目中更有效地管理和使用PHP會話數據。

說明如何使用會話進行用戶身份驗證。說明如何使用會話進行用戶身份驗證。Apr 26, 2025 am 12:04 AM

會話通過服務器端的狀態管理機制實現用戶認證。 1)會話創建並生成唯一ID,2)ID通過cookies傳遞,3)服務器存儲並通過ID訪問會話數據,4)實現用戶認證和狀態管理,提升應用安全性和用戶體驗。

舉一個如何在PHP會話中存儲用戶名的示例。舉一個如何在PHP會話中存儲用戶名的示例。Apr 26, 2025 am 12:03 AM

Tostoreauser'snameinaPHPsession,startthesessionwithsession_start(),thenassignthenameto$_SESSION['username'].1)Usesession_start()toinitializethesession.2)Assigntheuser'snameto$_SESSION['username'].Thisallowsyoutoaccessthenameacrossmultiplepages,enhanc

哪些常見問題會導致PHP會話失敗?哪些常見問題會導致PHP會話失敗?Apr 25, 2025 am 12:16 AM

PHPSession失效的原因包括配置錯誤、Cookie問題和Session過期。 1.配置錯誤:檢查並設置正確的session.save_path。 2.Cookie問題:確保Cookie設置正確。 3.Session過期:調整session.gc_maxlifetime值以延長會話時間。

您如何在PHP中調試與會話相關的問題?您如何在PHP中調試與會話相關的問題?Apr 25, 2025 am 12:12 AM

在PHP中調試會話問題的方法包括:1.檢查會話是否正確啟動;2.驗證會話ID的傳遞;3.檢查會話數據的存儲和讀取;4.查看服務器配置。通過輸出會話ID和數據、查看會話文件內容等方法,可以有效診斷和解決會話相關的問題。

如果session_start()被多次調用會發生什麼?如果session_start()被多次調用會發生什麼?Apr 25, 2025 am 12:06 AM

多次調用session_start()會導致警告信息和可能的數據覆蓋。 1)PHP會發出警告,提示session已啟動。 2)可能導致session數據意外覆蓋。 3)使用session_status()檢查session狀態,避免重複調用。

您如何在PHP中配置會話壽命?您如何在PHP中配置會話壽命?Apr 25, 2025 am 12:05 AM

在PHP中配置會話生命週期可以通過設置session.gc_maxlifetime和session.cookie_lifetime來實現。 1)session.gc_maxlifetime控制服務器端會話數據的存活時間,2)session.cookie_lifetime控制客戶端cookie的生命週期,設置為0時cookie在瀏覽器關閉時過期。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器