`DateTime` змінює сам себе на кожному `modify()`/`add()`, тому одна змінна тихо міняється в усіх місцях, де на неї посилаються; `DateTimeImmutable` має той самий API, але повертає новий екземпляр. Час зберігають як UTC-мітку, а пояс користувача застосовують лише на виводі через `setTimezone()` і завжди за ідентифікатором зони, а не за зсувом.
Питання на співбесіду з PHP
Питання для Junior, Middle та Senior PHP-розробників: типи й приведення, ООП, замикання, памʼять і збирач сміття, PHP 8.x-можливості, які вже питають на реальних інтервʼю.
Оператор == порівнює значення з приведенням типів, === порівнює і значення, і тип без жодних перетворень.
Інтерфейс — це публічний контракт без стану, і їх можна реалізувати скільки завгодно; абстрактний клас — частково готова реалізація зі станом, конструктором і protected-методами, але успадкувати можна лише один.
Рядок у PHP - це послідовність байтів без інформації про кодування, тому `strlen` рахує байти, а кирилиця в UTF-8 займає по два байти на літеру. Символи рахують і ріжуть `mb_`-функції (`mb_strlen`, `mb_substr`, `mb_strtolower`), а регулярні вирази переводить у режим символів модифікатор `u`.
Float у PHP - це двійковий double за IEEE 754, у якому 0.1, 0.2 чи 19.99 не мають точного представлення, тому суми накопичують похибку. Гроші тримають у цілих копійках (int) або в рядках з DECIMAL/numeric у базі, рахують через bcmath чи `BcMath\Number` з PHP 8.4, а валюту й правило округлення вважають частиною значення.
Enum (PHP 8.1+) — тип із фіксованим переліком case-ів; pure enum не має скалярного значення, backed enum привʼязаний до int або string і вміє from()/tryFrom().
Prepared statement - це запит із плейсхолдерами (`?` або `:name`), у якому дані передаються окремо від тексту SQL, тому вони ніколи не парсяться як команда. Захист працює саме через це розділення, а не через екранування лапок; у PDO MySQL за замовчуванням увімкнена емуляція, тому перевіряйте `PDO::ATTR_EMULATE_PREPARES` і завжди задавайте `charset` у DSN.
readonly дозволяє записати типізовану властивість рівно один раз зі скоупу класу; з PHP 8.2 модифікатор можна поставити на весь клас.
Впровадження залежностей означає, що клас отримує готові співпрацівники ззовні (найчастіше через конструктор), а не створює їх сам. Контейнер це фабрика з реєстром, яка вміє зібрати граф обʼєктів за типами параметрів конструктора і підставити реалізацію там, де оголошено інтерфейс.
З PHP 7 усе, що можна кинути, реалізує інтерфейс `Throwable`, під яким дві незалежні гілки: `Exception` (очікувані ситуації рантайму) і `Error` (помилки програміста: `TypeError`, `ValueError`, `DivisionByZeroError`). `finally` виконується завжди, окрім `exit()` і фатального завершення, а warnings лишилися для ситуацій, які PHP історично вміє пережити, і перетворюються на винятки через `set_error_handler`.
Хешувати треба `password_hash()` і перевіряти `password_verify()`: функція сама генерує криптостійку сіль і кладе її разом з алгоритмом і вартістю в один рядок. Окрема колонка для солі не потрібна, а параметри піднімають через `password_needs_rehash()` під час успішного логіну.
Замикання — це обʼєкт класу Closure; `function` захоплює зовнішні змінні лише явно через `use` (за значенням або за посиланням через `&`), а стрілочна функція `fn` (PHP 7.4+) захоплює їх автоматично й завжди за значенням.
У cookie браузер тримає лише ідентифікатор (типово `PHPSESSID`), а самі дані `$_SESSION` лежать на сервері: у файлах `session.save_path`, у Redis або у власному `SessionHandlerInterface`. Cookie сесії має бути `HttpOnly`, `Secure` і `SameSite`, а API замість сесії краще авторизувати токеном у заголовку `Authorization`, бо cookie шлеться браузером автоматично й тягне за собою CSRF і прив'язку до сервера.
Composer реєструє свій завантажувач через `spl_autoload_register`, і при першій згадці класу PSR-4 перетворює його повне імʼя на шлях: найдовший збіг префікса замінюється базовою текою, решта імені стає підтеками й файлом. Classmap натомість заздалегідь сканує теки й будує таблицю «клас - файл», а `dump-autoload -o` перетворює PSR-4-правила на таку саму таблицю, щоб прибрати пошук по файловій системі.
Магічні методи - це хуки, які движок кличе замість помилки: `__get`/`__set`/`__call` підміняють доступ до неоголошених членів класу, `__invoke` робить обʼєкт callable, `__toString` дає рядкове представлення, `__construct` і `__destruct` тримають життєвий цикл. За `__get`/`__set`/`__call` платять автодоповненням, типами й рефакторингом, тому їх лишають для проксі та ORM, а в звичайному коді оголошують властивості й методи явно.
PHP 8.0 дав union (`int|string`), `mixed` як верхній тип і `static` як тип повернення; 8.1 додав `never` і intersection (`A&B`); 8.2 дозволив DNF - комбінації виду `(A&B)|null`. Повернення в нащадку можна звужувати, параметри розширювати, типи властивостей мають лишатися незмінними.
`self::` і `parent::` - посилання на клас, зафіксовані на етапі компіляції за місцем написання коду, а `static::` резолвиться в рантаймі в клас, на якому виклик почався. Це і є late static binding (PHP 5.3+): завдяки йому `new static()` у батьківській фабриці створює нащадка, а `static::CONST` бере константу нащадка.
Рівні 0-10 вмикають правила порціями: 0-3 ловлять неіснуючі символи й неузгоджені return-типи, 5 перевіряє типи аргументів, 6 вимагає типізації, 8 забороняє звертатися до методів на nullable, 9-10 закривають mixed. У легасі це вводять baseline: заморозити наявні помилки у phpstan-baseline.neon, поставити аналіз у CI на червоне для нового коду і піднімати рівень по одному.
8.0 дав match, іменовані аргументи, promotion, union-типи і `?->`; 8.1 - enum, readonly, first-class callable, Fibers; 8.2 - readonly-класи і DNF-типи; 8.3 - типізовані константи класу і `#[\Override]`; 8.4 - property hooks, асиметричну видимість і `new Foo()->method()` без дужок. Спільна лінія цих релізів: більше типів у сигнатурах і менше способів зіпсувати стан обʼєкта ззовні.
`declare(strict_types=1)` — це директива рівня одного файлу, яка вимикає приведення скалярів для викликів, зроблених із цього файлу: без неї PHP у coercive-режимі намагається сконвертувати аргумент до оголошеного типу (з PHP 8.0 нечислові рядки — це `TypeError`, а числові з «хвостом» — `int` плюс `E_WARNING`), зі strict приймається лише точний тип, з єдиним винятком `int` → `float`.
Це можливість оголосити get і set логіку прямо на властивості, без окремих методів і без магії __get/__set.
Генератор — це функція з `yield`, яка при виклику не виконує жодного рядка тіла, а повертає обʼєкт `Generator` (реалізує `Iterator`): значення обчислюються по одному на вимогу, тому пікова памʼять не залежить від обсягу даних; ціна — один-єдиний прохід без перемотки, без `count()` і без доступу за індексом.
Трейт — механізм горизонтального повторного використання коду: набір методів і властивостей, який компілятор копіює в клас.
Масив передається за значенням, але фізична копія не створюється: PHP збільшує лічильник посилань на внутрішню структуру і копіює її лише при першому записі (copy-on-write). Обʼєкт copy-on-write не має - копіюється лише ідентифікатор, тому зміни властивостей видно з обох боків.
Named arguments (PHP 8.0) дозволяють передавати аргументи за іменем параметра й пропускати необовʼязкові, а constructor property promotion (PHP 8.0) оголошує властивість прямо в сигнатурі конструктора; ціна — імена параметрів стають частиною публічного API, а промотована властивість ініціалізується ще до тіла конструктора.
`match` (PHP 8.0+) — це вираз, який повертає значення й порівнює строго через `===` без провалювання між гілками, а якщо жодна умова не збіглася і немає `default` — кидає `UnhandledMatchError`; `switch` — оператор, який порівнює нежорстко через `==`, потребує `break` і при відсутності збігу мовчки нічого не робить.
`Fiber` (PHP 8.1+) — це низькорівневий примітив кооперативної багатозадачності: блок коду з власним стеком, який можна призупинити з будь-якої глибини викликів через `Fiber::suspend()` і відновити через `resume()`. Планувальника й циклу подій у ядрі немає — їх дають revolt/event-loop та AMPHP v3.
Атрибути (PHP 8.0) - це структуровані метадані `#[Attr(...)]`, які компілятор зберігає разом із класом, методом, властивістю чи параметром і які читають через `getAttributes()` у Reflection. Сам атрибут нічого не робить: його треба знайти рефлексією і перетворити на обʼєкт через `newInstance()`, тому продакшн-код робить це один раз під час прогріву кешу, а не на кожен запит.
Мінімум - `IteratorAggregate` (обхід у `foreach` через `getIterator()`), `Countable` (`count()`), `JsonSerializable` (контроль над `json_encode()`) і `Stringable` (типізація `string|Stringable`). `Iterator` пишуть вручну лише коли треба керувати станом обходу, а `ArrayAccess` дає синтаксис `$obj['key']`, який ламає типізацію і статичний аналіз.
WeakReference (PHP 7.4+) і WeakMap (PHP 8.0+) тримають обʼєкт, не збільшуючи його refcount: як тільки обʼєкт знищується, посилання стає null, а запис у WeakMap зникає сам — це штатний спосіб робити привʼязані до обʼєктів кеші й реєстри в довгоживучих процесах.
PHP 8.5 (реліз 20 листопада 2025) додав оператор конвеєра `|>`, синтаксис `clone $obj with {...}`, атрибут `#[\NoDiscard]` з приведенням `(void)`, вбудоване розширення URI з класами `Uri\Rfc3986\Uri` та `Uri\WhatWg\Url`, а також дрібніші зручності — `array_first()`/`array_last()`, атрибути на константах, замикання в константних виразах і бектрейси для фатальних помилок.
OPcache тримає в спільній памʼяті вже скомпільовані опкоди, прибираючи парсинг і компіляцію з кожного запиту — це і є основний виграш; preload (PHP 7.4+) додатково тримає звʼязані класи фреймворку, а JIT (PHP 8.0+, вимкнений за замовчуванням через `opcache.jit_buffer_size=0`) компілює гарячі опкоди в машинний код і майже нічого не дає типовому веб-застосунку, бо той упирається в I/O, а не в CPU.
PHP звільняє памʼять через підрахунок посилань, а окремий збирач циклів періодично прибирає обʼєкти, які посилаються одне на одного.
Під FPM памʼять прибирає смерть процесу наприкінці запиту, у worker mode цього прибирання немає: усе, що лишилось у статиці, контейнері чи реєстрі слухачів, накопичується. Робочий набір це гігієна стану між ітераціями, явний запуск збирача циклів за порогом і перезапуск воркера за лімітом памʼяті або кількості запитів.
Core PHP питають на кожній співбесіді, навіть коли вакансія суто на Laravel. Найчастіше валяться на приведенні типів і на тому, як PHP тримає памʼять у довгих воркерах.