在PHP中创建可选的生成器

6tdlim6h  于 2023-06-21  发布在  PHP
关注(0)|答案(2)|浏览(92)

我正在努力在PHP中创建可选的生成器函数。从本质上讲,我试图构建一个函数,它只在有多个值的情况下才变成生成器,并且在只有一个值的情况下表现得像一个普通函数。
根据official documentation
任何包含yield的函数都是生成器函数。
一个简单而容易遵循的规则。然而,我最近一直在做的事情让我在这方面遇到了麻烦。特别是,我还没有找到一种干净的方法来实现生成器函数,这种方法允许我只在必要时返回生成器。举个简单的例子:

function generatorTest() {
    if(false) {
        yield 0;
    } else {
        return 0;
    }
}

var_dump(generatorTest()); // object(Generator)

查看代码,人们可能会期望只获取值,但事实证明,只要包含yield关键字,函数就会变成生成器。这与文件相符,但值得怀疑。即使yield关键字不可访问,该函数也会变成生成器。一个简单的方法是返回一个本身就是生成器的闭包,这样函数本身就不会变成生成器:

function generatorTest() {
    if(false) {
        return (function() {
            yield 0;
        })();
    } else {
        return 0;
    }
}

var_dump(generatorTest()); // int(0)

这虽然不是最干净的解决方法,但实际上有效。然而,对于我目前正在开发的应用程序,这是相当不切实际的,因为我不只是产生/返回一个简单的值,而是迭代复杂的API请求。因此,我会有两个相当相似的块,并且不必要地使函数膨胀。
我还研究了其他选择,并提出了这个:

function generatorTest() {
    $ret = (function() {
        if(false) {
            yield 0;
        } else {
            return 0;
        }
    })();
    
    try {
        return $ret->getReturn();
    } catch(Exception $e) {
        return $ret;
    }
}

var_dump(generatorTest()); // int(0)

如果需要,它不会返回一个生成器函数,而是总是创建一个生成器并检查是否存在任何返回值。它不会使代码膨胀,因为它总是返回一个函数,只有return和yield实际上是分支,其他所有东西只需要写一次。然而,它基本上故意使用异常来检查它实际上是一个生成器还是返回的正常值。这真的是有点丑陋。
所以这里有一个问题,是否有更好的方法来创建可选的生成器函数,就像在函数中,只有在条件适用时才是生成器,否则将正常返回一样?我是否完全脱离了我的建模,应该寻找不同的方法?或者我的情况是如此的与众不同,以至于我丑陋的解决方案几乎是这种情况下最好的?

编辑:申请的其他详细信息

问题中的应用程序是this MediaWiki bot。具体来说,我目前正在处理这个函数,它已经使用了我在示例中提到的第二个方法,但是在返回生成器之前存储了它加载的所有数据,这几乎违背了生成器的目的,既没有效率也没有用处,因为我必须等待一个小时才能知道它在执行大量请求时是否真的工作。
上面提到的函数用于查询wiki上的修订版本,其想法是根据您想要查询的内容提供不同的返回类型。由于重载在PHP中实际上是不可能的,所以这是我得到的结果。当使用单个id调用函数时,它看起来像这样:

require_once("bottibott_v5/src/autoload.php");

$bot = new Bot(URL); // URL is the url to the api endpoint of the wiki
$revisions = new Revisions($bot);
$revisions->setIds("1");

var_dump($revisions->getRevisionsFromRevids()); // would output some kind of revision record, depending on the wiki

同时,当使用多个id调用同一个函数时,请求看起来像这样:

require_once("bottibott_v5/src/autoload.php");

$bot = new Bot(URL); // URL is the url to the api endpoint of the wiki
$revisions = new Revisions($bot);
$revisions->setIds(range(0, 10));

foreach($revisions->getRevisionsFromRevids() as $revision) {
    var_dump($revision); // would output each revision record individually
}
nr9pn0ug

nr9pn0ug1#

如果你有一个值,你可以把它转换成一个数组,这样你总是从函数中得到任何值。主要的优点是你只需要在生成器上进行foreach并执行相同的逻辑...

function generatorTest(mixed $range)
{
    if (!is_array($range)) {
        $range = [$range];
    }
    foreach ($range as $individual) {
        yield $individual;
    }
}

var_dump(generatorTest(1));
// class Generator#1 (0) {
// }

var_dump(generatorTest(range(0, 10))); 
// class Generator#1 (0) {
// }
mzillmmw

mzillmmw2#

实际上,我同意在所有情况下都使用生成器,以使代码更简洁,更易于使用!

function generatorTest(mixed $arr): Generator
{
    yield from is_array($arr)? $arr: [$arr];
}

但有一种方法可以做到如你所愿更干净一点

使用两个函数

function makeGenerator(array $array):Generator
{
    yield from $array;
}

function generatorTest(bool $generator = false):Generator|int
{
    $array = [1, 2, 3];
    return $generator? makeGenerator($array): 5;
}

在你的例子中,代码应该是这样的,从第113行到第123行:

return $generator && count($revisions) == 1? $this->makeGenerator($revisions): reset($revisions);

在第125行生成一个新函数:

protected function makeGenerator(array $revisions){
    yield from $revisions;
}

相关问题