编程

探索 PHP yield 关键词

132 2025-03-25 18:11:00

想象一个场景,我们要编写一个命令,将文件迭代存入 S3 bucket 并对其逐个进行处理。我们可以使用 listObjectsV2 方法调用 S3 API 罗列 bucket 中的内容:

AwsFacade::createClient('s3', $config)
    ->listObjectsV2([
        'Bucket' => 'top-secret-bucket'
    ]);

这个 API 调用将会返回多达 1000 个结果,鉴于我们需要扫描多达上百上千个文件,我们准备进行上百次调用。在对数据库记录进行分页时,可以使用 LIMIT 和 OFFSET 轻松完成,但在进行像 S3 这样的 HTTP 调用时呢?

public function handle(): void
{
    $s3Client = AwsFacade::createClient('s3', $config);
    $continuationToken = null;
 
    do {
        $response = $s3Client->listObjectsV2([
            'Bucket' => 'top-secret-bucket',
            'ContinuationToken' => $continuationToken,
        ]);
 
        foreach ($response->get('Contents') as $result) {
            $this->processResult($result);
        }
 
        $continuationToken = $response->get('NextContinuationToken');
        $isTruncated = $response->get('IsTruncated');
    } while ($isTruncated);
}

IsTruncated 指明是否还有更多的结果,NextContinuationToken 为我们提供了一个指针,我们可以使用它对 bucket 中的所有文件进行分页。

不过,从 S3 获取文件列表只是其中一部分,所以我真的不想让所有的分页功能弄乱我命令的主要方法。我更喜欢这样的东西;

public function handle(): void
{
    foreach ($this->listS3Files() as $s3File) {
        $this->processFile($s3File);
    }
}

一种方法是将 do-while 循环移动到一个单独的方法中,并构建一个返回的结果数组,但当我们处理如此多的记录时,这变得不切实际。我们需要某种方法将 HTTP 调用提取到一个单独的方法中,同时轻松地对结果进行分页,以便我们可以根据需要进行尽可能多的调用。yield 来了:

public function handle(): void
{
    $this->s3Client = AwsFacade::createClient('s3', $config);
 
    foreach ($this->listS3Files() as $s3File) {
        $this->processFile($s3File);
    }
}
 
// The return type of the method is now iterable 
private function listS3Files(): iterable
{
    $continuationToken = null;
 
    do {
        $response = $this->s3Client->listObjectsV2([
            'Bucket' => 'top-secret-bucket',
            'ContinuationToken' => $continuationToken,
        ]);
 
        foreach ($response->get('Contents') as $result) {
            // Using yield rather than return allows us to paginate over results 
            yield $result;
        }
 
        $continuationToken = $response->get('NextContinuationToken');
        $isTruncated = $response->get('IsTruncated');
    } while ($isTruncated);
}

return 替换为 yield 会将此方法更改为所谓的生成器。当一个方法返回结果时,该方法范围内的所有其他内容都将被丢弃。如果我们得到结果,生成器的状态将被保存,$continuationToken 将被保留,它会在每次调用时从停止的地方继续,对所有结果进行懒惰的分页。这意味着我们可以在 foreach 循环中使用该方法,迭代 S3 bucket 中的每个文件,根据需要处理每个文件,同时在需要时进行额外的 HTTP 调用以获取更多结果!

 

PHP
下一篇