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 finally
或 a 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 c
或 a 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 将被消耗 逐个。
这段代码的输出:
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 finally
或 a 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 c
或 a 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 将被消耗 逐个。