PHP
PHP — серверна мова, що обробляє кожен вебзапит у воркері, який стартує порожнім і забуває все, щойно відповідь надіслано. Цей один факт — модель shared-nothing — пояснює, чому стан живе в Redis, чому повільна робота йде в чергу і чому стандартно налаштований сервер обробляє п’ять запитів одночасно. Сама мова змінилася більше, ніж її репутація: PHP 8 має типи, енуми, суворий режим і нову велику можливість щолистопада. Сторінка починається з того, що забуває воркер, і дванадцяти наслідків; далі — пул воркерів і його арифметика, мова такою, як вона є у 8.5, фреймворки й стандарти, база даних і виклики, які варто шукати в коді, довгоживучі сервери та п’ять задокументованих кейсів.
- Поточна версія
- 8.5 · 8.6 — 19 лист. 2026
- Модель
- Один запит на воркер, потім забуто
- Підтримка
- 2 роки активна + 2 безпекова
Що забуває воркер PHP — і дванадцять наслідків
Сервіс на Java, Node.js чи Go стартує один раз і далі тижнями обслуговує запити з того самого процесу. Усе, що він поклав у пам’ять, там і лишається: кеш, пул з’єднань, лічильник, таймер. Воркер PHP робить навпаки. Він будує застосунок з нуля для кожного запиту і викидає все це, щойно відповідь надіслано. Майже все, що дивує розробника, який прийшов з іншої мови, випливає з цієї однієї різниці. Ось дванадцять наслідків — для кожного звичка, яку він ламає, і те, що PHP робить натомість. Наступні розділи посилаються на рядки за номерами.
| # | Звичка з довгоживучого середовища | Що відбувається в PHP | Що робити натомість |
|---|---|---|---|
| 1 | Кешувати значення в статичній властивості чи глобальній змінній | Воно зникає з надсиланням відповіді; наступний запит обчислює його знову | Спільний кеш поза воркером: APCu на одному сервері, Redis на кількох |
| 2 | Тримати сесії в пам’яті | Типовий обробник пише файли сесій на локальний диск сервера | Сесії в Redis чи базі даних, щойно з’являється другий сервер |
| 3 | Писати завантажені файли на локальний диск | Наступний запит може потрапити на сервер, де цього файлу немає | Об’єктне сховище, а ключ файлу — у базі даних |
| 4 | Рахувати в пам’яті — ліміт запитів, лічильник | Кожен воркер рахує сам, і кожен підрахунок помирає зі своїм запитом | Атомарний лічильник у Redis (INCR) чи базі даних |
| 5 | Тримати пул з’єднань усередині застосунку | Жоден пул не виживає; кожен запит відкриває власне з’єднання | Пулер перед базою даних (PgBouncer, ProxySQL) |
| 6 | Робити повільну роботу після відповіді — лист, PDF, вебхук | Воркер зайнятий, доки не закінчить, і не може взяти наступний запит | Черга, яку споживає окремий довгоживучий процес-воркер |
| 7 | Запустити таймер чи фоновий потік | Між запитами немає живого процесу, який би його виконав | Cron, що щохвилини викликає планувальник фреймворку |
| 8 | Тримати відкритий WebSocket | Воркер, якого тримає одне з’єднання, втрачений для всіх інших користувачів | Окремий сервер реального часу: Laravel Reverb, Mercure, сервіс на Node чи Go |
| 9 | Платити за старт один раз | Фреймворк завантажується на кожен запит | OPcache, попереднє завантаження і кешовані конфіг, маршрути й контейнер |
| 10 | Спостерігати, як витік пам’яті росте годинами | Витік помирає зі своїм запитом — крім воркерів черги, що живуть годинами | Перезапускати воркери черги після N задач чи за лімітом пам’яті |
| 11 | Деплоїти, копіюючи файли поверх робочого коду | OPcache далі віддає байт-код старих файлів | Перемкнутися на нову теку релізу одним кроком, потім перезавантажити PHP-FPM |
| 12 | Брати стільки запитів одночасно, скільки приносить трафік | Пул обслуговує рівно pm.max_children одночасно — 5 у стандартному конфігу | Розрахувати пул від пам’яті й тримати кожен запит коротким |
Один запит, один чистий процес — і пул зі стелею
PHP майже ніколи не говорить із мережею сам. Вебсервер — nginx, Apache чи Caddy — приймає з’єднання й передає запит протоколом FastCGI до PHP-FPM, менеджера процесів FastCGI. FPM тримає пул робочих процесів. Вільний воркер бере запит, завантажує фреймворк, виконує ваш код, пише відповідь — і викидає кожен створений об’єкт, змінну й з’єднання. Наступний запит отримує воркер у тому самому порожньому стані. Це модель shared-nothing: запити навмисно не ділять у пам’яті нічого.
Плюс у тому, що цілий клас помилок просто не може існувати. Витік об’єкта, зіпсована глобальна змінна, з’єднання в поламаному стані — усе це викидається за мілісекунди, тож запит здатен отруїти лише сам себе. Фатальна помилка забирає один запит, а не сервер. Масштабування так само прямолінійне: воркери нічого не ділять, тож більше потужності — це більше воркерів, а більше воркерів на більшій кількості серверів не потребують жодної координації, крім бази даних.
Пул — це стеля
Рядок 12 — той, що спричиняє аварії. Воркер обробляє один запит за раз, тож розмір пулу — це кількість запитів, які PHP може обслуговувати одночасно, а стандартний конфіг FPM задає pm.max_children = 5. Свіже встановлення обробляє п’ять одночасних запитів; шостий чекає в черзі. Правильне число береться з пам’яті, а не з процесора: виміряйте, скільки займає один воркер під реальним трафіком, поділіть на це пам’ять, яку можна віддати PHP, і лишіть місце для решти на машині. За 4 ГБ для PHP і 64 МБ на воркер це близько шістдесяти воркерів.
Тоді в пулу є стеля пропускної здатності, і це проста арифметика: кількість воркерів, поділена на час одного запиту. Шістдесят воркерів по 100 мс на запит обслуговують 600 запитів за секунду. Тепер нехай платіжний провайдер сповільниться до 2 секунд. Кожен запит, що його викликає, тримає свій воркер 2 секунди, ті самі шістдесят воркерів обслуговують 30 запитів за секунду, а все понад це стає в чергу. Схема показує це на пулі з восьми. Графік процесора весь цей час показує майже вільний сервер, бо очікування не коштує процесорного часу, — тому цю аварію так часто хибно читають як «замало серверів».
| Що ви бачите | Що це означає | Куди дивитися |
|---|---|---|
server reached pm.max_children setting у лозі FPM | Усі воркери були зайняті; запити стали в чергу | Спершу повільні запити, потім розмір пулу |
| 504 Gateway Timeout від nginx | Запит чекав чи виконувався довше, ніж дозволяє вебсервер | Найповільніша залежність цього ендпоінта |
| 502 Bad Gateway | FPM відмовив у з’єднанні або воркер помер посеред запиту | Лог FPM: повна черга, падіння, вбитий воркер |
Allowed memory size of 134217728 bytes exhausted | Один запит уперся в memory_limit (типово 128 МБ) | Запит, що вантажить усю таблицю; читайте потоком чи частинами |
Maximum execution time of 30 seconds exceeded | Сам PHP обчислював 30 секунд — очікування не рахується | Цикл, а не повільний API |
Мова, яку пам’ятають, і мова, яка існує
Більшість думок про PHP склалася на PHP 5: жодних типів, порівняння без логіки й проблема зі швидкістю, настільки серйозна, що Facebook написав власний компілятор. PHP 7.0, у грудні 2015 року, переписав рушій і став до двох разів швидшим за 5.6, а кожен реліз відтоді додавав щось до системи типів. Тепер реліз виходить щолистопада, отримує два роки виправлень помилок і ще два — виправлень безпеки, а потім — нічого. Таблиця перелічує, що кожен із них додав такого, що ви справді зустрінете в коді, і як довго він отримує патчі.
| Версія | Випущено | Що ви зустрінете в коді | Патчі безпеки до |
|---|---|---|---|
| 7.0 | груд. 2015 | Скалярні типи параметрів і повернення, ??, рушій до двох разів швидший | Завершено січ. 2019 |
| 7.4 | лист. 2019 | Типізовані властивості, стрілкові функції fn, preloading | Завершено лист. 2022 |
| 8.0 | лист. 2020 | Типи-об’єднання, match, іменовані аргументи, атрибути, просування в конструкторі, ?->, розумніші порівняння, JIT | Завершено лист. 2023 |
| 8.1 | лист. 2021 | Енуми, властивості readonly, файбери, never, first-class callables | Завершено груд. 2025 |
| 8.2 | груд. 2022 | Readonly-класи, DNF-типи, динамічні властивості застарілі | 31 груд. 2026 |
| 8.3 | лист. 2023 | Типізовані константи класів, #[\Override], json_validate() | 31 груд. 2027 |
| 8.4 | лист. 2024 | Хуки властивостей, асиметрична видимість, ліниві об’єкти, array_find() | 31 груд. 2028 |
| 8.5 | лист. 2025 | Оператор |>, розширення URI, clone() з новими значеннями, #[\NoDiscard], array_first() | 31 груд. 2029 |
| 8.6 | Очікується 19 лист. 2026 | Часткове застосування функцій, значення за замовчуванням для readonly, сесійні cookie з HttpOnly і SameSite=Lax за замовчуванням | 31 груд. 2030, за політикою |
Суворі типи: вирішує файл, що викликає
Оголошення типів у PHP мають два режими. За замовчуванням вони приводять значення: передайте рядок "5" у параметр int, і PHP тихо перетворить його на 5. З declare(strict_types=1) угорі файлу вони перевіряють: той самий виклик кидає TypeError. Частина, у якій помиляються майже всі, — котрий файл вирішує. Словами посібника, суворі типи «застосовуються до викликів функцій, зроблених у файлі з увімкненою суворою типізацією, а не до функцій, оголошених у цьому файлі». Функція, написана в суворому файлі, однаково приводить значення, коли її викликають із файлу без цього рядка.
Практичне правило випливає саме: ставте цей рядок у кожен файл, разом із тестами й шаблонами, і хай інструмент перевірки стилю за цим стежить. Бібліотека не може зробити власні функції суворими для тих, хто їх викликає. Її єдиний захист — статичний аналіз на боці того, хто викликає. PHPStan і Psalm читають оголошення й знаходять null, що доходить до виклику методу, раніше за користувача. Обидва працюють рівнями — у PHPStan їх одинадцять, від 0 до 10, з релізу 2.0 у листопаді 2024 року, — тож наявна кодова база може підніматися на рівень за раз, а не одним неможливим ривком.
Порівняння, які PHP 8 виправив, і ті, що лишилися
Репутацію «дивних порівнянь» заробив нестрогий оператор ==, що перетворює обидва боки, перш ніж порівняти. PHP 8 змінив одне правило: число, яке порівнюють із рядком, що не схожий на число, тепер порівнюється як рядок. Це прибрало найгірші випадки. Решта лишилася, і саме через неї ===, що порівнює тип і значення без перетворень, — типовий вибір у сучасному коді.
| Вираз | PHP 7.4 | PHP 8 | Чому |
|---|---|---|---|
| 0 == "foo" | true | false | "foo" не число, тож 8 порівнює "0" і "foo" як рядки |
| in_array("abc", [0]) | true | false | Те саме правило; in_array нестрогий, якщо третій аргумент не true |
| "1" == "01" | true | true | Обидва схожі на числа, тож порівнюються як числа |
| "10" == "1e1" | true | true | "1e1" — експоненційний запис числа 10 |
| null == false | true | true | Обидва перетворюються на false |
Про JITJust-In-Time compilation — компіляція під час роботиКомпіляція коду тоді, коли програма вже працює: середовище спершу виконує його як є, дивиться, які ділянки гарячі, і на ходу перетворює саме їх на машинний код. Тому перші секунди після старту повільніші за наступні хвилини.Усі терміни-компілятор, доданий у 8.0, варто говорити чесно. Він помітно пришвидшує довгі числові обчислення й майже не впливає на типові вебзапити, бо вебзапит проводить час в очікуванні бази даних і мережі, а не в арифметиці. Якщо PHP-сторінка повільна, відповідь — у лозі запитів і в рядку 6, а не в JIT.
Laravel, Symfony і стандарти під ними
Майже ніхто не починає PHP-застосунок із порожньої теки. Composer, менеджер залежностей, ставить пакети з Packagist і записує точні обрані версії в composer.lock; комітьте цей файл і на сервері запускайте composer install, ніколи composer update. Composer також генерує автозавантажувач, тож ви не напишете жодного require для власних класів. Автозавантажувач працює завдяки стандарту: PHP-FIG, група авторів фреймворків і бібліотек, публікує PSR — PHP Standard Recommendations, — що дають коду з різних проєктів стикуватися між собою.
| Стандарт | Що він фіксує | Де ви його зустрінете |
|---|---|---|
| PSR-4 | Як ім’я класу відповідає шляху до файлу | Секція autoload кожного composer.json |
| PSR-3 | Один інтерфейс логера | Monolog, який використовують Laravel і Symfony |
| PSR-7, PSR-17 | Об’єкти запиту й відповіді та фабрики для них | HTTP-бібліотеки, Slim, Mezzio |
| PSR-15 | Middleware й обробники запитів | Middleware, написаний раз для багатьох фреймворків |
| PSR-11 | Отримання сервісу з контейнера | Кожен контейнер впровадження залежностей |
| PSR-6, PSR-16 | Детальний і простий інтерфейси кешу | Бібліотеки кешу, що працюють у будь-якому фреймворку |
| PSR-14, PSR-18 | Розсилання подій; HTTP-клієнт | SDK, що дають обрати HTTP-клієнт |
| PSR-20 | «Зараз» як сервіс, який можна підмінити | Тести, що заморожують час |
| PER Coding Style | Оформлення коду; замінив PSR-12 | PHP-CS-Fixer, PHP_CodeSniffer, Laravel Pint |
Над цим шаром поле тримають два фреймворки, і вибір між ними — це вибір, скільки фреймворк вирішує за вас.
| Laravel | Symfony | |
|---|---|---|
| Позиція | Вирішує за вас: один ідіоматичний спосіб для більшості речей, доступний одразу | Дає компоненти, які ви збираєте й налаштовуєте; менше мається на увазі |
| Доступ до даних | Eloquent, ORM у стилі active record: модель — це рядок | Doctrine, ORM у стилі data mapper: сутності нічого не знають про базу даних |
| Релізи | Мажорний реліз щороку; виправлення помилок 18 місяців, безпеки — 2 роки | Мінорний реліз кожні пів року; LTS (x.4) раз на два роки, 3 роки виправлень і 4 — безпеки |
| Де доречний | Продуктові команди, що швидко постачають фічі; малі й середні команди | Довгоживучі системи, складні домени, команди, яким потрібні явні межі |
| Головний ризик | Зручність заманює бізнес-логіку в моделі й контролери, доки ніщо не піддається тестуванню | Церемонія й конфігурація гальмують маленьку команду без жодної віддачі |
Чесне резюме: типовій продуктовій команді Laravel швидше дає робочу фічу, а ризик — архітектурний дрейф. Для системи, яку банк експлуатуватиме ще дванадцять років, явність Symfony варта церемонії. Обидва підтримуються й достатньо швидкі. Різниця між доброю і поганою кодовою базою в кожному — це дисципліна щодо того, де живе бізнес-логіка, а не фреймворк.
База даних і шість викликів, які варто пошукати перед кожним релізом
Репутація PHP щодо SQL-ін’єкцій виросла зі справжньої епохи запитів, склеєних із рядків, а виправлення є стандартом уже понад десятиліття: підготовлені вирази. Підготовлений вираз надсилає форму запиту й значення окремо, тож значення ніколи не прочитають як SQL. Кожен ORMObject-Relational Mapping — обʼєкти замість SQLБібліотека, що підставляє обʼєкти мови там, де в базі лежать рядки таблиці, — тож SQL ви майже не пишете. Зручність коштує контролю: згенерований запит буває значно важчим за той, який ви написали б самі, і зʼясовується це вже під навантаженням.Докладніше в статті →Усі терміни і будівник запитів в екосистемі використовує їх під капотом. Єдиний шлях назад до вразливості — вручну вставити змінну в SQL.
Ще дві звички відрізняють кодову базу, що переживає зростання, від тієї, що ні. Тримайте транзакції короткими: транзакція, відкрита на час HTTP-виклику до платіжного провайдера, блокує свої рядки рівно стільки, скільки той провайдер гальмує, і ще й тримає воркер (рядок 12). І тримайте міграції в системі контролю версій поруч із кодом, якому вони потрібні, щоб деплой і зміна схеми рухалися разом.
Ін’єкція — не єдині двері. Більшість серйозних вразливостей PHP походить від короткого списку викликів, безпечних із вашими даними й небезпечних із чужими. Кожен легко знайти текстовим пошуком, тож цей список — дешевий крок рев’ю перед кожним релізом:
| Що шукати | Що йде не так зі стороннім вводом | Натомість |
|---|---|---|
| unserialize( | Ввід обирає, які класи створюються, і їхні магічні методи виконуються — об’єктна ін’єкція | json_decode(); або allowed_classes => false |
| "SELECT … $ | SQL-ін’єкція | Зв’язані параметри |
| include $ | Виконує файл, який назвав зловмисник | Фіксована мапа дозволених імен |
| exec( · shell_exec( · system( | Ін’єкція команд | Масив аргументів (Symfony Process) або escapeshellarg() |
| md5( · sha1( на паролях | Зламуються офлайн за години | password_hash() і password_verify() |
| == на токенах і підписах | Нестроге порівняння й витік через час | hash_equals() |
Довгоживучий PHP: що worker mode повертає і що забирає
Тепер є й другий спосіб запускати PHP. Сервери застосунків завантажують фреймворк один раз і далі обслуговують багато запитів із того самого процесу. FrankenPHP, побудований на вебсервері Caddy й офіційно підтримуваний PHP Foundation з травня 2025 року, називає це worker mode. RoadRunner робить це з процесу на Go; Swoole й OpenSwoole — як розширення PHP із корутинами; Laravel Octane обгортає всі три, а Symfony працює на них через свій компонент Runtime.
Повертає це рядки 1, 5 і 9. Ініціалізація оплачується один раз, тож запит, що більшість часу завантажував фреймворк, стає в кілька разів швидшим. Кеш усередині процесу й постійне з’єднання знову щось означають. А забирає це страховку моделі shared-nothing. Пам’ять більше не стирається між запитами, тож усе, що запит лишив на довгоживучому об’єкті, видно наступному — а наступний належить іншому користувачеві.
Код, написаний під FPM, ніколи на це не тестувався, бо під FPM цього не могло статися. Перед переходом перевірте п’ять речей:
| Що шукати | Що стається у worker mode | Виправлення |
|---|---|---|
| Дані запиту на синглтоні чи в статичній властивості | Запит наступного користувача їх читає | Передавати аргументом або скидати після кожного запиту |
| Довгоживучий сервіс, що отримав запит, користувача чи конфіг у конструкторі | Він тримає копію першого запиту весь час життя воркера | Отримувати на кожен запит або впроваджувати резолвер замість значення |
| Масив, що працює як кеш | Росте, доки воркер не вб’ють за пам’ять | Кеш з обмеженим розміром або APCu чи Redis |
Глобальні налаштування на запит — setlocale(), date_default_timezone_set(), ini_set() | Лишаються встановленими для наступного запиту | Задавати на кожен запит або взагалі ні |
| З’єднання з базою, відкрите на старті | Сервер закриває його після тайм-ауту простою | Перепідключення при збої; більшість фреймворків у цьому режимі робить це за вас |
П’ять кейсів і те, чого в кожному бракувало
Кожен кейс нижче задокументовано у власних оголошеннях, баг-трекері й поштовій розсилці PHP або в опублікованому дослідженні, що його знайшло. Разом вони покривають рушій, стик із вебсервером, репозиторій вихідного коду, вікно підтримки й шар фреймворку.
| Кейс | Що сталося | Чого бракувало |
|---|---|---|
| HipHop, HHVM і PHP 7, 2010–2019 | Facebook збудував власні рушії PHP; PHP 7 наздогнав, і HHVM відмовився від PHP | Швидкість в еталонному рушії |
| CVE-2019-11043, жовт. 2019 | Перенесення рядка в URL ламало розбір шляху в nginx і давало переписати налаштування FPM | Відповідальний за стик вебсервера й FPM |
| Бекдор у php-src, бер. 2021 | Два коміти від імені ключових розробників додали бекдор для віддаленого виконання | Надійна ідентичність на комітах — спіймало рев’ю |
| CVE-2024-4577, черв. 2024 | М’який дефіс ставав прапорцем командного рядка для PHP-CGI у Windows | Підтримувана версія — і жодного режиму CGI в інтернеті |
| Laravel APP_KEY, 2018–2025 | 260 000 ключів на публічному GitHub; 600 застосунків, відкритих для віддаленого виконання коду | Сканування секретів, ротація — і жодного unserialize() за одним ключем |
Перший кейс — причина, з якої розділ про мову починається із заяви про швидкість. У лютому 2010 року Facebook оголосив HipHop, що перекладав PHP на C++, бо стандартний рушій не встигав за його трафіком, а згодом замінив його на HHVM — віртуальну машину з JIT. Кілька років найшвидший спосіб запускати PHP був не від самого PHP. Відповідь прийшла зсередини проєкту: PHP 7.0, випущений 3 грудня 2015 року, перебудував ключові структури даних рушія й став, словами оголошення про реліз, «до двох разів швидшим за PHP 5.6». У вересні 2018 року команда HHVM оголосила, що припиняє підтримку PHP, і HHVM 4.0, у лютому 2019 року, виконує лише Hack — власну мову Facebook. Команди, що перейшли на HHVM заради швидкості, перейшли назад. Форк, швидший за оригінал, — це ставка на дорожню карту однієї компанії.
Третій кейс — про власний код мови. 28 березня 2021 року в php-src на власному git-сервері PHP з’явилися два коміти, підписані іменами Расмуса Лердорфа, що створив PHP, і Нікіти Попова, одного з найактивніших розробників. Їх позначили як виправлення друкарських помилок. Вони додавали код, що виконував будь-який PHP, надісланий у заголовку User-Agentt — зверніть увагу на друге t, — який починався зі слова «zerodium». Розробники, що читали потік комітів, помітили перший за кілька годин і відкотили; другий повернув той самий код і теж був відкочений. Наступного дня проєкт зробив основним свій репозиторій на GitHub, з обов’язковою двофакторною автентифікацією для права запису. Пізніший висновок: git-сервер, імовірно, не зламували взагалі — найпевніше, витекла база користувачів за ним, тож зловмисник пушив зі справжніми обліковими даними. Рев’ю спрацювало. Ідентичність — ні.
П’ятий кейс поєднує перший рядок таблиці пошуку із секретом. Laravel шифрує та інші значення ключем застосунку APP_KEY, а його функція decrypt() за замовчуванням десеріалізує те, що розшифрувала. Тож хто має ключ, може зашифрувати об’єкт на свій вибір, передати його застосунку — і той його десеріалізує: віддалене виконання коду. Для старих версій Laravel це була CVECommon Vulnerabilities and Exposures — публічний номер відомої вразливостіПублічний ідентифікатор відомої вади безпеки, як-от CVE-2021-44228 для Log4Shell, щоб постачальники, сканери й бюлетені мали на увазі той самий баг. Номер каже, що вада існує; наскільки вона небезпечна саме для вас, залежить від того, чи працює у вас уражена версія і чи відкрита назовні вражена частина.Усі терміни-2018-15133. У липні 2025 року GitGuardian і Synacktiv опублікували, що це означає в масштабі: вони витягли понад 260 000 значень APP_KEY з публічних репозиторіїв GitHub, закомічених між 2018 роком і травнем 2025-го, і знайшли понад 600 живих застосунків, які можна було ними захопити. Ключ потрапляв у репозиторій разом із файлом .env чи вставленим у конфіг і ніколи не змінювався. Усі заходи були звичайними — сканер секретів на кожен пуш, ротація, коли ключ витік, — а урок дизайну старий: ніколи не дозволяйте одному секрету бути єдиним, що стоїть між інтернетом і unserialize().
Де він вироджується
Модель PHP поблажлива, і це водночас її сила і спосіб, у який вона відмовляє. Витік, погана глобальна змінна чи повільний цикл коштують одного запиту й забуваються, тож кодова база може нести чимало безладу, перш ніж щось помітно зламається. Коли ж ламається, це майже завжди один із дванадцяти рядків, який проігнорували, або один із шести викликів, якому довірили чужі дані:
- Повільна робота всередині запиту — листи, PDF, сторонні виклики — замість черги (рядок 6).
- Пул, залишений на стандартних п’яти воркерах або розрахований від процесора замість пам’яті (рядок 12).
- Жодного тайм-ауту реального часу на вихідних викликах, тож одна повільна залежність тримає всі воркери.
- Сесії чи завантаження на локальному диску: працює на одному сервері й тихо ламається на двох (рядки 2 і 3).
- Запити N+1зайвий запит на кожен рядокКласична пастка ORM: один запит дістає список, а потім цикл по ньому тихо робить ще по запиту на кожен елемент — сто рядків перетворюються на сто одне звернення до бази. Лікується тим, щоб попросити повʼязані дані одразу, а не рядок за рядком.Докладніше в статті →Усі терміни за зручним зв’язком, невидимі, доки в таблиці не з’являться справжні дані.
- Файли без
strict_types: оголошення типів приводять значення замість перевірки й дають хибну впевненість. - Бізнес-логіка всередині контролерів і моделей — доки правило можна перевірити лише через HTTP-запит.
unserialize()на будь-чому, що пройшло мережею, або ключ фреймворку, закомічений у репозиторій.- Worker mode, увімкнений заради швидкості без перевірки, що довгоживучі об’єкти тепер тримають між користувачами.
- Версія за межею останньої колонки таблиці, де наступний бюлетень безпеки не матиме патча.
Чесна межа — сама модель. Запит, що має лишатися відкритим, — чат, живий дашборд, стрим, — увесь час бореться з задумом «один запит — один воркер», і правильна відповідь зазвичай — другий, маленький сервіс на іншому середовищі поруч із PHP-застосунком, а не PHP-застосунок, вигнутий під цю форму. Те саме з важкими обчисленнями й конвеєрами даних: середовище здатне їх виконати, але бібліотеки, інструменти й люди — деінде. Worker mode звужує розрив, не закриваючи його, і купує свою швидкість саме тією страховкою, що робила PHP простим для міркувань.
Практична перевірка займає п’ять хвилин. Запустіть php -v на продакшн-сервері й знайдіть рядок у таблиці версій. Запустіть composer audit, що перелічує встановлені пакети з відомими бюлетенями безпеки. Відкрийте сторінку статусу FPM (pm.status_path) під реальним трафіком і порівняйте «active processes» з pm.max_children: якщо в пік вони рівні, рядок 12 уже ваше вузьке місце.
Коли це застосовувати
Застосовуйте, коли
- Вебзастосунки й API, де робота на запит коротка, а трафік масштабується горизонтально.
- Команди, яким потрібен робочий продукт швидко: фреймворк, ORM, черга й адмінка приходять одним пакетом.
- Контентні й електронно-комерційні системи, де глибокі й хостинг, і ринок фахівців, і екосистема.
- Наявні PHP-системи: кодова база на PHP 8 зі статичним аналізом — краща ставка, ніж переписування.
Уникайте, коли
- Довгі з’єднання — WebSocketпостійний двосторонній звʼязокЗʼєднання, що лишається відкритим в обидва боки: на відміну від HTTP, сервер може надіслати повідомлення сам, не чекаючи запиту. На ньому тримаються чати й живі стрічки — і більшість того, що виглядає як сторінка, що оновлюється сама.Усі терміни, стримінг, чат, — де «один воркер на запит» опирається на кожному кроці.
- Важкі обчислення й конвеєри даних: середовище здатне на це, але екосистема — деінде.
- Worker mode на коді, написаному під FPM, до перевірки того, що його синглтони тримають між користувачами.
- Версія після дати завершення патчів безпеки — або PHP-CGI, відкритий в інтернет узагалі.
Джерела й метод
На чому стоїть ця стаття
- 16 джерел
- 4 задокументовані збої
- Перевірено 13 жовтня 2026 р.
Збої, на яких вчиться стаття
чотири з «П’ять кейсів і те, чого в кожному бракувало»- ДокументаціяPHP Sec Bug #78599 — env_path_info underflow in fpm_main.c can lead to RCE (відкривається в новій вкладці)PHP Bug Tracker · 09.2019
- Статтяneex/phuip-fpizdam — Exploit for CVE-2019-11043 (відкривається в новій вкладці)GitHub · 22.10.2019
- ПостмортемNikita Popov — Changes to Git commit workflow (відкривається в новій вкладці)php.internals · 29.03.2021
- ПостмортемNikita Popov — Update on git.php.net incident (відкривається в новій вкладці)php.internals · 06.04.2021
- СтаттяSecurity Alert: CVE-2024-4577 — PHP CGI Argument Injection Vulnerability (відкривається в новій вкладці)DEVCORE · 06.06.2024
- СтаттяUpdate: CVE-2024-4577 quickly weaponized to distribute “TellYouThePass” Ransomware (відкривається в новій вкладці)Imperva · 06.2024
- ДослідженняExploiting Public APP_KEY Leaks to Achieve RCE in Hundreds of Laravel Applications (відкривається в новій вкладці)GitGuardian · 07.2025
Практики й походження термінів
що забуває воркер, система типів і таблиця версій- ДокументаціяPHP Manual — Type declarations (відкривається в новій вкладці)php.net
- ДокументаціяPHP Manual — set_time_limit (відкривається в новій вкладці)php.net
- ДокументаціяPHP — Supported Versions (відкривається в новій вкладці)php.net
- ДокументаціяPHP — Unsupported Branches (відкривається в новій вкладці)php.net
- СтандартPHP Standards Recommendations (відкривається в новій вкладці)PHP-FIG
- ДокументаціяLaravel — Release Notes (відкривається в новій вкладці)laravel.com
- ДокументаціяSymfony releases (відкривається в новій вкладці)symfony.com
Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.