Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Генераторы в действии

Anna | 31.05.2014 | нет комментариев

Не так давным-давно я решил для себя, что пора восполнить огромный пробел в умениях и решил прочитать про переходы между версиями PHP, т.к. понимал, что остался где-то между 5.2 и 5.3 и данный пробел нужно как-то устранить. До этого я читал про namespaces, traits и т.д, но дальше чтения не уходило. И вот здесь я подметил генераторы, почитал документацию, одну из статей на прогре на данный счет и позже этого появилась мысль — а как прежде без них жили-то?

Данным переводом хочу подмогнуть правда бы новичкам, от того что на php.net документация по генераторам на английском и, на мой взор, надлежащим образом не раскрывает всю идею и места использования. Текста много, кода чуть поменьше, картинок нет. Понадобятся всеобщие познания, скажем, про итераторы. ?вственный код комментировать не буду, а вот трудные для понимания примеры постараюсь объяснить в силу своих познаний.

Теория

Сразу скажу основную вещь — генераторы никоим образом не добавляют новых вероятностей языку. Всюду, где применяются генераторы, дозволено также применять итераторы. Сейчас, зная об этом, сразу взглянем на пример. Скажем, нам нужно пройтись по строкам в файле. В процедурном жанре это дозволено сделать как-то так:

$f = fopen($file, 'r');
while ($line = fgets($f)) {
    doSomethingWithLine($line);
}

Это обыкновенное решение, ничего необычного здесь нет. Но что если нам необходимо что-то больше абстрактное? Скажем, генерировать строки из абстрактного источника. Да, сегодня это может быть файл, но завтра мы решим, что больше благополучным решением будет база данных либо вообще что-то иное.

Теперь у нас есть два пути решения данной задачи — мы можем воротить массив либо итератор. Но возвращая массив есть несколько задач: во-первых мы не знаем сколько нам необходимо памяти (внезапно файл у нас размером 30 гб?), а во-вторых, допустимо, мы и совсем не сумеем описать наш источник как массив (скажем, мы можем возвращать безграничные доли данных и испробуй угадай когда данный поток закончится, если ты заказчик).

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

class FileIterator implements Iterator {
    protected $f;
    public function __construct($file) {
        $this->f = fopen($file, 'r');
        if (!$this->f) throw new Exception();
    }
    public function current() {
        return fgets($this->f);
    }
    public function key() {
        return ftell($this->f);
    }
    public function next() {
    }
    public function rewind() {
        fseek($this->f, 0);
    }
    public function valid() {
        return !feof($this->f);
    }
}

Вовсе легко, не так ли? Отлично, не вовсе, но теснее что-то. Правда если мы взглянем на пример внимательнее, то увидим, что мы не вовсе верно описали итератор, от того что двойственный вызов способа current() не даст нам ожидаемый итог в виде одного и того же значения.
Я (автор статьи, не «переводчик») сделал это намеренно, Дабы показать, что замена процедуры на итератор не неизменно является примитивный задачей, от того что в реальных обстановках все куда труднее. Давайте сделаем верный итератор для нашего файла.

class FileIterator implements Iterator {
    protected $f;
    protected $data;
    protected $key;
    public function __construct($file) {
        $this->f = fopen($file, 'r');
        if (!$this->f) throw new Exception();
    }
    public function __destruct() {
        fclose($this->f);
    }
    public function current() {
        return $this->data;
    }
    public function key() {
        return $this->key;
    }
    public function next() {
        $this->data = fgets($this->f);
        $this->key  ;
    }
    public function rewind() {
        fseek($this->f, 0);
        $this->data = fgets($this->f);
        $this->key = 0;
    }
    public function valid() {
        return false !== $this->data;
    }
}

Боже, как много каждого для, казалось бы, примитивный задачи типа обхода файла, да и основная работа все равно спрятана внутри функций работы с файлами. Сейчас, предположим, что нам необходимо сделать реализовать больше трудный алгорифм. Если продолжать нынешний подход, то онможет стать еще труднее и осознать его работу будет сложнее. Давайте решим нашу загвоздку с поддержкой генераторов.

