Створений щоб помирати
165 subscribers
30 photos
9 videos
43 links
Канал про PHP, розробку, проєктування і нескінченне навчання в циклі життя.
Download Telegram
Перелік хештегів для зручного пошуку постів

📚 Тематика
#php
#architecture
#symfony
#composer
#docker
#sql
#testing
#designpatterns
#solid
#oop
#ddd
#jsonrpc
#microservices
#documentations
#algorithm

🛠 Практика
#examples — корисне
#tools — інструменти, утиліти
#tips — короткі поради
#mistakes — типові граблі
#refactor — приклади рефакторингу
#debug — про дебаг, логування, профілювання
#phpstorm — найкраща IDE
#ai — ШІ помічник

👨‍🏫 Освіта / досвід
#videoarchive — архівне відео з (вебінарів)
#course — матеріали з курсів
#question — питання до аудиторії
#talks — думки й міркування
#4students — для випускників

😄 Гумор і факапи
#game — інтерактивна гра
#rants — бурчання з досвідом
#wtf — код або ситуації, від яких немає слів
#humor — ІТ гумор
#quote — цитати від студентів і колег

Інше
#longread
#holywar
#rating
#career
#work
#series — серія статей

UPD:
В коментарях під цим постом можна писати запити на теми, що вас цікавлять
🔥3👍2
#course #php #longread

match !== switch

Вже відносно давно — починаючи з PHP 8.0 (а це вже 1,5 роки тому 😳) — зʼявився новий інструмент: match. Але практика співбесід (як джунів, так і мідлів) показує, що багато хто, в кращому випадку, просто знає про нього, але не використовує у повсякденному коді. Основна причина — поверхневе розуміння: існує думка, що це просто ще один синтаксичний цукор над switch чи if/else.
❗️Але це не так.

Сьогодні я спробую пояснити, чому match — це окремий інструмент із своїми перевагами і призначенням.

1️⃣ match повертає значення 🔄
match — це вираз, а не інструкція. Він завжди повертає результат, який потрібно привласнити змінній:
$taxRate = match($countryCode) {
'UA' => 0.20,
'PL' => 0.23,
'DE' => 0.19,
default => 0.0,
};

switch та if/else, на відміну від цього, нічого не повертають. Якщо ви хочете отримати результат — доводиться оголошувати змінну перед умовою, потім в кожному кейсі встановлювати їй значення.

2️⃣ Обовʼязкове покриття всіх кейсів ✅
match зобовʼязує розробника обробити всі можливі варіанти або вказати default. Якщо не вказано відповідність — буде UnhandledMatchError. Це значно знижує ризики неочевидних помилок.
$action = match($input) {
'start' => 'Запуск',
'stop' => 'Зупинка',
default => 'Пауза',
};

У switch та if/else будь-який відсутній case просто проігнорується.

3️⃣ Завжди суворе порівняння (===), а не вільне (==) 🧠
У match значення порівнюються суворо — без приведення типів. Це ще один реверанс php в бік суворої типізації і плюс до безпеки та передбачуваності:
match(0) {
false => 'false', // ❌ UnhandledMatchError
0 => 'zero', // ✅ буде виконано
};

switch (0) {
case false:
echo 'false'; // ✅ буде виведено
break;
case 0:
echo 'zero'; // ❌ сюди не дійде
break;
}

☝️ switch у цьому випадку вибере false як відповідність до 0, що може призвести до неочікуваної поведінки.

4️⃣ Немає fallthrough і break 🧱
У switch завжди є ризик забути break, і тоді код "провалиться" в наступний кейс. Це класичне джерело помилок. У match кожен кейс — це окреме правило, і немає поняття break чи переходу до наступного.

Хто не знає про що мова, просто виконайте цей код:
$value = 2;
switch ($value) {
case 1:
echo 'one';
case 2:
echo 'two';
case 3:
echo 'three';
}


5️⃣ Виховує декларативність ✨
match заохочує до декларативного стилю — без зайвої логіки в тілі конструкції. Він не дозволяє писати довгі блоки коду в кожному кейсі, лише вирази. Це змушує структурувати код краще:
return match(true) {
$x > 0 => 'позитивне',
$x < 0 => 'негативне',
default => 'нуль',
};


6️⃣ Продуктивність: O(1) доступ по значенню ⚡️
match працює аналогічно хеш-мапі: якщо йдеться про скалярні значення (int, float, string, bool), він здійснює пошук у константний час — по складності алгоритму O(1). Це дає велику перевагу над switch, де кожен case порівнюється послідовно (складність алгоритму O(n)):
// match — O(1):
$color = match($code) {
'R' => 'Red',
'G' => 'Green',
'B' => 'Blue',
default => 'Unknown',
};

// switch — O(n):
switch ($code) {
case 'R': $color = 'Red'; break;
case 'G': $color = 'Green'; break;
case 'B': $color = 'Blue'; break;
default: $color = 'Unknown';
}

———
Висновки 🎯
match — це не альтернатива switch чи if/else, а окремий інструмент для ситуацій, де важлива чіткість та суворість, тобто для всіх ситуацій з умовами

Він допомагає уникати класичних помилок з switch і заохочує до чистішого, передбачуваного коду

Він швидкий: прості значення шукаються у константний час.


❗️Якщо ти ще не використовуєш match у повсякденній роботі, саме час спробувати його у своїй наступній задачі.
Як кажуть: "Відчуй різницю" 😉
🔥12👌4👍1
#course #php #php8_5 #talks #4students

А хіба current() та end() вже не справляються❓

На одній з персональних консультацій ми з одним з підписників обговорювали статтю про нововведення в PHP 8.5, зокрема появу функцій array_first() та array_last().
У нього виникло питання:
“А навіщо це потрібно, якщо є current() і end()?”

І раптом я подумав, що це питання може виникнути не тільки в нього, бо може здатися, що current() та end() роблять те саме.

Тому я вирішив написати короткий пост, щоб розкласти все по поличках 👇

Функції current() справді повертає перший елемент, а end() останній елемент масиву, але тільки після того, як зрушують внутрішній вказівник масиву. Це означає, що:
$array = ['a', 'b', 'c', 'd'];

