Генераторы PHP: как всегда чистить ресурсы, даже когда вызывается перерыв? - PullRequest
2 голосов
/ 10 октября 2019

Вывод этого кода:

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

Есть идеи?

Ответы [ 2 ]

2 голосов
/ 10 октября 2019

Я понимаю, что это довольно общий вопрос, а другой ответ является прямым ответом на него.

Однако, поскольку этот вопрос помечен 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.

Также, важное замечание: если вы ожидаете результатаустановите это большое значение, рассмотрите возможность использования небуферизованного запроса , так как в противном случае ваша оперативная память будет израсходована , несмотря на выборку строк одна за другой.

2 голосов
/ 10 октября 2019

Я только что поиграл с использованием try / finally внутри вашего блока генератора ...

Try / Наконец всегда: всегда выполняет содержимое блока 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 в зависимости от перерыва


Попробуйте / наконец с условием: блок try / finally с проверкой какого-либо условия.

В этом примере я использовал key( $rows ), так как он вернет null после того, как foreach был успешно исчерпан (т. е. полная итерация) илиненулевое значение в «перерыве»

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 в зависимости от перерыва

...