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

При вождении автомобиля – скорость это далеко не все. Но в WEB все решает скорость. Чем быстрее ваше приложение, тем лучше пользовательский опыт. Хорошо, эта статья о генераторах в PHP, так почему же мы говорим о скорости? Как вы увидите вскоре, генераторы привносят большие изменения по части скорости и потреблении памяти приложением.
Что такое PHP генераторы?
Добавленные в PHP в версии 5.5, генераторы представляют собой функции, обеспечивающие простой механизм для циклической обработки данных, без необходимости создавать массив данных в памяти. Все еще не понимаете о чем речь? Тогда давайте посмотрим на PHP генераторы в действии.
Создаем файл generator_test.php со следующим содержанием:
function getRange( $max = 10 ) <
$array = [];
for( $i = 0; $i < $max; $i++ ) <
$array[] = $i;
>
return $array;
>
foreach (getRange(15) as $range) <
echo «Данные <$range><br>»;
>
Затем в папке где у нас лежит этот файл открываем консоль и пишем следующее
php -S localhost:8000
Дальше открываем браузер и идем по следующему адресу:
Результат будет такой:
Данные 1
Данные 2
….
Данные 15
Код выше достаточно прост. Однако, давайте сделаем небольшое изменение в нем:
foreach (getRange(PHP_INT_MAX) as $range) <
echo «Данные <$range><br>»;
>
Теперь диапазон генерируемых чисел находится в пределах от 0 до константы PHP_INT_MAX, которая представляет собой наибольшее целое число, которое способен представить интерпретатор PHP. После этого опять идем в браузер и обновляем страницу. Однако на этот раз, вместо обычного текста получаем сообщение о том, что превышен объем доступной памяти, вследствие чего работа скрипта была аварийно завершена.
Что за досада – у PHP закончилась память! Первое что приходит на ум – это редактировать настройку memory_limit в php.ini. Но давайте спросим себя – действительно ли это так эффективно? Неужели мы хотим, чтобы какой-то единственный скрипт занимал всю доступную память?
Используем генераторы
Давайте напишем ту же самую функцию, что и выше, вызовем ее с тем же значением PHP_INT_MAX и запустим снова. Но в этот раз мы создадим функцию-генератор.
function getRange( $max = 10 ) <
for( $i = 1; $i < $max; $i++ ) <
yield $i;
>
>
foreach (getRange(PHP_INT_MAX) as $range) <
echo «Данные <$range><br>»;
>
Определяя функцию getRange на этот раз, мы всего лишь проходим по значениям и генерируем вывод. Ключевое слово yield похоже на инструкцию return тем, что возвращает значение из функции, но единственное отличие заключается в том, что yield возвращает значение только тогда, когда это необходимо и не пытается вместить весь массив данных в память за один раз. Перейдя к браузеру, вы должны увидеть данные, отображаемые на странице. Обратите внимание на тот факт, что генераторы в PHP могут быть использованы только лишь из функции.
Зачем нужны генераторы?
Время от времени возникают такие задачи, когда нам необходимо обработать большие объемы данных (например, файлы логов), выполнить вычисления на больших выборках из базы и т.д. И мы отнюдь не хотим, чтобы эти операции занимали всю доступную память, так мы должны стараться сохранять память насколько это возможно. Данные не обязательно должны быть большими – PHP генераторы эффективны вне зависимости от размера данных. И не забывайте, что наша цель – сделать приложение быстрым и при этом таким, чтобы оно потребляло как можно меньше памяти.
Возврат ключей
Бывают случаи, когда нам необходимо возвращать не просто значение, а пару ключ-значение. При использовании генераторов, мы можем генерировать пары ключ-значение следующим образом.
function getRange( $max = 10 ) <
for( $i = 0; $i < $max; $i++ ) <
$value = $i * mt_rand();
yield $i => $value;
>
>
?>
Использовать данную функцию мы можем также как и простой массив:
foreach (getRange(PHP_INT_MAX) as $key => $value ) <
echo «Ключ <$key>имеет значение <$value>«;
>
Отсылка значений генераторам
Генераторы также могут принимать значения. Под этим подразумевается, что генераторы позволяют нам вставлять значения, которое может представлять собой подобие команды или еще что-то. Например, мы можем отправить значение в наш генератор, которое сигнализирует о необходимости остановки исполнения или изменения выходных данных. Далее пример кода:
function getRange( $max = 10 ) <
for( $i = 1; $i < $max; $i++ ) <
$inject = yield $i;
if( $inject === 'stop' ) return;
>
>
foreach( $generator as $range ) <
if($range === 10000) <
// посылаем сообщение генератору
$generator -> send('stop');
>
print «Значение <$range><br>»;
>
Отмечу, что использование инструкции return в функции-генераторе приведет к немедленному выходу из этой функции.
В заключении отмечу, что генераторы предлагают значительное улучшение производительности, которое мы не можем игнорировать. Большую часть времени нам не нужно иметь мощные сервера для выполнения нашего кода — нам просто необходимо сделать небольшой рефакторинг. И генераторы очень полезный инструмент, который мы должны использовать более часто, при этом, не злоупотребляя им.
Кстати, о генераторах я подробно рассказываю в моем курсе PHP и MySQL с Нуля до Гуру 2.0. Там есть и примеры и задания, которые помогут лучше усвоить материал.

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!
Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.
Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления
Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.
Порекомендуйте эту статью друзьям:
Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):
Что такое генераторы в php
(PHP 5 >= 5.5.0, PHP 7, PHP 8)
Генераторы предоставляют лёгкий способ реализации простых итераторов без использования дополнительных ресурсов или сложностей, связанных с реализацией класса, реализующего интерфейс Iterator .
Генератор позволяет вам писать код, использующий foreach для перебора набора данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти, либо потребует довольно много времени для его создания. Вместо этого, вы можете написать функцию-генератор, которая, по сути, является обычной функцией, за исключением того, что вместо возврата единственного значения, генератор может возвращать (yield) столько раз, сколько необходимо для генерации значений, позволяющих перебрать исходный набор данных.
Наглядным примером вышесказанного может послужить использование функции range() как генератора. Стандартная функция range() генерирует массив, состоящий из значений, и возвращает его, что может привести к генерации огромных массивов данных. Например, вызов range(0, 1000000) приведёт к использованию более 100 МБ оперативной памяти.
В качестве альтернативы мы можем создать генератор xrange() , который использует память только для создания объекта Iterator и сохранения текущего состояния, что потребует не больше 1 килобайта памяти.
Пример #1 Реализация range() как генератора
<?php
function xrange ( $start , $limit , $step = 1 ) <
if ( $start <= $limit ) <
if ( $step <= 0 ) <
throw new LogicException ( ‘Шаг должен быть положительным’ );
>
for ( $i = $start ; $i <= $limit ; $i += $step ) <
yield $i ;
>
> else <
if ( $step >= 0 ) <
throw new LogicException ( ‘Шаг должен быть отрицательным’ );
>
for ( $i = $start ; $i >= $limit ; $i += $step ) <
yield $i ;
>
>
>
/* Обратите внимание, что и range() и xrange() дадут один и тот же вывод */
echo ‘Нечётные однозначные числа с помощью range(): ‘ ;
foreach ( range ( 1 , 9 , 2 ) as $number ) <
echo » $number » ;
>
echo «\n» ;
echo ‘Нечётные однозначные числа с помощью xrange(): ‘ ;
foreach ( xrange ( 1 , 9 , 2 ) as $number ) <
echo » $number » ;
>
?>
Результат выполнения данного примера:
Объект Generator
Когда функция генератор вызывается, она вернёт объект встроенного класса Generator . Этот объект реализует интерфейс Iterator , станет однонаправленным объектом итератора и предоставит методы, с помощью которых можно управлять его состоянием, включая передачу в него и возвращения из него значений.
Generators overview
Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.
A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.
A simple example of this is to reimplement the range() function as a generator. The standard range() function has to generate an array with every value in it and return it, which can result in large arrays: for example, calling range(0, 1000000) will result in well over 100 MB of memory being used.
As an alternative, we can implement an xrange() generator, which will only ever need enough memory to create an Iterator object and track the current state of the generator internally, which turns out to be less than 1 kilobyte.
Пример #1 Implementing range() as a generator
<?php
function xrange ( $start , $limit , $step = 1 ) <
if ( $start < $limit ) <
if ( $step <= 0 ) <
throw new LogicException ( ‘Step must be +ve’ );
>
for ( $i = $start ; $i <= $limit ; $i += $step ) <
yield $i ;
>
> else <
if ( $step >= 0 ) <
throw new LogicException ( ‘Step must be -ve’ );
>
for ( $i = $start ; $i >= $limit ; $i += $step ) <
yield $i ;
>
>
>
/*
* Note that both range() and xrange() result in the same
* output below.
*/
echo ‘Single digit odd numbers from range(): ‘ ;
foreach ( range ( 1 , 9 , 2 ) as $number ) <
echo » $number » ;
>
echo «\n» ;
echo ‘Single digit odd numbers from xrange(): ‘ ;
foreach ( xrange ( 1 , 9 , 2 ) as $number ) <
echo » $number » ;
>
?>
Результат выполнения данного примера:
Generator objects
When a generator function is called for the first time, an object of the internal Generator class is returned. This object implements the Iterator interface in much the same way as a forward-only iterator object would, and provides methods that can be called to manipulate the state of the generator, including sending values to and returning values from it.
# Generators
(opens new window) object and pauses execution of the generator function.
Here is an example of the range function, written as a generator:
You can see that this function returns a Generator
(opens new window) object by inspecting the output of var_dump :
# Yielding Values
(opens new window) object can then be iterated over like an array.
The above example will output:
# Yielding Values with Keys
In addition to yielding values, you can also yield key/value pairs.
The above example will output:
# Reading a large file with a generator
One common use case for generators is reading a file from disk and iterating over its contents. Below is a class that allows you to iterate over a CSV file. The memory usage for this script is very predictable, and will not fluctuate depending on the size of the CSV file.
# Why use a generator?
Generators are useful when you need to generate a large collection to later iterate over. They’re a simpler alternative to creating a class that implements an Iterator
(opens new window) , which is often overkill.
For example, consider the below function.
All this function does is generates an array that’s filled with random numbers. To use it, we might do randomNumbers(10) , which will give us an array of 10 random numbers. What if we want to generate one million random numbers? randomNumbers(1000000) will do that for us, but at a cost of memory. One million integers stored in an array uses approximately 33 megabytes of memory.
This is due to the entire one million random numbers being generated and returned at once, rather than one at a time. Generators are an easy way to solve this issue.
# Using the send()-function to pass values to a generator
Generators are fast coded and in many cases a slim alternative to heavy iterator-implementations. With the fast implementation comes a little lack of control when a generator should stop generating or if it should generate something else. However this can be achieved with the usage of the send() function, enabling the requesting function to send parameters to the generator after every loop.
Resulting in this Output:

# Re-writing randomNumbers() using a generator
Our randomNumbers() function can be re-written to use a generator.
Using a generator, we don’t have to build an entire list of random numbers to return from the function, leading to much less memory being used.