echo current($array); // 'a'

echo next($array); // 'b'
echo current($array); // 'b', а не 'a'

echo end($array); // 'd'
echo current($array); // 'd' а не 'a'

Тобто функція current() повертає не перший елемент масиву, а той, на який вказує вказівник, а end() останній елемент масиву, але при цьому переставляє на нього внутрішній вказівник масиву, що в свою чергу може привезти до непередбачуванних наслідків.
Концептуальний приклад:
Ми хочемо вивести всі елементи масиву, якщо останній елемент не "d"
$array = ['a', 'b', 'c'];

if (end($array) !== 'd') { // але вказівник вже перемістився на 'c'
while ($item = current($array)) {
echo $item . PHP_EOL;
next($array);
}
}

Але на екран виведеться тильки 'c'

✅ array_first() та array_last() — декларативні та передбачувані
Нові функції будуть завжди повертати перше або останнє значення без побічних ефектів.
Вони не змінюють стан вказівника і не залежать від нього:
$array = ['a', 'b', 'c'];

array_first($array); // 'a'
array_last($array); // 'c'

// Незалежно від вказівника
next($array);
array_first($array); // все ще 'a'


📌 PHP продовжує розвиватися в бік декларативного і безпечного коду, і ці нові функції — це не "зайве", а ще один крок до меншої кількості багів.

Якщо помітили у себе в коді current() чи end() — можливо, час подивитись на них під новим кутом.
❤5👍4
#course #php #talks #4students #tips #tools

🧲 Один з інструментів DRY або єдиний дозволений copy/paste

Багато технічних співбесід показують, що розробники або не використовують трейти зовсім, або роблять це без розуміння практичного сенсу. Часто їх просто бояться — мовляв, поведінка трейту може бути неочевидною або ж спричинити баги, які складно відслідкувати.

Але суть в тому, що трейти — це всього лише інструмент, який треба застосовувати там, де від нього буде найбільше користі.

І найчастіше це не сервісні класи (хоча є ситуації коли і тут вони будуть доречні), а обʼєктні структури: сутності, DTO, VO, event'и тощо. Саме там трейти — ідеальний інструмент для роздачі типової поведінки без зайвої ієрархії і порушення DRY.

Гарний приклад — бібліотека knplabs/doctrine-behaviors. своїми інтерфейсами вона надає вашим сутностям типову поведінку (наприклад, Timestampable, SoftDeletable, Translatable тощо), але замість того, щоб реалізовувати інтерфейси в кожному класі, ви просто додаєте реалізацію — підключаючи відповідний трейт. Ніяких милиць у вигляді абстрактних батьківських класів, які «надають поведінку», тільки чистий код, лаконічний use TraitName і ніякого copy/paste

Хоча справедливості заради варто визнати: copy/paste все одно відбувається, просто не в коді, а на рівні інтерпретатора.

🎯 Висновок

Трейти — це спосіб дотримуватися DRY і не лізти в спадкування там, де воно недоречне. Це дуже потужний інструмент якщо використовувати його правильно і там де треба.


PS:
🧠 Порада: як зробити, щоб трейт гарантовано працював в будь-якому класі без помилок?

❗️ Ніколи не звертайтесь всередині трейту до властивостей або методів класу, в який трейт підключено. Трейт не знає, куди його вставлять — і тому контекст будь-якого трейту має бути самообмеженим. Це головна умова, щоб ваш трейт працював стабільно і не викликав помилки.


Погано:
trait BadTrait {
public function generateUniqueId(): void
{
$this->id = uniqid();
}
}


Добре:
trait GoodTrait {
protected string $id;

public function generateUniqueId(): void
{
$this->id = uniqid();
}
}
👍8💩1
#course #php #talks #4students #tips

🧮 Який в тебе масив — асоціативний чи індексований?

В PHP масив — це універсальний інструмент, який не вимагає від тебе визначати розмір, тип елементів чи навіть структуру ключів. Це з одного боку ніби і зручно, але з іншого — є проблемою.
Більшість мов програмування виділяють масиви (indexed arrays) і словники/мапи (associative arrays), а в PHP це одна й та ж сама структура, будь-який масив може стати асоціативним або нумерованим в залежності від ключів, які ти використовуєш:
$a = [1, 2, 3, 4, 5];  // індексований
$b = ['foo' => 6]; // асоціативний
$c = [
...$a,
...$b
]; // змішаний

Часто виникає потреба по-різному обробляти масиви в залежності від їх типу. Але PHP не дає такої базової можливості, бо він взагалі не розрізняє індексовані та асоціативні масиви, про що прямо написано в документації.

Можна подумати, що array_is_list може допомогти зʼясувати хто є хто
array_is_list($a); // true  [0=>1, 1=>2, 2=>3, 3=>4, 4=>5]
array_is_list($b); // false [foo=>6]
array_is_list($c); // false [0=>1, 1=>2, 2=>3, 3=>4, 4=>5, foo=>6]

На перший погляд все добре, але ні.

Розширемо наш приклад, витягнемо з масиву $a тільки непарні числа
$a2 = array_filter($a, fn($x) => $x % 2 !== 0); // 1,3,5
array_is_list($a2); // false бо 0=>1, 2=>3, 4=>5

🙄 Що сталося з масивом? Невже він став асоціативним?

Ні. Просто array_filter зберігає оригінальні ключі. І хоч це все ще числові ключі, але через “дірки” в індексації array_is_list повертає false.
Тобто, PHP не ставить знак дорівнює між "нумерованим" та "індексованим" типами масивів.

Ще одна неприємність яка може вас очікувати: рядкові ключі — не гарантують “асоціативність”

Багато хто вважає, що якщо в масиві ключ рядковий — все, це асоціативний масив. Але якщо ключ виглядає як число, PHP конвертує його в int. Наприклад, штрихкоди товарів:
$products = [
"0123456789052" => "Товар",
"8018075001428" => "Ще товар"
];
print_r(array_keys($array));
//Array
//(
// [0] => "0123456789052"
// [1] => 8018075001428 // -- рядок став числом
//)

