PHP 生成器:如何始终清理资源,即使在调用 break 时也是如此?

PHP Generators: How to always clean resources, even when break is called?

这段代码的输出:

function gen() {
    $rows = ['a', 'b', 'c'];

    foreach ($rows as $row) {
        echo "yield $row\n";
        yield $row;
    }


    echo "finished\n";
}

foreach (gen() as $v) {
    echo "val $v\n";
    // break;
}

yield a
val a
yield b
val b
yield c
val c
finished

未完成的休息时间是:

yield a
val a

因此,如果我中断循环,则不会执行 gen() 函数中循环之后的代码。我需要清理一些资源,但我不知道该怎么做。

例如,这里:

public function getRows(string $query, array $pameters = []): \Generator
{
    $stmt = $this->pdo->prepare($query);

    // bind pameters...

    $stmt->execute();

    while ($row = $stmt->fetch()) {
        $item = $this->something($row);
        yield $item;
    }

    $stmt->closeCursor(); // this code is not executed if a break is called
}

或者一个读取文件的函数,游标应该在最后关闭:

$cursor = openFileCursor('myfile.txt')
while ($line = $cursor->getLine()) {
    $item = someFunction($line);
    yield $item
}
closeFileCursor($cursor);

有什么想法吗?

我刚刚在你的生成器块中使用了 try/finally...

Try/Finally总是:总是执行finally块的内容,不管break

function gen(){
  try{
    $rows = ['a', 'b', 'c'];
    foreach( $rows as $row ){
      yield $row;
    }
  }
  finally
  {
    // close your connections here, always
    echo "finally";
  }
}

foreach ( gen() as $v ){
  echo $v;
  break;
}

这将打印:a b c finallya finally 基于 break


Try/Finally 条件: try/finally 块检查某种条件。

在这个例子中,我使用了 key( $rows ) 因为这将 return null 一旦 foreach 已成功耗尽(即完整迭代)或非空 "break"

function gen(){
  try {
    $rows = ['a','b','c'];
    foreach ( $rows as $row) {
      yield $row;
    }
  }
  finally {
    // condition to detect incomplete return (like a row/total counter)
    if( key( $rows ) !== null ) {
      // close your connections here, but only on incomplete generation
      echo "finally";
    }

  }
}

foreach ( gen() as $v ) {
  echo $v;
  break;
}

这将打印:a b ca finally 基于 break

我确实意识到这是一个相当笼统的问题,另一个答案是对它的直接回答。

然而,由于这个问题是用 PDO 标记的,而你的实际例子是关于这个特定的 API,有一个更简单的方法来实现你的目标:假设 PDOStatement 已经可以遍历,你可以编写这个函数像这样

public function getRows(string $query, array $parameters = []): PDOStatement
{
    $stmt = $this->pdo->prepare($query);
    $res = $stmt->execute($parameters);
    return $stmt;
}

那么您可以像使用生成器函数一样使用它:

$sql = "SELECT * FROM users WHERE salary > ?";
foreach ($db->getRows($sql, [0]) as $row) {
    // whatever
}

当循环将以这种或另一种方式结束时,$stmt 将被取消,因此游标将自动关闭。

作为奖励,您可以为该函数指定一个更通用的名称,并将其用于任何查询,例如 INSERT 或 DELETE。

此外,重要说明:如果您期望结果集那么大,请考虑使用 unbuffered query,否则尽管获取行,您的 RAM 将被消耗 逐个。