У меня очень большой массив в PHP (5.6), сгенерированный динамически, который я хочу преобразовать в JSON. Проблема в том, что массив слишком велик, что он не вписывается в память – я получаю фатальную ошибку, когда пытаюсь ее обработать (израсходованная память). Поэтому я понял, что с использованием генераторов проблема с памятью исчезнет.
Это код, который я пробовал до сих пор (этот сокращенный пример не приводит к ошибке памяти):
<?php function arrayGenerator()// new way using generators { for ($i = 0; $i < 100; $i++) { yield $i; } } function getArray()// old way, generating and returning the full array { $array = []; for ($i = 0; $i < 100; $i++) { $array[] = $i; } return $array; } $object = [ 'id' => 'foo', 'type' => 'blah', 'data' => getArray(), 'gen' => arrayGenerator(), ]; echo json_encode($object);
Но PHP, похоже, не JSON-кодирует значения из генератора. Это результат, который я получаю из сценария previuos:
{ "id": "foo", "type": "blah", "data": [// old way - OK 0, 1, 2, 3, //... ], "gen": {}// using generator - empty object! }
Возможно ли даже JSON-кодирование массива, созданного генератором, без генерации полной последовательности, прежде чем я позвоню в json_encode
?
К сожалению, json_encode не может генерировать результат из функции генератора. Использование iterator_to_array
прежнему будет пытаться создать весь массив, что все равно вызовет проблемы с памятью.
Вам нужно будет создать свою функцию, которая будет генерировать json-строку из функции-генератора. Вот пример того, как это могло выглядеть:
function json_encode_generator(callable $generator) { $result = '['; foreach ($generator as $value) { $result .= json_encode($value) . ','; } return trim($result, ',') . ']'; }
Вместо кодирования всего массива одновременно он кодирует только один объект за раз и объединяет результаты в одну строку.
Приведенный выше пример заботится только о кодировании массива, но его можно легко расширить до рекурсивного кодирования целых объектов.
Если созданная строка по-прежнему слишком велика для размещения в памяти, то единственным оставшимся вариантом является прямое использование выходного потока. Вот как это могло выглядеть:
function json_encode_generator(callable $generator, $outputStream) { fwrite($outputStream, '['); foreach ($generator as $key => $value) { if ($key != 0) { fwrite($outputStream, ','); } fwrite($outputStream, json_encode($value)); } fwrite($outputStream, ']'); }
Как вы можете видеть, единственное отличие состоит в том, что теперь мы используем fwrite
для записи в переданный поток, а не для конкатенации строк, и нам также нужно позаботиться о концевой запятой по-другому.
Функция генератора представляет собой более компактный и эффективный способ записи итератора . Он позволяет вам определить функцию, которая будет вычислять и возвращать значения во время их циклирования :
Также в соответствии с документом из http://php.net/manual/en/language.generators.overview.php
Генераторы обеспечивают простой способ реализации простых итераторов без накладных расходов или сложности реализации класса, реализующего интерфейс Iterator.
Генератор позволяет вам писать код, который использует foreach для итерации по набору данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти или потребует значительного количества времени обработки для генерации. Вместо этого вы можете написать генераторную функцию, которая является такой же, как и для нормальной функции, за исключением того, что вместо того, чтобы возвращаться один раз, генератор может давать столько раз, сколько необходимо, чтобы обеспечить повторение итераций.
yield
? Ключевое слово yield
возвращает данные из функции генератора:
Сердцем функции генератора является ключевое слово yield. В своей простейшей форме оператор yield похож на оператор return, за исключением того, что вместо того, чтобы прекратить выполнение функции и возвращать, yield вместо этого дает значение для цикла кода над генератором и приостанавливает выполнение функции генератора.
Таким образом, в вашем случае для генерации ожидаемого результата вам нужно выполнить итерацию вывода функции arrayGenerator()
, используя цикл foreach
или iterator
прежде чем обрабатывать его до json (как было предложено @apokryfos)