Тому на ключі типу “рядок чи число” теж покладатися не можна.

Висновок:
Я бачив багато коду, де масив використовують як list, а потім раптово додають рядковий ключ — і все, data integrity накрилась. Якщо твоя логіка дійсно залежить від того, який у тебе масив — перевіряй не array_is_list, а реально структуру ключів (через array_keys, is_int, is_string і т.д.). А ще краще — не лінуйся типізувати і перевіряти дані на вході, навіть якщо це PHP.


Чи стикаєтеся ви в роботі з такими складностями і як з них виходите?
👍9🗿1
#symfony #php #architecture #videoarchive

Symfony 🆚 Laravel

Знайшов і опублікував для вас запис вебінару, який я проводив в травні минулого року
В самому вебінарі не тільки порівняння вказаних фреймворків, а ще й зробив невеличкий огляд на історію розвитку фреймворків в PHP, саме порівняння стартує з 34:40

👍 Ставте лайки або дизлайки, підписуйтесь на канал і робіть все інше, що треба, щоб на YouTube ставало більше україномовного контенту

Приємного перегляду!
Альтернативні думки в коментарях до відео або цього посту вітаються
👍13
#️⃣ #php #longread

💡 Майбутнє PHP: погляд на те, що принесе PHP 9

PHP — живіший, ніж будь-коли 😎
І хоча хтось досі вважає його “динозавром”, мова продовжує еволюціонувати. PHP 9.0 може стати тим самим моментом, коли “магія” поступиться місцем чистоті, а неочікувані трюки — передбачуваності.

🕒 Офіційної дати коли чекати PHP 9 ще немає, але RFC-дискусії вже киплять.
Після 8.5 (і можливо 8.6) ми отримаємо більш суворий, логічний і, що важливо, стабільний PHP.
Тож якщо ти ще не стежиш за RFC — то я поділюся з тобою цією інформацією 👀

1️⃣ Оператори ++ / -- стануть чеснішими
Тепер ніяких дивних перетворень рядків у цифри.
$a = 'a9';
$a++;
// PHP < 9.0 -> 'b0'
// PHP 9.0 -> TypeError

🧩 Менше “магії”

2️⃣ unserialize() кидатиме винятки
try {
$obj = unserialize($data);
} catch (UnserializationFailedException $e) {
// handle
}

🧠 Виключення замість warning

3️⃣ Менше перевизначень типив
$arr = false;
$arr[] = 2;
// PHP < 9.0 -> [2]
// PHP 9.0 -> Error

🙅‍♂️ Тепер буде помилка, а не постріл в коліно

4️⃣ Інтерполяція рядків — без зайвої магії
$name = "Alex";
echo "Hello $name"; // ✅
echo "Hello {$name}"; // ✅
echo "Hello ${name}"; // ❌ Deprecation

✨ Приведення до єдиного стандарту

5️⃣ Попередження == fatal error
Невизначені змінні, властивості чи звернення до null більше не проходитимуть "якось"
echo $undefinedVar;   
// PHP < 9.0 -> Попередження
// PHP 9.0 -> Фатальна помилка

🧱 PHP буде вимогливішим до якості коду — і це добре.

6️⃣ Прибирання старих “речей”
Старі конструктори, застарілі функції, нестандартна логіка — все це піде в архів 📦
Менше сміття, менше двозначностей.

🚀 Підсумок
PHP 9.0 — має кардинально змінити підхід до написання коду та його культури.
Менше вседозволеності, більше контролю.

Якщо твій код вже чистий, суворо типізований і без магії — тобі нема чого боятися 💪
🔥11👍6
#php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 1/10
10 потужних інструментів, які тобі слід використовувати 🧠

Кожен реліз PHP приносить нові можливості, що роблять код коротшим, розумнішим і приємнішим у підтримці.
Але, як показує досвід численних співбесід і консультацій, більшість із них проходять повз увагу навіть досвідчених розробників.

Ця серія — для тих, хто, як і я, прагне по-справжньому розкрити потенціал PHP і кайфувати від розробки 💥

Ми розберемо 10 фіч, які:
🔹 доступні з PHP 5.5, але досі поза увагою
🔹 реально спрощують код
🔹 показують, наскільки PHP універсальний
——————

Перша стаття серії

⚙️ Property Promotion

Менше коду — легше читати 💡

📦 Додано у версії: PHP 8.0
💡 Що дає: можливість оголошувати властивості класу прямо в конструкторі — без дублювання й шаблонного коду.

🚀 Чому це крутіше за старий підхід

— не потрібно окремо описувати властивості та привласнювати їх значення у конструкторі;
— менше шуму — одразу видно структуру класу;
— ідеально підходить для DTO, запитів, VO, команд, конфігів тощо.
🧠 Фактично це "data class" по-PHPшному — коротко, без додаткових бібліотек чи автогенерації коду.

🧱 Реальний приклад застосування
// ❌ Раніше
readonly class Product {
public string $sku;
public string $name;
public float $price;

public function __construct(string $sku, string $name, float $price) {
$this->sku = $sku;
$this->name = $name;
$this->price = $price;
}
}

// ✅ Тепер
readonly class Product {
public function __construct(
public string $sku,
public string $name,
public float $price,
) {}
}

📌 У коді це зменшує бойлерплейт майже втричі, а клас стає самоочевидним.

🧩 Де це реально корисно
— DTO у запитах/відповідях API;
— командні об’єкти в CQRS;
— значення, які описують одну сутність без поведінки (Value Object);
— Symfony-форми, коли треба швидко пробросити дані.
class CreateUserCommand {
public function __construct(
public string $email,
public string $password,
public bool $isAdmin = false,
) {}
}


🧭 Висновок

✨ Property Promotion — не просто синтаксичний цукор, це офіційний перехід до декларативного стилю.
Код виглядає лаконічніше, без зайвих рядків.

———
Анонімні класи ➡️
👍9❤1🔥1
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 2/10
🧩 Анонімні класи
Давня фіча, яку безліч розробників досі ігнорують 🤷‍♂️

