Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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.

Докладніше в документації: set_error_handler

?-> (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().

Докладніше в документації: Nullsafe-оператор