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

PHP: типи й помилки

20 питань · ~20 хв · Версія v3.0

Увійдіть, щоб продовжити

Строгі типи й приведення, порівняння й оператори, union і перетинні типи, винятки, finally і обробники помилок - питання всіх рівнів, від junior до lead.

За спробу
20
У пулі
62
Проходжень
0
Середній бал
-
Пройшли на 70%+
-

Питання для підготовки

20 питань

PHP дозволяє оголошувати типи аргументів, повернення та властивостей - і перевіряє їх у рантаймі.

class VacancyService
{
    public function __construct(
        private VacancyRepository $repository,
    ) {
    }

    public function publish(int $id, ?string $comment = null): Vacancy
    {
        // ...
    }
}

Що дає: помилка ловиться в момент виклику, а не через три шари; редактор підказує методи; статичний аналіз бачить невідповідності до запуску.

declare(strict_types=1) змінює поведінку перевірки. Без нього PHP приводить типи мовчки:

function repeat(int $times): string { /* ... */ }

repeat('5');     // без strict_types: '5' стане 5, викликається нормально
repeat('5');     // зі strict_types: TypeError

Саме мовчазне приведення небезпечне: 'abc' перетвориться на 0, а '5 котів' - на 5, і помилка проявиться далеко від місця, де виникла.

Важлива деталь: директива діє на файл, де вона написана, і стосується викликів з нього, а не в нього. Тому її ставлять у кожен файл, першим рядком після <?php.

Корисні типи PHP 8:

  • ?string - рядок або null.
  • int|string - обʼєднання типів.
  • never - функція не повертає керування (кидає виняток чи завершує процес).

У Laravel-проєктах declare(strict_types=1) зазвичай вимагається стилем коду й перевіряється Pint.

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

Обидва об'єднують масиви, але по-різному поводяться з однаковими ключами:

  • $a + $b - бере все з $a, а з $b додає лише ключі, яких у $a немає. Перемагає лівий.
  • array_merge($a, $b) - для рядкових ключів перемагає правий, а числові ключі перенумеровує з нуля й дописує в кінець.
$defaults = ['color' => 'red', 'size' => 'M'];
$options  = ['color' => 'blue'];

$options + $defaults;            // ['color' => 'blue', 'size' => 'M']
array_merge($defaults, $options); // ['color' => 'blue', 'size' => 'M']

[0 => 'a', 1 => 'b'] + [0 => 'c', 1 => 'd', 2 => 'e']; // ['a', 'b', 'e']
array_merge(['a', 'b'], ['c', 'd', 'e']);              // ['a', 'b', 'c', 'd', 'e']

Коли що:

  • + - накласти значення за замовчуванням: «мої опції, а чого бракує - з дефолтів».
  • array_merge або spread [...$a, ...$b] - склеїти списки.

Пастка: числові ключі, які насправді ідентифікатори ([15 => 'Київ']), array_merge перенумерує й загубить. Для таких масивів потрібен + або array_replace.

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

Через usort і оператор <=> («космічний корабель»), який повертає -1, 0 або 1. Масиви теж можна порівнювати через <=> - поелементно, зліва направо, і це дає сортування за кількома полями одним рядком:

usort($users, fn (array $a, array $b): int =>
    [$b['score'], $a['name']] <=> [$a['score'], $b['name']]
);

Тут спершу за score за спаданням (тому $b і $a поміняні місцями), а за однакового рахунку - за name за зростанням.

Що ще варто знати:

  • usort скидає ключі. Щоб зберегти їх, потрібен uasort.
  • З PHP 8.0 сортування стабільне: рівні за критерієм елементи лишаються в початковому порядку. На цьому можна будувати сортування в кілька проходів.
  • Рядки <=> порівнює побайтово, тож «Ярослав» і «Андрій» стануть не за абеткою. Для тексту людською мовою - Collator з розширення intl.

У Laravel колекції роблять те саме читабельніше: collect($users)->sortBy([['score', 'desc'], ['name', 'asc']]).

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

=== (тотожність) - true, лише якщо однакові і значення, і тип. == (рівність) спершу приводить операнди до спільного типу, а тоді порівнює.

1 === '1';    // false - int і string
1 == '1';     // true
0 == 'abc';   // false у PHP 8, true у PHP 7
null == false; // true
'1e3' == '1000'; // true - обидва числові рядки

Що змінив PHP 8: порівняння числа з нечисловим рядком тепер порівнює їх як рядки. Раніше 0 == 'abc' було true, бо 'abc' перетворювався на 0 - джерело реальних вразливостей у перевірках паролів і токенів.

Правило: за замовчуванням ===. == - лише коли приведення типів свідомо потрібне, і тоді краще привести явно: (int) $request->input('page') === 1.

Ще пастки ==:

  • in_array($value, $list) і array_search за замовчуванням порівнюють через ==. Третій аргумент true вмикає строге порівняння.
  • switch теж порівнює через ==; match - через ===.

Докладніше в документації: Оператори порівняння

У PHP дві гілки «кидабельних» об'єктів, і обидві реалізують інтерфейс Throwable:

  • Exception - очікувані ситуації, які код програми кидає сам і може обробити: немає файлу, сервіс не відповів, дані не пройшли перевірку.
  • Error - помилки в самому коді чи середовищі, які кидає рушій: TypeError, ArgumentCountError, DivisionByZeroError, виклик методу на null.
try {
    $user->profile->avatar(); // $user->profile === null
} catch (Exception $e) {
    // сюди не потрапить: це Error
} catch (Error $e) {
    // Call to a member function avatar() on null
}

Що з цим робити:

  • Ловіть конкретні класи винятків, які очікуєте: catch (ConnectionException $e).
  • catch (Throwable $e) - лише на самому верху: в обробнику помилок фреймворку, у воркері черги, щоб записати збій і не впасти цілком.
  • Error зазвичай не «обробляють», а виправляють: це баг, і його треба побачити в логах, а не проковтнути.

До PHP 7 більшість таких помилок були фатальними й не ловилися взагалі - Error з'явився саме для того, щоб їх можна було перехопити.

Докладніше в документації: Помилки в PHP 7+

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.