📦 Додано у версії: PHP 7.0
💡 Що дає: можливість створювати клас “на льоту” — без імені, без окремого файлу, просто у місці, де він реально потрібен.

🧠 Чому про це варто згадати

Попри те, що анонімні класи з’явилися ще у PHP 7, спілкування з розробниками показує приблизно таку статистику:
🔹9 з 10 не бачать для них “практичного сенсу”,
🔹7 з 10 — ніколи не пробували створити new class {} навіть у петпроєктах або чернетках.
А дарма — це інструмент, який ідеально підходить для тимчасових об’єктів, адаптерів, обгорток і стратегій.

⚙️ Коли анонімні класи реально зручні
— коли потрібен mock обʼєкт для тестів;
— коли треба підмінити поведінку об’єкта лише тут і зараз;
— коли не хочеться створювати окремий клас заради дрібної логіки;
— коли потрібно “локально” адаптувати чи розширити поведінку;
— коли потрібен короткий об’єкт із власним станом або кількома методами.

🧱 Реальний приклад застосування
Не буду наводити приклад створення mockів для тестів, цих прикладів повно в інтернеті, натомість поділюся більш практичним застосуванням анонімних класів.

🔹 Тимчасовий адаптер
$adapter = new class($apiClient) implements PaymentGateway 
{
public function __construct(private ApiClient $api) {}

public function pay(float $amount): bool {
return $this->api->request('/pay', ['sum' => $amount]);
}
};

$adapter->pay(9.99);

✅ Пара рядків і є готовий адаптер без створення файлу.

🔹 Локальна стратегія або callback-об’єкт
$transformer = new class 
{
public array $alreadyTransform = [];
public function transform(int $v): int
{
return $this->alreadyTransform[$v] ??= $v*10;
}
};

$result = array_map(
$transformer->transform(...),
[1, 2, 3, 2, 1, 2, 3]
);
var_dump($result); //[10, 20, 30, 20, 10, 20, 30]

✅ Callable з методом і власним станом.
Результат отримаємо для сімох елементів, а процес запуститься лише тричі

🔹 Тимчасовий listener для події
$dispatcher->addListener('user.registered', new class {
public function __invoke(User $user): void {
// логування, повідомлення, email — все що завгодно
}
});

✅ Одноразовий слухач — і не треба роздувати кількість файлів в проєкті

🔹 DTO “на льоту”
return new class($user, $stats) {
public function __construct(
public User $user,
public array $stats
) {}

public function total(): int {
return array_sum($this->stats);
}
};

✅ Коли є зайвим створювати окрему структуру для разового результату.

🧭 Висновок
Анонімні класи — це старий, але дуже недооцінений інструмент.
Вони економлять час, зменшують кількість файлів і дозволяють тримати тимчасову логіку саме там, де вона потрібна.


🔥 Якщо ти теж ще не пробував new class {}, зроби це сьогодні. Можливо, відкриєш для себе новий улюблений трюк PHP
———
⬅️ Property Promotion | Стрілкові функції ➡️
👍4❤2
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 3/10
⚡️ Стрілкові функції (Arrow Functions)
Як анонімка, тільки стрункіше

📦 Додано у версії: PHP 7.4
💡 Що дає: компактний синтаксис для коротких функцій, які автоматично бачать змінні з оточення та завжди повертають результат.

🧠 Особливості, які треба знати
— завжди повертають значення
— можуть містити лише один вираз — без {} і багатооператорних блоків;
— автоматично успадковують змінні з оточення, без use($x);
— читаються як вираз, а не як інструкція.

⚙️ Чому це зручно
— менше шаблонного коду;
— виразність у callback-ах (map, filter, reduce);
— краще читається, особливо в колекціях і стрімах;
— підходить для будь-якого місця, де потрібен мінімалістичний обробник.

🧱 Реальний приклад застосування

🔹 Перетворення масиву
$prices = [100, 200, 300];
$tax = 0.2;

// ❌ Звичайна анонімна функція
$withTax = array_map(function (int $p) use ($tax) {
return $p * (1 + $tax);
}, $prices);

// ✅ Стрілкова функція
$withTax = array_map(fn(int $p) => $p * (1 + $tax), $prices);


🔹 Фільтрація елементів
$activeUsers = array_filter($users, fn(User $u) => $u->active);


🔹 Підрахунок значення
$total = array_reduce($orders, fn(int $sum, Order $o) => $sum + $o->amount, 0);


🔹 Простий калькулятор з доступом до зовнішньої змінної
$multiplier = 3;
$calc = fn(int $v) => $v * $multiplier;

echo $calc(10); // 30


🧭 Висновок
Стрілкові функції — це лаконічна форма анонімок, створена для чистих, коротких виразів у прямолінійній логіці

🔥 Якщо тобі потрібен в коді function ($x) use ($y) { return …; } — просто заміни на fn() і відчуй різницю
———
⬅️ Анонімні класи | Генератори ➡️
👍5
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 4/10
🔄 Генератори
🧠 Старий інструмент, який більшість досі не використовує

📦 Додано у версії: PHP 5.5
💡 Що дає: функції з yield, які повертають елементи поступово — замість того, щоб збирати все в памʼяті й віддавати одразу.

⚙️ Реальність така
Хоча генератори існують у PHP з 5.5, на співбесідах більшість розробників або не можуть пояснити, де саме вони реально економлять памʼять, або згадують їх лише теоретично — на кшталт "це щось для великих даних". А по практичним кейсам — нуль ідей.

Тому давайте я накину вам трохи ідей як і де їх можна використовувати.

🧩 Як вони працюють насправді
На відміну від звичайних ітераторів чи масивів:
— генератор не знає, скільки буде елементів;
— не знає, чи є наступний;
— не пам’ятає, чи була попередня ітерація;
— однопрохідний — пройшов, і все, повторно не повернешся.

А попри всі ці “обмеження” — він має одну суттєву перевагу:⚡️ він може призупиняти власне виконання, що дозволяє не тримати в памʼяті всі стани, а тільки поточний.
Це не “масив”, а скоріше потік — і саме тому він безцінний у правильному місці.

