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

Junior: питання на співбесіді з теми «Основи мови»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

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.

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

Усі чотири конструкції підключають і виконують інший PHP-файл. Різниця - у реакції на відсутній файл і в повторному підключенні:

  • require - файлу немає: фатальна помилка, виконання зупиняється.
  • include - файлу немає: попередження, виконання продовжується.
  • require_once / include_once - якщо файл уже підключали, повторно не підключають.
require __DIR__ . '/vendor/autoload.php';   // без нього застосунок не має сенсу
include __DIR__ . '/partials/banner.php';   // банер необов'язковий

Коли що:

  • require - для того, без чого код не може працювати: автозавантажувач, конфігурація.
  • *_once - для файлів з оголошеннями функцій і класів: повторне оголошення - фатальна помилка.
  • include - для справді необов'язкових фрагментів. На практиці рідко: краще явно перевірити file_exists.

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

  • Підключений файл бачить змінні з області видимості, де стоїть include, і може повернути значення: $config = require 'config.php'; - так влаштовані конфіг-файли Laravel.
  • Відносні шляхи розв'язуються відносно include_path і поточного робочого каталогу, а не файлу, що підключає. Тому завжди __DIR__ . '/...'.
  • Шлях від користувача в include - вразливість (Local/Remote File Inclusion): include $_GET['page'] . '.php'.

У сучасному коді ручні require майже зникли: класи завантажує автозавантажувач Composer (PSR-4), а require лишається в точці входу (public/index.php) і для файлів, що повертають масиви.

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

У PHP функція не бачить змінних ззовні - на відміну від JavaScript, де внутрішня функція бачить змінні зовнішньої.

$rate = 1.2;

function withTax(int $price): float
{
    return $price * $rate;   // Warning: Undefined variable $rate
}

Як передати значення у функцію:

  • Параметром - правильний спосіб: withTax(100, $rate).
  • global $rate; чи $GLOBALS['rate'] - доступ до глобальної змінної. Працює, але робить функцію залежною від прихованого глобального стану: її важко тестувати й переносити.
  • Замикання з use - захоплює значення змінної в момент створення:
$withTax = function (int $price) use ($rate): float {
    return $price * $rate;
};
  • Стрілкова функція захоплює зовнішні змінні автоматично (за значенням):
$withTax = fn (int $price): float => $price * $rate;

Статичні змінні зберігають значення між викликами функції:

function counter(): int
{
    static $count = 0;
    return ++$count;
}

Корисно для мемоізації, але це теж прихований стан, що живе весь процес.

Блоки не створюють області видимості: змінна, оголошена в if чи foreach, видна після нього до кінця функції - наприклад, $item після foreach ($items as $item) дорівнює останньому елементу.

Методи класу бачать лише свої параметри й $this, тож стан об'єкта - явна альтернатива глобальним змінним.

Докладніше в документації: Область видимості змінних

Класичний PHP працює за моделлю «нічого не спільного» (shared-nothing): кожен запит починає з чистого аркуша.

Життєвий цикл запиту:

  1. Веб-сервер (Nginx, Caddy) передає запит у PHP-FPM.
  2. Вільний воркер FPM запускає скрипт-точку входу (public/index.php).
  3. Підключається автозавантажувач, створюється застосунок, завантажується конфігурація, реєструються сервіс-провайдери - на кожному запиті заново.
  4. Обробляється запит, формується відповідь.
  5. Після відповіді уся пам'ять звільняється: змінні, об'єкти, з'єднання (крім постійних), статичні властивості.

Що з цього випливає:

  • Витоки пам'яті майже не шкодять: усе звільниться в кінці запиту.
  • Стан між запитами живе лише зовні: в базі, кеші (Redis), сесії, файлах. Статична змінна, змінена в одному запиті, у наступному знову має початкове значення.
  • Помилка в одному запиті не впливає на інші - кожен ізольований.
  • Ціна - повторна ініціалізація. Завантажити фреймворк з сотнями класів на кожен запит дорого. Частково це компенсують OPcache (готовий байт-код) і кешування конфігурації й маршрутів (php artisan optimize).

Інша модель - довгоживучі воркери (Laravel Octane на FrankenPHP, RoadRunner чи Swoole): застосунок завантажується один раз і обслуговує тисячі запитів. Це швидше, але стан зберігається між запитами: статичні властивості, синглтони, кеш у пам'яті сервісу. Код, написаний з розрахунку на shared-nothing, може «протікати» даними одного користувача в запит іншого.

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