chris_archer: (Default)

Однажды я жаловался, что

foreach((function () {for ($i = 0; $i < 10; $i++) yield $i;})() as $i) echo $i;

в PHP не работает. Так вот. Начиная с PHP7 благодаря новому парсингу синтаксиса

foreach((function () {for ($i = 0; $i < 10; $i++) yield $i;})() as $i) echo $i; // 0123456789

очень даже работает.

Originally published at Chase Your Dreams!. You can comment here or there.
chris_archer: (Default)

Очень интересный и полезный момент сегодня всплыл в комментариях на Хабре. В PHP 5.5, как известно, сделали поддержку функций-генераторов, по типу питоновских. Там раньше были итераторы, но с адовым синтаксисом (как всё в SPL), а теперь ввели оператор ‘yield’ и всё волшебным образом упростилось.

Например, можно написать такой генератор, читающий построчно файл:

  1. function getLines($file) {
  2.     f = fopen($file, ‘r’);
  3.     while ($line = fgets($f)) {
  4.         yield $line;
  5.     }
  6.     fclose($f);
  7. }

‘yield’ означает «вернуть значение и продолжить с этого места при следующем вызове функции». Имея такой генератор, можно сделать вот такую печать файла:

  1. foreach (getLines("file.txt") as $line) {
  2.      echo $line;
  3. }

Удобно? Очень удобно. Оператор ‘yield’ выдаст все строки файла, а потом, когда файл закончится, произойдёт обычный ‘return’ из функции, который закроет генератор (и закончит цикл).

Но как известно, если всё идёт хорошо, значит, вы чего-то не заметили. Немного изменим наш цикл:

  1. foreach (getLines("file.txt") as $n => $line) {
  2.     if ($n > 5) break;
  3.     echo $line;
  4. }

Предположим, нас интересуют только первые шесть строк файла, а дальше мы хотим прервать цикл оператором ‘break’. Имеем на то полное право. Но что в этом случае произойдёт внутри генератора? А ничего. Он останется стоять на последнем исполненном yield-е и никогда не дойдёт до строки ‘fclose($f)’. И наш файл останется незакрытым.

Мы получили утечку ресурса (открытого файла). Понятно, что внутри генератора могут быть открыты любые ресурсы и объекты, и их необходимо правильно и предсказуемо закрывать. Но как это сделать, если юзер может в любой момент сделать break? Обычная документация (http://www.php.net/manual/en/language.generators.overview.php) никаких намёков не даёт.

Так вот, оказывается (и за это спасибо юзеру weirdan с Хабра: http://habrahabr.ru/post/189796/#comment_6594776), что читать в этом случае надо не документацию, а RFC по генераторам: https://wiki.php.net/rfc/generators#closing_a_generator. А в нём сказано, что при освобождении ссылки на генератор, у него внутри обязаны выполниться все блоки ‘finally’. И тогда мы получаем очень простой, красивый и безопасный код:

  1. function getLines($file) {
  2.     f = fopen($file, ‘r’);
  3.     try {
  4.         while ($line = fgets($f)) {
  5.             yield $line;
  6.         }
  7.     } finally {
  8.         fclose($f);
  9.     }
  10. }

В этом случае блок ‘finally’ выполнится и при нормальном выходе из цикла по генератору и при выходе по break-у. Ура.

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

ru-php.livejournal.com/1566325.html

Originally published at Chase Your Dreams!. You can comment here or there.

Profile

chris_archer: (Default)
chris_archer

June 2017

S M T W T F S
    123
456 78910
11121314151617
18192021222324
252627 282930 

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Aug. 21st, 2017 06:39 am
Powered by Dreamwidth Studios