🧱 Реальні кейси, де yield справді потрібен

🔹 1. Посторінкове читання з БД
function fetchUsers(PDO $db, int $limit = 500): Generator 
{
$offset = 0;
while (true) {
$rows = $db->query("SELECT * FROM users LIMIT $limit OFFSET $offset")->fetchAll();
if (!$rows) break;
yield from $rows;
$offset += $limit;
}
}

🔸 економія памʼяті колосальна — працює навіть на таблицях у сотні тисяч рядків.

🔹 2. Потоковий парсинг великих файлів
function readCsv(string $path): Generator 
{
$h = fopen($path, 'r');
while (($row = fgetcsv($h)) !== false) {
yield $row;
}
fclose($h);
}

💡 читає рядок за рядком — не вантажить весь CSV в памʼять

🔹 3. Потоковий запис результату в файл
function streamCsvRows(iterable $data): Generator 
{
foreach ($data as $row) {
yield implode(',', $row) . "\n";
}
}

header('Content-Type: text/csv');
foreach (streamCsvRows($repository->getLargeDataset()) as $line) {
echo $line;
flush();
}

⚙️ відправка великих звітів чи експорту — без буферизації, у реальному часі.

🔹 4. Поступове завантаження з API
function fetchPages(ApiClient $client): Generator 
{
$page = 1;
do {
$data = $client->getPage($page++);
yield from $data['items'];
} while ($data['has_more']);
}

⚡️ ідеально для стрімінгової обробки даних без буферизації

🔹 5. Обхід великої файлової структури
function scanDirRecursive(string $path): Generator 
{
foreach (scandir($path) as $item) {
if ($item === '.' || $item === '..') continue;
$full = "$path/$item";
if (is_dir($full)) {
yield from scanDirRecursive($full);
} else {
yield $full;
}
}
}

foreach (scanDirRecursive('/var/log') as $file) {
echo $file . PHP_EOL;
}

📁 ідеально для обходу каталогів, навіть якщо їх тисячі

🧭 Висновок

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

🔥 Якщо ти колись писав скрипт, який падав через out of memory, — yield твій новий друг.
———
⬅️ Стрілкові функції | DateTimeImmutable ➡️
👍6👏2
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 5/10
⏳ DateTimeImmutable
🧊 Коли час змінюється, а обʼєкти ні

📦 Додано у версії: PHP 5.5
💡 Що дає: клас, який поводиться як DateTime, але не змінюється при модифікації, а повертає клонований обʼєкт.

🧠 В чому суть
У PHP якщо ми зробимо зміни часу у обʼєкта DateTime, він змінився всюди, де передавався, бо обʼєкти в PHP завжди передаються по посиланню, і така поведінка може призвести до неочікуваної поведінки.
DateTimeImmutable вирішує цю проблему — будь-яка дія над датою повертає новий екземпляр, а не мутує старий.

⚙️ Коли це справді має сенс
— у DTO / Value Object — щоб час гарантовано не змінювався після передачі;
— у планувальниках, розкладах, звітах — коли ти рахуєш багато дат від базової;
— у бізнес-логіці — коли з однієї дати обчислюється інша (початок/кінець періоду, дедлайн, акція, тощо).

⚙️ Проблема, яку можна не помітити
У продакшн-коді DateTime часто передають між сервісами, DTO або кэшують у контейнерах.
І тут трапляється типова історія:
class Campaign {
public function __construct(
public string $name,
public DateTime $startsAt,
public DateTime $endsAt,
) {}
}

$base = new DateTime('2025-10-01');
$campaign = new Campaign(
name: 'Autumn Sale',
startsAt: $base,
endsAt: $base->modify('+7 days')
);

echo $campaign->startsAt->format('Y-m-d'); // 2025-10-08 ❌
echo $campaign->endsAt->format('Y-m-d'); // 2025-10-08

Ми очікували, що початок буде 1 жовтня, але $base мутував під час modify, як наслідок — початок і кінець однакові.
Той самий код, але з DateTimeImmutable працює корректно
$base = new DateTimeImmutable('2025-10-01');
$campaign = new Campaign(
name: 'Autumn Sale',
startsAt: $base,
endsAt: $base->modify('+7 days')
);

echo $campaign->startsAt->format('Y-m-d'); // 2025-10-01 ✅
echo $campaign->endsAt->format('Y-m-d'); // 2025-10-08


🧱 Додатковий приклад
class Period {
public function __construct(
public DateTimeImmutable $from,
public DateTimeImmutable $to
) {}

public function days(): int {
return $this->to->diff($this->from)->days;
}
}

$period = new Period(
from: $from = new DateTimeImmutable('2025-06-01'),
to: $from->modify('+10 days'),
);

echo $period->days(); // 10

Жодних несподіваних мутацій — об’єкт можна безпечно кешувати чи передавати далі.

🧭 Висновок
DateTimeImmutable — це безпечний вибір для роботи з логікою часу.
Він виключає випадкові зміни дат, коли одна змінна призводить до неочікуваних результатів.

🔥 Якщо твоя дата здатна змінитись — вона рано чи пізно зламає щось у звіті, логіці або фінансах. Immutable цього не дозволить.
———
⬅️ Генератори | Іменовані аргументи ➡️
👍7🤝2
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 6/10
🧭 Іменовані аргументи
📬 Послідовність більше не має значення

📦 Додано у версії: PHP 8.0
💡 Що дає: можливість передавати аргументи в функції і методи, вказуючи їх імена, а не лише значення.

🧠 В чому суть
До PHP 8 доводилось або пам’ятати точну послідовність аргументів, або постійно підглядати в сігнатуру методу щоб не помилитися який параметр йде за яким. Або в разі, якщо потрібно передати останній з кількох необовʼязкових параметрів, доводилося передавати всі.
З іменованими аргументами можна передавати тільки потрібні параметри — за іменем та ще й в будь-якому порядку.

