Senior: питання на співбесіді з теми «PHP 8+»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Лінивий об'єкт створюється одразу, але його справжня ініціалізація (дорогий конструктор, запит до бази, з'єднання) відкладається до моменту, коли об'єкт справді знадобиться. PHP 8.4 зробив це вбудованою можливістю мови через Reflection.
Два види:
- Ghost - сам об'єкт потрібного класу, який ініціалізується «на місці» при першому зверненні до його стану:
$reflector = new ReflectionClass(Connection::class);
$connection = $reflector->newLazyGhost(function (Connection $object): void {
$object->__construct(config('database.dsn')); // підключення лише тепер
});
// ... з'єднання ще не встановлено
$connection->query('SELECT 1'); // перше звернення до властивостей - ініціалізація
- Proxy - об'єкт-замісник, який при першому зверненні створює справжній екземпляр через фабрику й далі перенаправляє йому всі звернення:
$service = $reflector->newLazyProxy(fn () => $container->make(HeavyService::class));
Важливий нюанс: ініціалізацію запускає звернення до властивостей об'єкта. Виклик методу, який не чіпає стан, лінивий об'єкт не ініціалізує.
Де це корисно:
- Контейнери залежностей: сервіс, впроваджений у контролер, але не використаний у цьому запиті, не створюється взагалі.
- ORM: зв'язана сутність завантажується з бази лише при зверненні до неї (так Doctrine давно робила через згенеровані класи-проксі - тепер це вміє мова).
- Важкі клієнти: з'єднання з Redis, S3, сторонніми API, яке потрібне лише в частині запитів.
Чим краще за рукописні проксі: не треба генерувати код класів-проксі, об'єкт лишається того самого класу (працюють instanceof і типи), а final-класи теж можна зробити лінивими.
Обмеження: ініціалізація може відбутися в несподіваний момент (наприклад, при var_dump чи серіалізації), а помилка в ініціалізаторі виникне там, де об'єкт уперше використали, а не там, де його створили.
Оновлення PHP - це не зміна однієї цифри в Dockerfile. Ламаються зазвичай не нові можливості, а зміни поведінки й залежності.
1. Прочитати посібник з міграції на php.net для кожної версії між поточною й цільовою: несумісні зміни, застаріле, змінені функції.
2. Перевірити залежності:
composer why-not php 8.5
composer outdated --direct
Пакет, що не підтримує нову версію PHP, - найчастіший блокер. Оновити, знайти заміну чи дочекатися релізу.
3. Автоматичні інструменти:
- Rector - автоматично переписує код під нову версію (набори правил
UpgradeToPhp84тощо) і прибирає застарілі конструкції. - PHPStan/Psalm з налаштованою цільовою версією PHP.
- PHPCompatibility для PHP_CodeSniffer - знаходить несумісний код.
4. Тести на обох версіях. Матриця в CI: поточна й нова версія PHP. Поки обидві зелені, можна оновлюватися поступово.
5. Застарілі попередження - як помилки в тестах. Deprecation сьогодні - помилка в наступній мажорній версії. PHPUnit/Pest можна налаштувати так, щоб E_DEPRECATED валили тест.
6. Поступове розгортання: спершу стейджинг, потім частина продакшн-трафіку (канарка), моніторинг помилок і швидкодії, і лише потім усе.
Типові проблеми при оновленні:
- PHP 8.0: порівняння чисел з рядками,
TypeErrorзамість попереджень у вбудованих функціях. - PHP 8.1: передача
nullу параметри вбудованих функцій, що не приймають null (deprecated). - PHP 8.2: динамічні властивості й інтерполяція
${var}- deprecated. - PHP 8.4: неявно nullable параметри - deprecated.
Не відкладати надовго: кожна версія PHP отримує виправлення безпеки обмежений час. Оновлюватися щороку на одну версію значно легше, ніж стрибати через три.
Застарілі (deprecated) можливості ще працюють, але видають попередження й будуть прибрані в наступній мажорній версії. Найпоширеніші в реальному коді:
Динамічні властивості (PHP 8.2):
$user = new User();
$user->temporaryFlag = true; // Deprecated: Creation of dynamic property
Оголосіть властивість явно. Для класів, яким це справді потрібно, - #[\AllowDynamicProperties]. stdClass і класи з __get/__set не зачіпаються.
Інтерполяція ${var} у рядках (8.2):
"Привіт, ${name}"; // застаріло
"Привіт, {$name}"; // правильно
Неявно nullable параметри (8.4):
function find(Criteria $criteria = null) {} // застаріло
function find(?Criteria $criteria = null) {} // правильно
null у параметрах вбудованих функцій, що не приймають null (8.1):
strlen(null); // Deprecated
trim($request->input('name')); // якщо поле відсутнє - null
trim($request->input('name') ?? '');
Інше:
utf8_encode()/utf8_decode()(8.2) →mb_convert_encoding().- Часткова підтримка callable-рядків на кшталт
"self::method"(8.2). - Неявне приведення
floatз дробовою частиною доintу ключах і операціях (8.1). E_STRICTяк константа (8.4).
Як знайти все одразу:
error_reporting(E_ALL)у тестах і перетворення deprecation на помилки тестів.- Laravel пише застарілі в окремий канал логів (
LOG_DEPRECATIONS_CHANNEL) - увімкнути його на стейджингу й подивитися, що сиплеться під реальним трафіком. - Rector автоматично виправляє більшість із цього списку.
Чим раніше прибрати застаріле, тим дешевше наступне оновлення PHP.