Рассматриваем PHP генераторы

8227

Рассматриваем PHP генераторы

PHP генераторы — полезное понятие, чтобы понять мир разработки PHP.

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

Что такое PHP генераторы?

Добавленные в PHP в версии 5.5, генераторы являются функциями, которые обеспечивают простой способ для перебора данных без необходимости создания массива в памяти. Еще немного путаетесь? Пример — хороший способ показать генераторы в действии.

Во-первых, быстро создадим файл generator.php который мы будем использовать в этом руководстве. После создания файла, мы добавим этот небольшой фрагмент кода.

function getRange ($max = 10) {
    $array = [];

    for ($i = 1; $i < $max; $i++) {
        $array[] = $i;
    }

    return $array;
}

foreach (getRange(15) as $range) {
    echo "Dataset {$range} 
";
}

Мы можем быстро распаковать встроенный PHP-сервер в каталог, где мы создали файл generator.php:

php -S localhost:8000

Поэтому если мы перейдем по адресу (http://localhost:8000/generator.php), мы получим что-то вроде следующего:

Dataset 1
Dataset 2
Dataset 3
Dataset 4
Dataset 5
Dataset 6
Dataset 7
Dataset 8
Dataset 9
Dataset 10
Dataset 11
Dataset 12
Dataset 13
Dataset 14
Dataset 15

Код скажет все сам за себя, и его, безусловно, не так много. Но если мы вернемся в наш код и сделаем небольшие изменения, то получим:

foreach (getRange(PHP_INT_MAX) as $range) {
    echo "Dataset {$range} 
";
}

Теперь upper range (max) с сгенерированных чисел — PHP_INT_MAX, что является наибольшим числом, которого может достичь ваша версия PHP. После этого, зайдите в браузер и обновите его. Но на этот раз вы увидите что-то другое. Генератор выдаст ошибку.

Fatal error: Allowed memory size of 209715200 bytes exhausted (tried to allocate 4104 bytes) in X:\home\localhost\www\bookflow\includes\index.php on line 637

Состоялось переполнения памяти. Возможные решения, которые приходят на ум: обратиться в php.ini и увеличить memory_limit. Зададим себе вопрос, действительно ли это будет эффективно? Хотим ли мы иметь один скрипт, который будет содержать всю память нашего сервера? Ответы — нет и нет. Это не эффективно, и мы не хотим скрипт, который будет использовать всю нашу память.

Использование генераторов

Определим ту же функцию, которую использовали выше, вызовем ее с тем же значением PHP_INT_MAX и запустим программу. Но, на этот раз, мы будем создавать функцию-генератор.

function getRange ($max = 10) {
    for ($i = 1; $i < $max; $i++) {
        yield $i;
    }
}

foreach (getRange(PHP_INT_MAX) as $range) {
    echo "Dataset {$range} 
";
}

Рассматривая функцию getRange, мы только выполняем цикл по значениям и даем исходную информацию — yield. yield похож на return, поскольку он возвращает значение из функции, но с той лишь разницей, что yield возвращает значение только тогда, когда это необходимо, и не пытается сохранить весь набор данных в памяти.

Если вы зайдете в свой браузер, вы должны увидеть данные, которые отображаются на странице.

Примечание: Генераторы могут быть использованы только в функции.

Зачем нужны генераторы?

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

Возвращаемые значения

Есть моменты, когда наши данные имеют смысл только тогда, когда они являются ключевыми. При использовании генераторов, мы можем возвращать значение, например:

function getRange ($max = 10) {
    for ($i = 1; $i < $max; $i++) { $value = $i * mt_rand(); yield $i => $value;
    }
}

Затем мы можем пойти дальше и использовать пары значений, поскольку мы могли бы сделать это с любым массивом, например:

foreach (getRange(PHP_INT_MAX) as $range => $value) {
    echo "Dataset {$range} has {$value} value
";
}

Отправка переменных к генераторам

Генераторы могут также принимать переменные. Это означает, что генераторы позволяют вводить значения в них, это может быть команда или что-то другое. Например, мы можем послать переменную к нашему генератора, чтобы остановить выполнение или изменить output. С помощью функции getRange, которая уже была приведена выше, мы можем сделать это:

function getRange ($max = 10) {
    for ($i = 1; $i < $max; $i++) {
        $injected = yield $i;

        if ($injected === 'stop') return;
    }
}

Передача значения, сделайте следующее:

$generator = getRange(PHP_INT_MAX);

foreach ($generator as $range) {
    if ($range === 10000) {
        $generator->send('stop');
    }

    echo "Dataset {$range} 
";
}

Примечание: Использование return в генераторе нарушит функцию генератора.

Не злоупотребляйте генераторами

Использование PHP_INT_MAX немного неуместно. Для меня PHP_INT_MAX это 2147483647, а именно:

Генераторы должны быть эффективными. Это не значит, что они не будут вызывать ту же проблему, которую они пытаются решить при неправильном использовании.