Middle: питання на співбесіді з теми «Типи й помилки»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
- Nullable
?T- значення типуTабоnull:?string. - Union
A|B(PHP 8.0) - будь-який з перелічених типів:int|string.?string- це скорочення дляstring|null. mixed(PHP 8.0) - будь-який тип, включно зnull. Явно каже: «тут справді що завгодно».- Intersection
A&B(PHP 8.1) - об'єкт, що реалізує всі перелічені інтерфейси.
function findUser(int|string $id): ?User
{
return is_int($id) ? User::find($id) : User::firstWhere('slug', $id);
}
Що варто знати:
- Union-тип змушує код усередині розрізняти варіанти (
is_int,instanceof). Якщо гілок стає багато, це сигнал, що функція робить дві різні речі. mixedне означає «тип не вказали»: він дозволяє все і забирає в аналізатора можливість допомогти. Використовуйте його лише там, де значення справді довільне - наприклад, у кеші.- Під
declare(strict_types=1)скалярні типи не приводяться:findUser('5')передасть рядок, а не число. - PHP 8.2 дозволив і DNF-типи:
(A&B)|null.
finally виконується завжди: після успішного try, після обробленого винятку, після необробленого і навіть після return у try чи catch. Це місце для прибирання - закрити файл, зняти блокування, відкотити стан.
$lock = Cache::lock('import', 60);
$lock->block(5);
try {
$this->import();
} finally {
$lock->release(); // і після помилки теж
}
Пастка з return: якщо finally сам робить return, він перекриває все попереднє - і значення з try, і виняток, що летів:
function f(): string
{
try {
throw new RuntimeException('збій');
} finally {
return 'ok'; // виняток загублено без сліду
}
}
Тому в finally не повертають значень і не кидають нових винятків - лише прибирають.
Альтернатива в багатьох випадках - готові обгортки, які роблять try/finally за вас: $lock->get(fn () => ...), DB::transaction(fn () => ...).
У PHP є дві системи повідомлень про проблеми: винятки (throw) і помилки рушія - E_WARNING, E_NOTICE, E_DEPRECATED. Попередження за замовчуванням лише пишеться в лог, а виконання продовжується - часто з неправильними даними:
$content = file_get_contents('/missing.txt'); // Warning, $content === false
$data = json_decode($content); // працюємо далі з false
set_error_handler перехоплює такі помилки. Найпоширеніший прийом - перетворити їх на ErrorException:
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
if (! (error_reporting() & $severity)) {
return false; // помилку придушено через @ чи налаштування
}
throw new ErrorException($message, 0, $severity, $file, $line);
});
Тепер file_get_contents для неіснуючого файлу кидає виняток, і код не піде далі з false.
Laravel робить це за вас: обробник помилок фреймворку (HandleExceptions) перетворює попередження й notice на ErrorException. Тому в Laravel-застосунку «Undefined array key» - це виняток зі стек-трейсом, а не рядок у лозі. Застарілі (E_DEPRECATED) Laravel на винятки не перетворює, а пише в окремий канал логів, якщо його налаштовано (LOG_DEPRECATIONS_CHANNEL).
Що варто знати:
- Фатальні помилки (
E_ERROR, вичерпання пам'яті)set_error_handlerне перехоплює. Для них -register_shutdown_function()зerror_get_last(). - Оператор
@придушує попередження, але приховує й справжні проблеми. Краще перевіряти результат функції явно. - Сучасні функції часто вже кидають винятки:
json_decode(..., flags: JSON_THROW_ON_ERROR),random_int(), більшість методів класів SPL і intl.
?-> (PHP 8.0) звертається до методу чи властивості, лише якщо об'єкт ліворуч не null. Якщо він null, весь ланцюжок повертає null і далі не виконується (short-circuit).
// До PHP 8
$country = null;
if ($session !== null) {
$user = $session->user();
if ($user !== null && $user->address !== null) {
$country = $user->address->country;
}
}
// PHP 8
$country = $session?->user()?->address?->country;
Short-circuit важливий: якщо $session - null, метод user() навіть не викликається, а аргументи далі по ланцюжку не обчислюються.
Обмеження:
- Лише читання.
$user?->name = 'Оля'- помилка компіляції: присвоювати через?->не можна. - Не працює з масивами:
$data?['key']такого синтаксису немає. Для масивів -??. - Не для посилань:
foo($obj?->prop)у параметр за посиланням не передати. null- не «помилка». Якщо об'єкт мав бути там завжди,?->ховає баг: замість зрозумілого винятку код тихо працює зnull.
Разом з ?? дає значення за замовчуванням:
$city = $order->customer?->address?->city ?? 'Не вказано';
Порада: ?-> - для справді необов'язкових зв'язків (у користувача може не бути профілю). Довгі ланцюжки ?-> - запах: код занадто багато знає про внутрішню будову інших об'єктів, і варто подумати про метод на кшталт $order->shippingCity().