PHP
PHP — це мова, на якій працює велика частка вебу, і та, про яку судять за версією, баченою востаннє у 2012 році. Сучасний PHP має типи, енуми й суворий режим; що не змінилося — це модель виконання, і саме вона пояснює майже все інше. На цій сторінці — життєвий цикл запиту, який вигляд мова має насправді сьогодні, Laravel проти Symfony і те, де PHP-проєкти зазвичай ламаються.
- Модель
- Один запит на воркер
- Стан
- Поза процесом
- Стандарти
- PSR, від PHP-FIG
Що насправді відбувається, коли приходить запит
PHP майже завжди запускають як PHP-FPM: пул робочих процесів за вебсервером. Запит віддають вільному воркеру, той будує весь застосунок з нуля, віддає відповідь — і викидає кожен створений об’єкт, змінну й з’єднання. Наступний запит отримує воркер у тому самому порожньому стані. Це називають моделлю shared-nothing, і це найважливіше, що варто розуміти про цю мову.
Плюс у тому, що цілий клас помилок просто не може існувати. Витік об’єкта, зіпсована глобальна змінна, з’єднання в поламаному стані — усе це викидається за мілісекунди, тож запит здатен отруїти лише сам себе. Масштабування так само прямолінійне: воркери не ділять нічого, тож нарощування потужності означає нарощування воркерів, а падіння забирає один запит, а не сервер.
Ціна в тому, що не виживає нічого. Немає ані вартого уваги кешу всередині процесу, ані пулу з’єднань усередині застосунку, ані фонового потоку, що працює після надсилання відповіді, ані спільного лічильника. Кожне з цього доводиться виносити назовні: стан — у базу даних, кеш — у Redis чи Memcached, фонову роботу — у чергу з окремим процесом-споживачем. Це не милиця, а задум: саме тому PHP-застосунки зазвичай горизонтально масштабовані від початку, тоді як інші стеки треба такими робити свідомо.
Тепер існує й другий спосіб запускати PHP, і варто знати про нього до того, як він знадобиться. Довгоживучі сервери — Swoole, RoadRunner, FrankenPHP і Laravel Octane, що їх обгортає, — піднімають застосунок один раз і далі обслуговують багато запитів із того самого процесу. Пропускна здатність різко зростає, бо зникає ініціалізація. Разом із нею зникає й страхувальна сітка: витеклий об’єкт тепер тече весь час життя процесу, а шматок стану, залишений на синглтоні, видно в запиті наступного користувача. Це реальний клас безпекових помилок, і саме тому такий режим — свідома оптимізація, а не типовий вибір.
Мова, яку пам’ятають, і мова, яка існує
PHP 8 — типізована мова, коли ви цього просите. Параметри, значення, що повертаються, властивості й константи класів приймають оголошення типів; можна виразити об’єднання й перетини; а declare(strict_types=1) на початку файлу перетворює оголошення з підказок для приведення на справжні перевірки. Без цього рядка передача рядка "5" у параметр int тихо його конвертує. З ним ви отримаєте TypeError. Ставте його в кожен файл: сучасна екосистема PHP на це розраховує, а статичний аналіз значно корисніший, коли може довіряти сигнатурам.
Енуми
Фіксований набір випадків як справжній тип, із методами й необов’язковим базовим значенням — заміна константам класів і «магічним рядкам».
Readonly та просування
Просування властивостей у конструкторі прибирає шаблонний код;
readonlyробить властивість записуваною раз — так об’єкти-значення перестають випадково мутувати.match
Вираз, а не інструкція: повертає значення, порівнює суворо й кидає виняток, якщо нічого не збіглося, — три вади
switchвиправлено одразу.Іменовані аргументи
Виклик за іменем параметра з пропуском необов’язкових. Робить виклик із трьома булевими читабельним — і робить імена параметрів частиною вашого публічного API.
Атрибути
Структуровані метадані на класах і методах, які читає рефлексія. Маршрути, правила валідації й ORM-мапінг переїхали сюди з коментарів у doc-блоках.
Файбери
Функції, що перериваються, — примітив під асинхронними бібліотеками. Ви майже ніколи не писатимете їх прямо; ви користуватиметеся бібліотекою, яка це робить.
Дві речі мають бути в кожному серйозному PHP-проєкті, і жодна з них не постачається з мовою. Composer — це менеджер залежностей і, через PSR-4, автозавантажувач: ви не напишете жодного require для власних класів. Статичний аналіз, PHPStan чи Psalm, читає ваші оголошення типів і знаходить null, що доходить до виклику методу, раніше за користувача; обидва працюють рівнями, тож наявна кодова база може заходити на них по щаблю, а не одним неможливим ривком.
Про JIT-компілятор, доданий у PHP 8, варто говорити чесно. Він помітно пришвидшує довгі числові обчислення й майже не впливає на типову обробку вебзапиту, бо там панує ввід-вивід і база даних, а не арифметика. Якщо ваш PHP повільний, відповідь не в JIT — вона в логу запитів.
Laravel, Symfony і стандарти під ними
Майже ніхто не починає з порожньої теки. Поле тримають два фреймворки, і вибір між ними — це вибір, скільки фреймворк вирішує за вас. Під обома лежить PHP-FIG зі стандартами PSR: PSR-4 для автозавантаження, PSR-7 і PSR-15 для HTTP-повідомлень і middleware, PSR-12 для стилю коду, PSR-11 для контейнерів. Саме цей спільний шар робить так, що бібліотека кешу чи логер, написані під один фреймворк, зазвичай приживаються в іншому.
| Laravel | Symfony | |
|---|---|---|
| Позиція | Вирішує за вас. Один ідіоматичний спосіб для більшості речей, і він доступний одразу. | Дає вам компоненти. Ви збираєте й налаштовуєте; менше мається на увазі. |
| Доступ до даних | Eloquent, ORM у стилі active record: модель — це рядок, і писати швидко. | Doctrine, ORM у стилі data mapper: сутності нічого не знають про базу даних. |
| Де доречний | Продуктові команди, що швидко постачають; малі й середні команди; широка стандартна бібліотека. | Довгоживучі корпоративні системи, складні домени, команди, яким потрібні явні межі. |
| Головний ризик | Зручність заманює бізнес-логіку в моделі й контролери, доки ніщо не піддається тестуванню. | Церемонія й обсяг конфігурації гальмують маленьку команду без жодної віддачі. |
Чесне резюме: типовій продуктовій команді Laravel швидше дає робочу функційність, а ризик — архітектурний дрейф; для системи, яку банк експлуатуватиме ще дванадцять років, явність Symfony варта додаткової церемонії. Обидва підтримуються, обидва достатньо швидкі, а різниця між доброю й поганою кодовою базою в кожному з них — це дисципліна щодо того, де живе бізнес-логіка, а не фреймворк.
Розмова з базою даних
Репутація PHP щодо SQL-ін’єкцій виросла зі справжньої епохи запитів, склеєних із рядків, і виправлення є стандартом уже понад десятиліття: підготовлені вирази. Підготовлений вираз надсилає форму запиту й значення окремо, тож значення ніколи не прочитають як синтаксис. Кожен ORM і білдер запитів в екосистемі використовує їх під капотом. Єдиний спосіб повернути вразливість — вручну вставити змінну в SQL, і саме тому будь-який сирий запит зі знаком $ усередині заслуговує на другий погляд на рев’ю.
Ще дві звички відрізняють кодову базу, яка переживає зростання, від тієї, яка не переживає. Загортайте будь-яку послідовність записів, що має або вся вдатися, або вся скасуватися, у транзакцію — і тримайте транзакцію короткою: транзакція, відкрита на час HTTP-виклику до платіжного провайдера, триматиме рядки заблокованими рівно стільки, скільки той провайдер гальмує. І тримайте міграції під контролем версій поруч із кодом, якому вони потрібні, щоб деплой і зміна схеми рухалися разом; схема, що розійшлася з кодом, — це саме той збій, який перетворює звичайний реліз на аварію.
Як це виявляється в реальній поставці
Оскільки воркер не може пережити свою відповідь, усе повільне має піти з запиту. Надсилання листа, генерація PDF, виклик стороннього API, зміна розміру зображення — усе це йде в чергу, а споживає окремий довгоживучий процес-воркер. Обидва великі фреймворки постачають це з коробки, і це не просунута техніка: це стандартний спосіб робити фонову роботу в PHP, а запит, що виконує щось із цього всередині себе, — це сторінка, про яку користувачі казатимуть «іноді вона просто зависає».
Розгортання підпорядковане тій самій логіці. Реліз підміняє код і скидає OPcache, бо воркери досі тримають скомпільований байт-код попередньої версії; забудьте цей крок — і отримаєте плутаний стан, коли частина запитів виконує новий код, а частина старий. Конфігурація береться з оточення, ніколи з закомітованого файлу. Сесії не можуть жити на локальному диску воркера, щойно серверів стало більше ніж один, — інакше другий запит користувача потрапить на інший сервер і виявить, що той вийшов із системи.
Де це вироджується
- Бізнес-логіка всередині контролерів і моделей — доки правило можна перевірити лише через HTTP-запит.
- Запити N+1, сховані за зручним зв’язком ORM, невидимі, доки в таблиці не з’являться справжні дані.
- Повільна робота всередині запиту — листи, PDF, виклики до сторонніх сервісів — замість черги.
- Файли без
strict_types: оголошення типів приводять значення замість перевірки й дають хибну впевненість. - Сирий SQL, зібраний конкатенацією, — єдиний спосіб, що лишився, написати ін’єкцію в сучасному PHP.
- Сесії чи завантажені файли на локальному диску: працює на одному сервері й тихо ламається на двох.
- Перехід на довгоживучий сервер заради швидкості без ревізії того, який стан синглтони тепер тримають між користувачами.
Коли це застосовувати
Застосовуйте, коли
- Вебзастосунки й API, де робота на запит коротка, а трафік масштабується горизонтально.
- Команди, яким потрібен робочий продукт швидко: фреймворк, ORM, черга й адмінка приходять одним пакетом.
- Контентні й електронно-комерційні системи, де глибокі й хостинг, і ринок фахівців, і екосистема.
- Наявні PHP-системи: осучаснена база на PHP 8 зі статичним аналізом — краща ставка, ніж переписування.
Уникайте, коли
- Довгі з’єднання — WebSocket, стримінг, чат, — де модель «процес на запит» опирається на кожному кроці.
- Важкі обчислення й конвеєри даних: середовище не для цього, а екосистема — в іншому місці.
- Усе, що потребує справжньої паралельності всередині процесу, — стандартна модель виконання її не дає.
- Тримати стан на синглтоні після переходу на Octane, Swoole чи RoadRunner — це витік даних між користувачами.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.