⚙️ Переваги
— код стає більш самодокументованим — видно, що означає кожен аргумент;
— не треба передавати кіпу непотрібних параметрів лише щоб змінити останній;
— менше ризику переплутати порядок;
— легше рефакторити код — старі виклики не ламаються, якщо змінено послідовність параметрів.

🧱 Реальні приклади
function subscribe(
string $email,
?string $name = null,
?string $language = 'uk',
bool $doubleOptIn = true,
?string $source = null,
?string $tag = null,
bool $sendWelcomeLetter = true,
)
{ /* ... */ }

// до 8.0 треба передати всі, навіть непотрібні ❌
subscribe('alex@example.com', null, 'uk', true, null, null, false);

// після 8.0 можна обмежитися потрібними ✅
createReport('alex@example.com', sendWelcomeLetter: false);

// або навіть передати аргументи в довільному порядку
createReport('alex@example.com', sendWelcomeLetter: false, tag: 'promo2025');

✅ Решта параметрів беруться з дефолтів, і код читається як специфікація виклику методу.

🔹 Приклад із фреймворку (Symfony)
protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse 
{ /* ... */ }

//....
return $this->redirectToRoute(
route: 'user.profile',
status: 301,
parameters: ['id' => $user->getId()]
);

Порядок аргументів не має значення, головне — зрозуміло, що ти задаєш.
⚠️ Варто зазначити, що не треба без необхідності змінювати порядок аргументів лише тому, що PHP це дозволяє. Якщо бачиш в коді такі місця — краще відрефактор їх і поверни природну послідовність параметрів.
IDE, наприклад PhpStorm, зазвичай підсвічує такі виклики не дарма — це підказка, що час навести лад.


🔹 Виклик API або SDK
$client->sendMessage(
to: '380501234567',
text: 'Привіт 👋',
channel: ChannelsEnum::TELEGRAM,
);

Кожен аргумент одразу видно без коментарів і документації.

🧭 Висновок
Іменовані аргументи — це конфігураційний стиль виклику функцій, який робить код читабельним і безпечним для змін.
Більше не потрібно прокидати кіпу null, щоб дістатися до потрібного параметра.

🔥 Якщо функція має більше трьох параметрів — ти знаєш що робити.
———
⬅️ DateTimeImmutable | Enums ➡️
👍5🔥1
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 7/10
🧱 Enums
🎯 Тилькі цільові значення

📦 Додано у версії: PHP 8.1
💡 Що дає: нативний механізм для створення перелічуваних типів — із чіткими, типізованими значеннями замість "магічних рядків та чисел" і нескінченних const.

❤️ Трохи особистого
Це один із моїх найулюбленіших інструментів у PHP. З моменту їх появи я взагалі не уявляю розробки без enum’ів — вони зменшують кількість помилок, роблять код читабельним і гарантують типобезпеку.
Але спілкування з розробниками показує, що далеко не всі розділяють цей захват. Тому хочу поділитися своєю любов’ю до них і пояснити, чому це не просто "список значень", а справжній крок вперед для PHP.

🧠 Типи enum’ів
PHP має два різновиди enum’ів:
1️⃣ Backed Enum — із базовим значенням (string або int)
Кожен case має прив’язане значення, це дозволяє порівнювати його з іншими значеннями, серіалізувати, зберігати його в БД, та при цьому бути впевненним, що значення завжди буде одним з дозволених в переліченні.
enum Status: string 
{
case DRAFT = 'draft';
case ACTIVE = 'active';
case ARCHIVED = 'archived';
}

📦 Для чого:
— зручно в статусах замовлень, документів, платежів, типів повідомлень, типів працівників, тощо;
— легко зберігати в базі як звичайний рядок;
— можна отримати enum через Status::from('draft') або безпечно — tryFrom('draft').

2️⃣ Pure Enum — без бекінгу
Просто набір унікальних case без значень. Використовується тоді, коли значення не потрібне —
важливий сам факт "стану" або "режиму".
enum Direction 
{
case NORTH;
case SOUTH;
case EAST;
case WEST;
}

📦 Для чого:
— для внутрішніх логічних станів або режимів роботи;
— коли не треба нічого зберігати у БД;
— коли досить просто перевірити if ($direction === Direction::NORTH).

🧩 Більше, ніж просто перелік
Більшість вважає, що enum — це просто заміна const. Насправді це класоподібна конструкція, у якій можна (і треба!) додавати методи, поведінку, статичні методи та перетворення значень.

⚙️ Приклад enum із методами
enum Status: string 
{
case DRAFT = 'draft';
case ACTIVE = 'active';
case ARCHIVED = 'archived';

// 🔹 Нестатичний метод — для конкретного значення
public function label(): string
{
return match($this) {
self::DRAFT => 'Чернетка',
self::ACTIVE => 'Активна',
self::ARCHIVED => 'Архівована',
};
}

// 🔹 Статичний метод — спільний для всіх
public static function visible(): array
{
return [self::DRAFT, self::ACTIVE];
}
}

echo Status::ACTIVE->label(); // Активна
print_r(Status::visible()); // [Status::DRAFT, Status::ACTIVE]


🧱 Інший приклад
Статуси документів, каналів, ролей
enum Channel: string 
{
case EMAIL = 'email';
case SMS = 'sms';
case TELEGRAM = 'telegram';

public function icon(): string
{
return match($this) {
self::EMAIL => '✉️',
self::SMS => '📱',
self::TELEGRAM => '💬',
};
}

public function getSenderFQCN(): string
{
return match($this) {
self::EMAIL => EmailSender::class,
self::SMS => SMSSender::class,
self::TELEGRAM => TelegramSender::class,
};
}

public static function fromString(string $value): ?self
{
return self::tryFrom($value);
}
}

🧭 Висновок
Enum’и — це одне із найвдаліших оновлень в PHP за останні роки.
Вони дисциплінують код, роблять модель даних передбачуваною й забезпечують реальну типобезпеку.


❤️ Я вже не уявляю розробки без enum’ів.
І якщо ти досі не використовуєш їх у своїх проєктах — просто спробуй, через тиждень без них ти вже не зможеш писати старим способом.
———
⬅️ Іменовані аргументи | readonly + accessors ➡️
❤2👍1
#️⃣ #php #doctrine #enum #4students #tips #tools

🧩 Doctrine + Enum

Я так люблю enum’и, що зробив навіть власний пакет — ufo-tech/doctrine-helper, який навчає Doctrine розуміти enum у типізованих полях сутності і створювати під них ENUM() тип поля в MySQL.

1️⃣ Створи свій PHP Enum для поля сутності
<?php
namespace App;

enum MyEnum: string
{
case CASE_1 = 'Мій варіант 1';
case CASE_2 = 'Мій варіант 2';
}


2️⃣ Створи власний DBAL-тип і наслідуй Ufo\DoctrineHelper\DBAL\AbstractEnumType
<?php
namespace App\DBAL;

use BackedEnum;
use App\MyEnum;

class MyEnumType extends AbstractEnumType
{
protected string|BackedEnum $enum = MyEnum::class;
}


3️⃣ Зареєструй новий тип у Doctrine
doctrine:
dbal:
types:
App\MyEnum: 'App\DBAL\MyEnumType'
mapping_types:
enum: string


4️⃣ Використовуй PHP Enum для зберігання й зміни значення
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\MyEnum;

#[ORM\Entity]
#[ORM\Table(name: 'demo')]
class Demo
{
#[ORM\Column(type: MyEnum::class, nullable: false)]
protected string $case;

public function __construct(MyEnum $case)
{
$this->case = $case->value;
}

public function changeCase(MyEnum $case): void
{
$this->case = $case->value;
}

public function getCase(): string
{
return $this->case;
}
}


💡 Таким чином MySQL ENUM поводиться як повноцінний PHP Enum — із типобезпекою, автопідказками IDE та без ручного кастингу.
👍3❤1
#holywar #4students #architecture #php
📤 Відповідаю на питання
Саша привіт! Потрібна твоя консультація. Скажи, будь ласка:
1. чи можна використовувати try catch для управління логікою? Я досі використовував, але сьогодні натрапив на твердження, що це не є хороша практика.

2. Наскільки ресурсозатратне використання вкладених try catch? Прочитав, що сам try catch сам по собі "дешевий", а от throw вимагає багато ресурсів. Це так?

Наперед дякую


1. Чи можна використовувати try/catch для управління логікою?

Так, можна, і це цілком робочий підхід. Питання тут не в "правильно/неправильно", а в тому, яка філософія тобі ближча і що підходить під конкретну архітектуру.

Насправді, це дуже холіварна тема.

Є табір, який каже "винятки тільки для виняткових ситуацій", а є табір, який спокійно використовує їх як механізм flow control. І обидва підходи живуть собі нормально.

Я особисто вважаю, що це питання зручності, а не абсолютна істина, мені зручніше обʼєктно-орієнтований підхід, ІМХО якщо код читається краще — значить підхід має право на життя.

Приклад:
try {
$code = $repo->getByUrl($url);
} catch (NotFoundException) {
$code = $this->generateAndSave($url);
}

У такій ситуації це працює природно і зрозуміло: "немає? створи". Немає нічого поганого в тому, щоб ловити конкретний виняток і робити fallback-логіку.

Проблеми починаються тільки тоді, коли exception використовується замість звичайного if у найпростіших місцях, або коли репозиторій починає кидати винятки там, де очікувано повертати null. Але то вже питання дизайну коду.

2. Чи дійсно throw дорогий?
Так, це правда.
try/catch сам по собі майже нічого не коштує. Їх можна хоч вкладати — накладні витрати мізерні, але throw — дорогий, бо PHP генерує backtrace і створює повноцінний Exception-об’єкт, що виїдає процесор.

Тому:
— Якщо throw трапляється рідко — взагалі не проблема.
— Якщо "not found" стає нормальним шляхом виконання (часто), тоді варто задуматись, чи не простіше повертати null або Result-об’єкт — чисто з точки зору продуктивності.

Але й тут все залежить від контексту. Якщо загрузка невелика — різниця взагалі не має сенсу.

Підсумую мою позицію
— Використовувати try/catch для логіки можна. Це не є поганою практикою само по собі, це питання смаку, стилю і команди.
— Якщо код читається краще — я завжди за.
— try/catch дешевий, throw дорожчий, але в реальних проєктах це рідко стає bottleneck’ом.

Якщо тобі це зручно і воно вписується в архітектуру — продовжуй використовувати.

———
Чекаю холіварників у коментах
👍11🔥2👎1
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 8/10
🔒 readonly + accessors

🧱 Стабільні DTO і Value Object без бойлерплейту

📦 Додано у версіях:
readonly — PHP 8.1
accessors — PHP 8.4

💡 Що це дає: можливість створювати об’єкти, дані в яких неможливо змінити випадково,
та новий синтаксис контролю доступу до властивостей через get / set без окремих методів.

🧠 В чому суть:
readonly робить властивість незмінною після ініціалізації —
значення можна встановити лише один раз (зазвичай у конструкторі).

accessors дозволяють додати контроль при читанні та записі властивостей
без класичних геттерів і сеттерів — прямо в тілі класу.

Разом вони створюють ідеальну комбінацію для DTO, VO і конфігураційних класів:
дані незмінні, а доступ контрольований.

🧱 Приклад класичного підходу
class Product {
private string $name;
private float $price;

public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
}

public function getPrice(): float {
return $this->price;
}

public function setPrice(float $price): void {
if ($price < 0) {
throw new InvalidArgumentException('Price must be positive');
}
$this->price = $price;
}
}

✅ працює, але більша частина коду тут тільки заради забезпечення базової інкапсуляції.

⚡️ Тепер те саме — сучасно
class Product {
public readonly string $name;
private float $price {
get => $this->price;
set => $this->price = max(0, $value);
}

public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
}
}

readonly гарантує незмінність імені,
accessors автоматично перевіряють ціну при записі.

Ще приклад — Value Object
class Coordinates {
public readonly float $lat {
get => round($this->lat, 6);
}

public readonly float $lng {
get => round($this->lng, 6);
}

public function __construct(float $lat, float $lng)
{
$this->lat = $lat;
$this->lng = $lng;
}
}