function getLines($file) {
    $f = fopen($file, 'r');
    if (!$f) throw new Exception();
    while ($line = fgets($f)) {          
        yield $line;
    }
    fclose($f);
}

Гораздо проще! Да, это примерно как 1-й пример с функцией, только возникло исключение и ключевое слово yield.

Выходит, как оно работает?

Дюже значимо понимать, что в примере выше изменяется возвращаемое значение функции. Это не null, как может показаться с первого взора. Присутствие yield говорит о том, что PHP вернет нам особый класс — генератор. Генератор ведет себя также, как и итератор, от того что он реализует его. И применять генератор дозволено подобно итераторам.

foreach (getLines("someFile") as $line) {
    doSomethingWithLine($line);
}

Каждая фишка тут в том, что мы можем писать код как желательно и легко выбрасывать (yield, йелднуть, йелдануть… не знаю как перевести положительнее, когда есть бросание исключений) всякий раз новое значение когда нам это нужно. Выходит, как же оно работает? Когда мы вызываем функцию getLines(), PHP исполнит код до первой встречи ключевого слова yield, на котором он запомнит это значение и вернет генератор. После этого, будет вызов способа next() у генератора (тот, что описан нами либо итератором), PHP вновь исполнит код, только начнет его не с самого начала, а начиная с прошлого значения, которое мы удачно выкинули и позабыли о нем, и вновь, до дальнейшего yield либо же конца функции, либо return. Зная данный алгорифм, сейчас дозволено сделать пригодный генератор:

function doStuff() {
    $last = 0;
    $current = 1;
    yield 1;                                               
    while (true) {                                     
        $current = $last   $current;
        $last = $current - $last;
        yield $current;                              
    }
}

Допустимо, с первого взора не вовсе ясно что это, да и вообще безграничный цикл все испортит. Да, эта функция и будет трудиться как безграничный цикл. Но посмотрите внимательнее — это чай числа Фибоначчи.

Необходимо подметить, что генераторы не являются заменой итераторам. Это лишь легкой путь их приобретения. Итераторы по-бывшему являются сильным инструментом.

Трудный пример

Нам необходимо сделать личный ArrayObject. Взамен того, Дабы делать итератор, сделаем маленький трюк с генератором. Интерфейс IteratorAggregate требует от нас каждого один способ — getIterator(). Так как генератор возвращает объект, реализующий итератор, то мы можем переопределить данный способ таким образом, Дабы он возвращал генератор. Все легко:

class ArrayObject implements IteratorAggregate {
    protected $array;
    public function __construct(array $array) {
        $this->array = $array;
    }
    public function getIterator() {
        foreach ($this->array as $key => $value) {
            yield $key => $value;
        }
    }
}

В точку! Сейчас мы можем перебрать все свойства нашего массива через генератор либо через обыкновенный синтаксис обращения по ключу.

Отправляем данные обратно

Генераторы разрешают отправлять себе данные, применяя способ send(). В некоторых случаях это может быть дюже комфортно. Скажем, когда нужно сделать какой-то лог-файл. Взамен того, Дабы писать целый класс для него, дозволено легко воспользоваться генераторами:

function createLog($file) {
    $f = fopen($file, 'a');
    while (true) {          # да, вновь безграничный цикл;
        $line = yield;      # безгранично "слушаем" способ send() для установки нового значения $line;
        fwrite($f, $line);
    }
}
$log = createLog($file);
$log->send("First");
$log->send("Second");
$log->send("Third");

Достаточно легко и стремительно. Дабы немножко усложнить задачу, посмотрим пример, где функции работают коллективно, перекидывая управление между собой при помощи генераторов. Нам необходимо возвести очередь, которая получает и отправляет данные пакетами. Изредка такие задачи возникают, когда мы читаем бинарный поток и необходимо контролировать

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB