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) і для файлів, що повертають масиви.
У 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): кожен запит починає з чистого аркуша.
Життєвий цикл запиту:
- Веб-сервер (Nginx, Caddy) передає запит у PHP-FPM.
- Вільний воркер FPM запускає скрипт-точку входу (
public/index.php). - Підключається автозавантажувач, створюється застосунок, завантажується конфігурація, реєструються сервіс-провайдери - на кожному запиті заново.
- Обробляється запит, формується відповідь.
- Після відповіді уся пам'ять звільняється: змінні, об'єкти, з'єднання (крім постійних), статичні властивості.
Що з цього випливає:
- Витоки пам'яті майже не шкодять: усе звільниться в кінці запиту.
- Стан між запитами живе лише зовні: в базі, кеші (Redis), сесії, файлах. Статична змінна, змінена в одному запиті, у наступному знову має початкове значення.
- Помилка в одному запиті не впливає на інші - кожен ізольований.
- Ціна - повторна ініціалізація. Завантажити фреймворк з сотнями класів на кожен запит дорого. Частково це компенсують OPcache (готовий байт-код) і кешування конфігурації й маршрутів (
php artisan optimize).
Інша модель - довгоживучі воркери (Laravel Octane на FrankenPHP, RoadRunner чи Swoole): застосунок завантажується один раз і обслуговує тисячі запитів. Це швидше, але стан зберігається між запитами: статичні властивості, синглтони, кеш у пам'яті сервісу. Код, написаний з розрахунку на shared-nothing, може «протікати» даними одного користувача в запит іншого.