Heim  >  Artikel  >  Backend-Entwicklung  >  Lassen Sie uns über die Verwendung des PHP-Generators sprechen

Lassen Sie uns über die Verwendung des PHP-Generators sprechen

不言
不言Original
2018-07-16 11:10:421713Durchsuche

PHP hat in Version 5.5 die Funktion „Generator“ eingeführt, diese Funktion erregte jedoch keine Aufmerksamkeit. Bei der offiziellen Migration von PHP 5.4.x auf PHP 5.5.x wurde eingeführt, dass Iteratoren (Iteratoren) auf einfache Weise implementiert werden können. Aber in welchen Szenarien können Generatoren darüber hinaus eingesetzt werden?

Die Generatorimplementierung wird über das Schlüsselwort yield abgeschlossen. Generatoren bieten eine einfache Möglichkeit, Iteratoren nahezu ohne zusätzlichen Overhead oder die Komplexität der Implementierung von Iteratoren durch Klassen zu implementieren, die die Iteratorschnittstelle implementieren.

Die Dokumentation enthält ein einfaches Beispiel, das diesen einfachen Iterator demonstriert, siehe Code unten:

function xrange($start, $limit, $step = 1) {
    for ($i = $start; $i <= $limit; $i += $step) {
        yield $i;
    }
}

Vergleichen wir es mit einem Array ohne Iteratorunterstützung:

foreach xrange($start, $limit, $step = 1) {
    $elements = [];
    
    for ($i = $start; $i <= $limit; $i += $step) {
        $elements[] = $i;
    }

    return $elements;
}

Beide Versionen von Funktionsunterstützung foreach iteratives Erhalten aller Elemente:

foreach (xrange(1, 100) as $i) {
    print $i . PHP_EOL;
}

Was können wir also außer einer kürzeren Funktionsdefinition noch bekommen? Ertrag Was genau hast du gemacht? Warum können Daten zurückgegeben werden, wenn die erste Funktion definiert ist, auch ohne die return-Anweisung?

Beginnen wir mit dem Rückgabewert. Generatoren sind eine ganz besondere Funktion in PHP. Wenn eine Funktion yield enthält, ist die Funktion keine gewöhnliche Funktion mehr, sondern gibt immer eine „Generator“-Instanz zurück. Der Generator implementiert die Iterator-Schnittstelle, weshalb er zum foreach-Traversal fähig ist.

Als nächstes verwende ich die Methoden in der Iterator-Schnittstelle, um die vorherige foreach-Schleife neu zu schreiben. Die Ergebnisse können Sie auf 3v4l.org einsehen.

$generator = xrange(1, 100);

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

    $generator->next();
}

Wir können deutlich erkennen, dass Generatoren eine fortschrittlichere Technologie sind. Lassen Sie uns nun ein neues Generatorbeispiel schreiben, um besser zu verstehen, wie es im Generator verarbeitet wird.

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

    for ($i = 0; $i < 5; $i++) {
        print &#39;foobar - yielding...&#39; . PHP_EOL;
        yield $i;
        print &#39;foobar - continued...&#39; . PHP_EOL;
    }

    print &#39;foobar - end&#39; . PHP_EOL;
}

$generator = foobar();

print &#39;Generator created&#39; . PHP_EOL;

while ($generator->valid()) {
    print "Getting current value from the generator..." . PHP_EOL;

    print $generator->current() . PHP_EOL;

    $generator->next();
}
rrree

Hä? Warum wird Generator erstellt zuerst gedruckt? Dies liegt daran, dass der Generator nichts tut, bis er verwendet wird. Im obigen Beispiel ist es der Code $generator->valid()**, der mit der Ausführung des Generators beginnt. Wir sehen, dass der Generator bis zum ersten **yield** läuft und den Kontrollfluss an den Aufrufer **$generator->valid() zurückgibt. Wenn $generator->next() aufgerufen wird, wird die Generatorausführung fortgesetzt und stoppt beim nächsten Yield erneut, und so weiter, bis kein Yield mehr vorhanden ist bis. Wir verfügen jetzt über Terminalfunktionen, die eine Pause und eine Fortsetzung bei jedem Ertrag durchführen können. Mit dieser Funktion können vom Client benötigte Verzögerungsfunktionen geschrieben werden.

Sie können eine Funktion erstellen, die alle Benutzer aus der GitHub-API liest. Paginierung wird unterstützt, Sie können diese Details jedoch ausblenden und die nächste Datenseite nur bei Bedarf abrufen. Sie können yield verwenden, um alle Benutzerdaten von der aktuellen Seite abzurufen, bis alle Benutzer auf der aktuellen Seite abgerufen sind. Anschließend können Sie die Daten der nächsten Seite abrufen.

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

Der Client kann alle Benutzer iterieren oder die Iteration jederzeit stoppen.

Generatoren als Iteratoren zu verwenden ist langweilig

Ja, Sie haben die richtige Idee. Alle Erklärungen, die ich oben gegeben habe, kann jeder aus der PHP-Dokumentation entnehmen. Aber als Iterator nutzen diese Anwendungen nicht einmal die Hälfte seiner leistungsstarken Fähigkeiten. Generatoren bieten auch die Funktionen send() und throw(), die nicht Teil der Iterator-Schnittstelle sind. Wir haben bereits über die Möglichkeit gesprochen, die Generatorausführung anzuhalten und fortzusetzen. Wenn Sie den Generator wiederherstellen müssen, können Sie nicht nur die Methode Generator::next() verwenden, sondern auch Generator::send() und Generator:: throw() Methode.

Generator::send() ermöglicht es Ihnen, den Rückgabewert von yield anzugeben, während Generator::throw() dies ermöglicht request yield löst eine Ausnahme aus. Durch diese Methoden können wir nicht nur Daten vom Generator abrufen, sondern auch neue Daten an den Generator senden.

Sehen wir uns ein Logger-Protokollbeispiel aus kooperativem Multitasking mit Coroutinen an (die Lektüre dieses Artikels wird dringend empfohlen).

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);
    }
}

Yield wird hier als Ausdruck verwendet. Wenn wir Daten senden, werden die Daten von yield zurückgegeben und dann als Parameter an fwrite() übergeben.

Um ehrlich zu sein, ist dieses Beispiel in tatsächlichen Projekten nutzlos. Es wird nur verwendet, um das Verwendungsprinzip von Generator::send() zu demonstrieren, aber die bloße Möglichkeit, Daten zu senden, hat keine große Wirkung. Anders wäre es, wenn es eine Klasse gäbe, die gewöhnliche Funktionen unterstützt.

使用生成器的乐趣来自于通过 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 < 100; $i++) {
    $parser->push("bar\r");
    $parser->push("\nfoo");
}

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

相关推荐:

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

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

Das obige ist der detaillierte Inhalt vonLassen Sie uns über die Verwendung des PHP-Generators sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn