Junior: питання на співбесіді з теми «Функції й замикання»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Обидві створюють об'єкт Closure, але по-різному працюють із зовнішніми змінними.
Анонімна функція (function () use (...)) бачить лише ті зовнішні змінні, які явно перелічені в use, і захоплює їх за значенням у момент створення:
$rate = 1.2;
$withTax = function (int $price) use ($rate): float {
return $price * $rate;
};
Щоб змінювати зовнішню змінну, - захоплення за посиланням: use (&$total).
Стрілкова функція (fn, PHP 7.4) автоматично захоплює всі зовнішні змінні, які використовує, за значенням:
$withTax = fn (int $price): float => $price * $rate;
Відмінності:
- Лише один вираз. Тіло стрілкової функції - один вираз, результат якого повертається. Кілька інструкцій - тільки анонімна функція.
- Не можна змінити зовнішню змінну.
fn () => $count++змінить копію, а не$countззовні. Для накопичення - анонімна функція зuse (&$count). useне потрібен - менше шуму в коді.
Де що використовують:
fn- короткі колбеки:array_map(fn ($u) => $u->email, $users),->filter(fn ($o) => $o->isPaid()),->when($search, fn ($q) => $q->where(...)).function- колбеки з кількома кроками, розгалуженнями чи зміною зовнішнього стану.
Обидві автоматично прив'язують $this усередині класу. Щоб уникнути цього (і зайвого утримання об'єкта в пам'яті), - static fn чи static function.
Значення за замовчуванням роблять параметр необов'язковим:
function paginate(array $items, int $perPage = 15, int $page = 1): array
{
return array_slice($items, ($page - 1) * $perPage, $perPage);
}
paginate($items); // 15 на сторінку, перша сторінка
paginate($items, 50);
paginate($items, page: 3); // іменований аргумент пропускає $perPage
Правила:
- Значення за замовчуванням - константний вираз: літерал, константа,
new(з PHP 8.1), масив. Не виклик функції. - Необов'язкові параметри йдуть після обов'язкових. Обов'язковий параметр після необов'язкового - deprecated.
nullза замовчуванням вимагає nullable-типу:?string $name = null.
Variadic-параметр ...$name збирає всі решту аргументів у масив:
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
sum(1, 2, 3); // 6
sum(...[1, 2, 3]); // розпаковка масиву в аргументи
- Може бути лише останнім параметром.
- Тип застосовується до кожного елемента:
int ...$numbers. - Іменовані аргументи, що не відповідають жодному параметру, потрапляють у variadic з рядковими ключами.
Пастки:
- Змінюване значення за замовчуванням через
new(Options $o = new Options()) створює новий об'єкт при кожному виклику - на відміну від Python, тут проблеми спільного стану немає. - Багато необов'язкових параметрів - запах. Якщо їх п'ять, краще об'єкт параметрів (DTO) чи кілька окремих методів.
- Булевий прапорець за замовчуванням (
bool $force = false) робить виклики незрозумілими - іменований аргумент рятує:delete(force: true).
За замовчуванням аргументи передаються за значенням: функція отримує копію, і зміна параметра не впливає на змінну викликача. Знак & у сигнатурі робить передачу за посиланням:
function addItem(array &$cart, string $item): void
{
$cart[] = $item;
}
$cart = [];
addItem($cart, 'книга');
// $cart === ['книга']
Де це в стандартній бібліотеці: sort(), array_push(), preg_match() (масив збігів), array_walk() - вони змінюють передану змінну.
Чому у власному коді це часто погана ідея:
- Прихований побічний ефект. З виклику
addItem($cart, 'книга')не видно, що$cartзміниться. Читач має знати сигнатуру. - Важче міркувати й тестувати: функція не просто повертає результат, а змінює стан ззовні.
- Не дає економії пам'яті. Масиви копіюються ліниво (copy-on-write), тож передача за значенням не копіює дані, доки їх не змінять.
- Не можна передати вираз:
addItem([], 'x')чиaddItem(getCart(), 'x')- помилка, потрібна саме змінна.
Краща альтернатива - повертати нове значення:
function withItem(array $cart, string $item): array
{
return [...$cart, $item];
}
$cart = withItem($cart, 'книга');
Або - об'єкт зі станом і методом: $cart->add('книга') - зміна очевидна з виклику.
Об'єкти й так не копіюються: функція, що отримала об'єкт, працює з тим самим об'єктом. & для об'єкта потрібен лише тоді, коли функція має замінити змінну іншим об'єктом.
Коли посилання виправдані: зміна великих вкладених структур на місці в гарячому коді та рідкісні API на кшталт «повернути кілька значень» - хоча тут читабельніше повернути масив чи об'єкт.
Докладніше в документації: Передача аргументів за посиланням