Генератори

← Генератори

Ashterix

docs: English JsonRpcBundle docs, REST and MCP Bundle sections, package cards

uk/articles/from-tg/series/php-can-do/generators.md новий файл +129 −0

1 + # Генератори
2 +
3 + *Старий інструмент, який більшість досі не використовує*
4 +
5 + - **Додано у версії:** PHP 5.5
6 + - **Що дає:** функції з `yield`, які повертають елементи поступово — замість того, щоб збирати все в памʼяті й віддавати одразу.
7 +
8 + ## Реальність така
9 +
10 + Хоча генератори існують у PHP з 5.5, на співбесідах більшість розробників або не можуть пояснити, де саме вони реально економлять памʼять, або згадують їх лише теоретично — на кшталт "це щось для великих даних". А по практичним кейсам — нуль ідей.
11 +
12 + Тому давайте я накину вам трохи ідей як і де їх можна використовувати.
13 +
14 + ## Як вони працюють насправді
15 +
16 + На відміну від звичайних ітераторів чи масивів:
17 +
18 + - генератор не знає, скільки буде елементів;
19 + - не знає, чи є наступний;
20 + - не пам’ятає, чи була попередня ітерація;
21 + - однопрохідний — пройшов, і все, повторно не повернешся.
22 +
23 + А попри всі ці “обмеження” — він має одну суттєву перевагу: він може призупиняти власне виконання, що дозволяє не тримати в памʼяті всі стани, а тільки поточний.
24 +
25 + Це не “масив”, а скоріше потік — і саме тому він безцінний у правильному місці.
26 +
27 + ## Реальні кейси, де yield справді потрібен
28 +
29 + ### 1. Посторінкове читання з БД
30 +
31 + ```php
32 + function fetchUsers(PDO $db, int $limit = 500): Generator
33 + {
34 + $offset = 0;
35 + while (true) {
36 + $rows = $db->query("SELECT * FROM users LIMIT $limit OFFSET $offset")->fetchAll();
37 + if (!$rows) break;
38 + yield from $rows;
39 + $offset += $limit;
40 + }
41 + }
42 + ```
43 +
44 + Економія памʼяті колосальна — працює навіть на таблицях у сотні тисяч рядків.
45 +
46 + ### 2. Потоковий парсинг великих файлів
47 +
48 + ```php
49 + function readCsv(string $path): Generator
50 + {
51 + $h = fopen($path, 'r');
52 + while (($row = fgetcsv($h)) !== false) {
53 + yield $row;
54 + }
55 + fclose($h);
56 + }
57 + ```
58 +
59 + Читає рядок за рядком — не вантажить весь CSV в памʼять.
60 +
61 + ### 3. Потоковий запис результату в файл
62 +
63 + ```php
64 + function streamCsvRows(iterable $data): Generator
65 + {
66 + foreach ($data as $row) {
67 + yield implode(',', $row) . "\n";
68 + }
69 + }
70 +
71 + header('Content-Type: text/csv');
72 + foreach (streamCsvRows($repository->getLargeDataset()) as $line) {
73 + echo $line;
74 + flush();
75 + }
76 + ```
77 +
78 + Відправка великих звітів чи експорту — без буферизації, у реальному часі.
79 +
80 + ### 4. Поступове завантаження з API
81 +
82 + ```php
83 + function fetchPages(ApiClient $client): Generator
84 + {
85 + $page = 1;
86 + do {
87 + $data = $client->getPage($page++);
88 + yield from $data['items'];
89 + } while ($data['has_more']);
90 + }
91 + ```
92 +
93 + Ідеально для стрімінгової обробки даних без буферизації.
94 +
95 + ### 5. Обхід великої файлової структури
96 +
97 + ```php
98 + function scanDirRecursive(string $path): Generator
99 + {
100 + foreach (scandir($path) as $item) {
101 + if ($item === '.' || $item === '..') continue;
102 + $full = "$path/$item";
103 + if (is_dir($full)) {
104 + yield from scanDirRecursive($full);
105 + } else {
106 + yield $full;
107 + }
108 + }
109 + }
110 +
111 + foreach (scanDirRecursive('/var/log') as $file) {
112 + echo $file . PHP_EOL;
113 + }
114 + ```
115 +
116 + Ідеально для обходу каталогів, навіть якщо їх тисячі.
117 +
118 + ## Висновок
119 +
120 + > Генератори — це інструмент не для зручності, а для ефективності.
121 + > Вони не дають можливості перемотати чи дізнатись розмір, але саме тому й дозволяють працювати з необмеженими потоками даних.
122 +
123 + Якщо ти колись писав скрипт, який падав через *out of memory*, — `yield` твій новий друг.
124 +
125 + ← [Попередня: Стрілкові функції](/articles/from-tg/series/php-can-do/arrow-functions) · [Уся серія](/articles/from-tg/series/php-can-do) · [Наступна: DateTimeImmutable →](/articles/from-tg/series/php-can-do/datetime-immutable)
126 +
127 + ---
128 +
129 + *Вперше опубліковано в Telegram-каналі [«Створений щоб помирати»](https://t.me/php_while_true/78) 18.10.2025.*