👌 Ніяких змін після створення, тільки контрольований доступ при читанні.

⚙️ Де це реально корисно
🔹DTO: запити й відповіді в API;
🔹Value Object: Money, Price, GeoPoint, Range;
🔹Immutable конфігурації: параметри сервісів, константні стани.

🧭 Висновок
readonly і accessors — це крок до справжніх record-класів у PHP.
Вони дозволяють писати чисті, передбачувані структури без тонни бойлерплейту.

🔥 Сьогодні кожен мій DTO — readonly.
———
⬅️ Enums | Атрибути ➡️
👍1
#️⃣ #php #course #series

🚀 Чи знаєш ти, що PHP так вміє? 9/10
🏷 Атрибути
✨ Метапрограмування без DocBlock-хаків

📦 Додано у версії: PHP 8.0
💡 Що дає: нативний, типобезпечний механізм додавання метаданих до класів, методів, властивостей та параметрів — без необхідності при їх читанні парсити DocBlock-и та встановлювати сторонні бібліотеки.

❤️ Трохи особистого
До PHP 8.0 в мене був один технічний “біль”, про який знають тільки ті, хто коли-небудь пробував будувати API-документацію чи SDK на основі читання коду.
Скажу лише одне: парсинг DocBlock-ів — це дуже невдячна робота.

Атрибути стали тією фічею, після якої мої бібліотеки буквально отримали "вдих свіжого повітря".
Код став чистішим, простішим, а метадані — нарешті передбачуваними.

🧩 Навіщо вони взагалі потрібні?
До PHP 8 метадані доводилося писати так:
/**
* @Route("/users")
* @SomeFlag(true)
*/
class UserController {}

Це був просто коментар, який не розбирається стандартними засобами PHP такими як рефлексія. Для аналізу потрібна ще одна бібліотека-парсер, яка мала вгадати, що розробник мав на увазі.

Атрибути роблять метакоментарі типізованими, тепер вони обʼєктні:
#[Route('/users')]
#[SomeFlag(true)]
class UserController {}

Це буквально декларація єкземпляру класів Route та SomeFlag і IDE точно знає, що всередині. Це дає:
— нативне використання
— розуміння які параметри треба передати
— легке читання коду
— легке використання цих даних

🧱 Приклади застосування
1️⃣ Доктрина

#[ORM\Entity]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
public int $id;

#[ORM\Column(length: 255)]
public string $name;
}


2️⃣ Symfony Routing
#[Route('/orders/{id}', methods: ['GET'])]
public function show(int $id) { ... }


3️⃣ Валідація
class RegistrationDTO
{
#[Assert\Email]
#[Assert\NotBlank]
public string $email;

#[Assert\Length(min: 8)]
public string $password;
}


4️⃣ Кастомні аттрибути
#[API\Method('user.create')]
class CreateUserRequest
{
#[API\Param]
public string $email;

#[API\Param]
public string $password;
}


🧠 А що можна робити з атрибутами?

Майже все, що роблять Java/Spring- або C#-атрибути:
🔹позначати класи й методи
🔹додавати метадані
🔹створювати власні декларативні правила
🔹будувати роутинг
🔹маркувати серіалізацію/десеріалізацію
🔹автоматизувати DI/автовіринг
🔹вказувати, як працює кешування чи логування
🔹створювати декларативні контракти для DTO

Фактично — інструмент, щоб писати менше імперативного коду та більше декларативного.

✨ Як створити власний атрибут
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Loggable
{
public function __construct(
public string $message = 'default',
public bool $enabled = true,
) {}
}


Використання:
#[Loggable(message: 'Auth attempt')]
class AuthService {}


🔍 Як прочитати атрибут через Reflection
$ref = new ReflectionClass(AuthService::class);
$attrs = $ref->getAttributes(Loggable::class);

$instance = $attrs[0]->newInstance();
echo $instance->message;


📌 Особливо важливо
🔹атрибути це повноцінний об’єкт, а не просто рядок в коментарі
🔹вони мають простір імен
🔹можна типізувати параметри
🔹можна використовувати передові конструкції (enum, readonly, VO)
🔹ідеально комбінуються з DI-контейнерами

🧭 Висновок
Атрибути — це одна з тих фіч, після яких старий PHP здається архаїчним.
Типобезпека, декларативність, прозора робота через Reflection, відсутність DocBlock-магії — це все робить їх інструментом, який я обожнюю використовувати у власних бібліотеках.
Це настільки природно, що важко повірити, що колись ми писали метадані в коментарях.

———
⬅️ readonly + accessors
👍14
#php #composer #security

🚨 Оновіть Composer до версії 2.9.6 або 2.2.27 (LTS)

В Composer виправили вразливості CVE-2026-40176, CVE-2026-40261

Через Perforce driver можна було підсунути значення, які попадали в shell-команди без екранування, як результат — це була command injection в вашій консолі.
Нічого критично нового, але ще один сигнал, що безпека supply chain — це не теорія, а реальність.

🧠 Цей сухий факт, це ще одне нагадування про очевидну, але часто ігноровану річ:

composer install === виконання чужого коду на своєму компі

І не тільки через scripts в composer.json
А і на рівні самого composer

💥 Найнеприємніше

Навіть якщо ти навіть не використовуєш Perforce, dependency може змусити composer виконати команду.

📌 Без оновлення composer ти в зоні ризику якщо:
— ставиш пакети з source
— працюєш з dev-версіями
— підключаєш кастомні репозиторії
— просто запускаєш composer на чужому коді

✅ Що робити?
— оновити composer (там вже пофіксили)
— по можливості використовувати dist замість source
— менше довіряти всьому підряд

🧭 Моя позиція
Composer давно перестав бути просто менеджером залежностей, сьогодні це повноцінний execution layer і кожен composer install — це маленький "bash" з інтернету, перед підключенням кастомних залежностей потрібно оцінювати що саме воно буде тягнути за собою.
🔥8🤯1