1. Введение

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

В таких случаях, когда наш набор данных слишком велик или мы ожидаем, что объем данных увеличится в будущем, нам могут помочь генераторы. Генератор (generator) в PHP — это функция, которая позволяет нам перебирать данные без необходимости создания массива в памяти. В отличие от стандартной функции, которая может возвращать только одно значение, генератор может отдавать (yield) столько значений, сколько ему нужно.

Каждый раз, когда вызывается функция-генератор, в результате мы получаем итерируемый объект Generator. Например, если мы воспользуемся foreach-циклом для итерации по объекту, PHP будет вызывать функцию-генератор каждый раз, когда ему нужно будет получить значение, а затем сохраняет состояние генератора, когда генератор отдает значение, чтобы его можно было возобновить, когда потребуется следующее значение. Когда больше не остается значений, которые можно отдать, функция-генератор ведет себя так же, как если бы значения закончились в массиве.

2. Итерация по массиву vs генераторы

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

Функция будет выглядеть следующим образом:

Если мы вызовем функцию для заполнения массива значениями в диапазоне от 1 до 500, функция будет выполнена без каких-либо проблем, как показано на изображении ниже.

Вывод функции:

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

В следующем примере в качестве конца диапазона мы будем использовать PHP_INT_MAX – наибольшее целое число, которое может выдать текущая версия PHP.

После выполнения получаем ошибку, связанную с тем, что доступный объем памяти исчерпан.

Давайте попробуем тот же пример, но на этот раз для отображения необходимых значений мы будем использовать генераторы.

Когда мы выполняем этот код, мы уже не получаем ошибку, с которой столкнулись ранее, из чего следует, что у нас не заканчивается память. Когда мы отдаем (yield) значение, мы возвращаем значение в тот, когда оно необходимо, что означает, что мы не храним весь набор данных в памяти.

Существуют и другие возможные решения этой конкретной проблемы, такие как переход в php.ini и увеличение memory_limit, но эффективен ли такой подход и хотим ли мы, чтобы наш код использовал всю память сервера, – большой вопрос.

3. Полезные фичи генераторов

3.1. Получение значений по ключам

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

В качестве входных данных мы возьмем набор пользователей с разными навыками, а на выходе получим только тех пользователей, в наборе навыков которых есть PHP.

Вот что мы получим на выходе:

3.2. Отправка значений в генератор

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

Чтобы проиллюстрировать это, мы будем использовать первый пример с пользовательской функцией для вывода диапазона значений.

Результат на выходе:

3.3. Возврат значений из генератора

Как и в предыдущем примере, когда мы вводили значение в функцию-генератор, в этом примере, мы вернем значение после завершения ее выполнения.

В результате выполнения мы получим:

Если мы хотим получить первый элемент из функции-генератора, то мы не сможем сделать это так, как обычно делаем это с массивами (array [0]), мы получим ошибку при выполнении. Для этих целей в нашем распоряжении есть методы current () и next ().

А вот результат выполнения этого кода:

4. Заключение

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

В Laravel, как в одном из самых популярных PHP-фреймворков, генераторы используются для ленивой загрузки коллекций, начиная с версии 6.

Генераторы дают значительный прирост производительности с точки зрения потребления памяти с помощью ограничения использования памяти большими наборами данных. Когда мы говорим о производительности и оптимизации, мы всегда балансируем между тем, что мы можем улучшить, и стоимостью того или иного аспекта приложения. В этом контексте, когда мы используем генераторы, мы не получим ошибку переполнения памяти, но нам нужно следить за временем выполнения, необходимого для получения всех данных. Дело в том, что мы должны по максимуму использовать мощные возможности генераторов, но нам нужно рассмотреть все остальные аспекты приложения и выбрать наиболее подходящее решение для нашей проблемы.


Материал подготовлен в преддверии старта онлайн-курса "PHP Developer. Professional". В рамках этого курса недавно прошел открытый урок, посвященный PHP 8.2, на котором посмотрели на нововведения и применили их сразу на практике. Если интересно, посмотрите запись урока.

Комментарии (2)


  1. webhamster
    24.01.2023 14:18
    +1

    Я ничего не понял. Так что же такое генератор?

    В отличие от стандартной функции, которая может возвращать только одно значение, генератор может отдавать (yield) столько значений, сколько ему нужно.

    А сколько ему нужно? Может ли генератор отдавать значения, если конечное количество значений заранее неизвестно? То есть если становится известно есть ли следующий элемент только при вычислении текущего элемента?

    Что является признаком того, что генератор прекратил свою работу? Возврат return вместо yeild? Об этом ничего в статье не написано.

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

    А что означает "состояние генератора"? Это просто состояние некоего объекта Generator (фиг знает что там внутри) или состояние объекта Generator плюс полное состояние функции на том моменте, на котором она остановилась при выдаче yield? (Иначе как она будет продолжать работать?).


  1. FanatPHP
    24.01.2023 15:08

    Мужчина, мы с вами уже общались на эту тему. И вы снова, с упорством, достойным лучшего применения, продолжаете публиковать бабкины сказки вместо технических статей. Сколько можно повторять, ну не экономят генераторы память. Память экономят циклы. Если заглянуть этому генератору под капот, то мы увидим там цикл for. Который и делает всю работу. И экономит память. А генератор — это всего лишь красивая обертка.


    В этой статье не рассказывается вообще ничего нового, просто пересказываются старые байки и текст из документации.


    Пример "Получение значений по ключам" какой-то идиотский. Мы уже забыли, что генераторы "экономят память" и вбухиваем в него весь массив целиком. И дальше делаем обычную фильтрацию. Зачем здесь генератор — загадка. Почему фильтрация по ключам — это свойство генератора, а не того говнокода, который написан внутри — загадка.


    Пример про получение значений просто неграмотный. Ну как можно писать, что
    current() возвращает "первое" значение? Ну даже при минимальном знании английского будет понятно, что к первому это слово не имеет никакого отношения. А возвращает текущее значение. Которое будет первым только если генератор еще не запускали.


    В целом статья — это халтура. Я так понимаю, что весь сайт iwconnect.com — это такой ресурс для копирайтеров, статьи ни о чем, лишь бы массу нагнать. Нам не надо здесь такого на Хабре, даже если вам надо очень рекламировать курсы Отус.