Что такое генераторы в 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 «Данные
«;
>
Затем в папке где у нас лежит этот файл открываем консоль и пишем следующее
php -S localhost:8000
Дальше открываем браузер и идем по следующему адресу:
Результат будет такой:
Данные 1
Данные 2
….
Данные 15
Код выше достаточно прост. Однако, давайте сделаем небольшое изменение в нем:
foreach (getRange(PHP_INT_MAX) as $range) echo «Данные
«;
>
Теперь диапазон генерируемых чисел находится в пределах от 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 «Данные
«;
>
Определяя функцию 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 «Ключ имеет значение «;
>
Отсылка значений генераторам
Генераторы также могут принимать значения. Под этим подразумевается, что генераторы позволяют нам вставлять значения, которое может представлять собой подобие команды или еще что-то. Например, мы можем отправить значение в наш генератор, которое сигнализирует о необходимости остановки исполнения или изменения выходных данных. Далее пример кода:
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 «Значение
«;
>
Отмечу, что использование инструкции return в функции-генераторе приведет к немедленному выходу из этой функции.
В заключении отмечу, что генераторы предлагают значительное улучшение производительности, которое мы не можем игнорировать. Большую часть времени нам не нужно иметь мощные сервера для выполнения нашего кода — нам просто необходимо сделать небольшой рефакторинг. И генераторы очень полезный инструмент, который мы должны использовать более часто, при этом, не злоупотребляя им.
Кстати, о генераторах я подробно рассказываю в моем курсе PHP и MySQL с Нуля до Гуру 2.0. Там есть и примеры и задания, которые помогут лучше усвоить материал.
![]()
Создано 06.02.2018 12:57:20
Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!
Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.
Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления
Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.
Порекомендуйте эту статью друзьям:
Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):
- Кнопка:
Она выглядит вот так: - Текстовая ссылка:
Она выглядит вот так: Как создать свой сайт - BB-код ссылки для форумов (например, можете поставить её в подписи):
Комментарии ( 2 ):
Silver78 07.02.2018 12:05:38
нихера не понял
romas_s 07.02.2018 17:05:06
генераторы в отличии от массивов ОЗУ не кушают и код выполняется в разы быстрее. НО генератор можно записать только 1 раз + перезаписать данные генератора нет возможности + считать данные тоже можно только 1 раз. Наведите плз пример кто шарит — как запихнуть результат выборки с базы данных в генератор, (результат запроса в базу данных).
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.
Copyright © 2010-2023 Русаков Михаил Юрьевич. Все права защищены.
Что такое генераторы и как их использовать php
(PHP 5 >= 5.5.0, PHP 7, PHP 8)
Генераторы предоставляют лёгкий способ реализации простых итераторов без использования дополнительных ресурсов или сложностей, связанных с реализацией класса, реализующего интерфейс Iterator .
Генератор позволяет вам писать код, использующий foreach для перебора набора данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти, либо потребует довольно много времени для его создания. Вместо этого, вы можете написать функцию-генератор, которая, по сути, является обычной функцией, за исключением того, что вместо возврата единственного значения, генератор может возвращать (yield) столько раз, сколько необходимо для генерации значений, позволяющих перебрать исходный набор данных.
Наглядным примером вышесказанного может послужить использование функции range() как генератора. Стандартная функция range() генерирует массив, состоящий из значений, и возвращает его, что может привести к генерации огромных массивов данных. Например, вызов range(0, 1000000) приведёт к использованию более 100 МБ оперативной памяти.
В качестве альтернативы мы можем создать генератор xrange() , который использует память только для создания объекта Iterator и сохранения текущего состояния, что потребует не больше 1 килобайта памяти.
Пример #1 Реализация range() как генератора
function xrange ( $start , $limit , $step = 1 ) if ( $start <= $limit ) if ( $step <= 0 ) throw new LogicException ( 'Шаг должен быть положительным' );
>
?php
for ( $i = $start ; $i 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 » ;
>
?>
Результат выполнения данного примера:
Нечётные однозначные числа с помощью range(): 1 3 5 7 9 Нечётные однозначные числа с помощью xrange(): 1 3 5 7 9
Объект Generator
Когда функция генератор вызывается, она вернёт объект встроенного класса Generator . Этот объект реализует интерфейс Iterator , станет однонаправленным объектом итератора и предоставит методы, с помощью которых можно управлять его состоянием, включая передачу в него и возвращения из него значений.
Что такое генераторы и как их использовать php
Генератор предоставляет функцию, которая генерирует набор значений.
Для возвращения значения из функции применяется оператор yield . Но в отличие от return оператор yield сохраняет состояние функции, позволяя ей продолжать работу с того места, когда остановилось ее выполнение.
Например, определим простейшую функцию генератора:
function generator()
Здесь функция генератора фактически возвращает только одно число 21. Тем не менее мы можем перебирать результат функции генератора в цикле как стандартный массив:
foreach(generator() as $number) < echo $number; // 21 >
Подобным образом генератор может возвращать и большее количество значений:
function generateNumbers() < for ($i = 0; $i > foreach(generateNumbers() as $number) < echo $number; // 012345 >
В данном случае функция генератора generateNumbers() с помощью цикла генерирует значения от 0 до 5 включительно. Это все равно, если бы написали:
function generateNumbers()
При переборе в цикле мы фактически перебираем результат функции как обычный массив, каждый элемент которого имеет числовой индекс, начиная с нуля:
> foreach(generateNumbers() as $index => $number) < echo "$index - $number
"; // 012345 > ?>
0 - 10 1 - 11 2 - 12 3 - 13 4 - 14 5 - 15
С помощью оператора from можно определять массив — источник данных для генератора:
function generateNumbers() < yield 1; yield from [2, 3, 4]; yield 5; >foreach(generateNumbers() as $number) < echo $number; // 12345 >
В данном случае функция generateNumbers() для генерации часть данных берет из массива [2, 3, 4] с помощью выражения yield from [2, 3, 4] .
Функция генератора, как и любая функция может принимать параметры, что позволяет настраивать поведение генератора:
function generateNumbers($start, $end) < for($i = $start; $i < $end; $i++)< yield $i; >> foreach(generateNumbers(4, 9) as $number) < echo $number; // 45678 >
Но естественно может возникнуть вопрос: а зачем нужны генераторы? Разве мы не можем с тем же успехом перебират обычный массив? Например:
$numbers = [1, 2, 3, 4, 5]; foreach($numbers as $number) < echo $number; // 12345 >
Дело в том, что при работе с массивом весь массив загружается в память. При небольших объемах проблема может быть игнорироваться. Но чем больше размер массива, соответственно тем больше издержки и потери в производительности. Именно эту проблему и призваны решить генераторы, которые извлекают только одно значение одномоментно при обращении к функции, экономя тем самым вычислительные ресурсы.
Сохранение в массив (например, с помощью iterator_to_array() )
yield from не сбрасывает ключи. Ключи, возвращённые из объекта Traversable или массива, сохранятся. Таким образом, некоторые значения, могут пересекаться по ключам с другими yield или yield from, что, при записи в массив, повлечёт за собой перезапись прежних значений.
Общий случай, когда это имеет значение, это когда iterator_to_array() возвращает массив с ключами по умолчанию. В этом случае можно получить неожиданный результат. iterator_to_array() имеет второй параметр preserve_keys , который можно установить в false , для генерации собственных ключей и игнорирования ключей, переданных из объекта Generator .
Пример #5 yield from с iterator_to_array()
function inner () yield 1 ; // ключ 0
yield 2 ; // ключ 1
yield 3 ; // ключ 2
>
function gen () yield 0 ; // ключ 0
yield from inner (); // ключи 0-2
yield 4 ; // ключ 1
>
// Задайте false вторым параметром для получения массива [0, 1, 2, 3, 4]
var_dump ( iterator_to_array ( gen ()));
?>?php
Результат выполнения данного примера:
array(3) < [0]=>int(1) [1]=> int(4) [2]=> int(3) >
Пример #6 Основы использования yield from
function count_to_ten () yield 1 ;
yield 2 ;
yield from [ 3 , 4 ];
yield from new ArrayIterator ([ 5 , 6 ]);
yield from seven_eight ();
yield 9 ;
yield 10 ;
>
?php
function seven_eight () yield 7 ;
yield from eight ();
>
function eight () yield 8 ;
>
foreach ( count_to_ten () as $num ) echo » $num » ;
>
?>
Результат выполнения данного примера:
1 2 3 4 5 6 7 8 9 10
Пример #7 yield from и возвращаемые значения
function count_to_ten () yield 1 ;
yield 2 ;
yield from [ 3 , 4 ];
yield from new ArrayIterator ([ 5 , 6 ]);
yield from seven_eight ();
return yield from nine_ten ();
>
?php
function seven_eight () yield 7 ;
yield from eight ();
>
function eight () yield 8 ;
>
function nine_ten () yield 9 ;
return 10 ;
>
$gen = count_to_ten ();
foreach ( $gen as $num ) echo » $num » ;
>
echo $gen -> getReturn ();
?>
Результат выполнения данного примера:
1 2 3 4 5 6 7 8 9 10
