РозробкаБудь-який рівень

PHP

PHP — це мова, на якій працює велика частка вебу, і та, про яку судять за версією, баченою востаннє у 2012 році. Сучасний PHP має типи, енуми й суворий режим; що не змінилося — це модель виконання, і саме вона пояснює майже все інше. На цій сторінці — життєвий цикл запиту, який вигляд мова має насправді сьогодні, Laravel проти Symfony і те, де PHP-проєкти зазвичай ламаються.

Модель
Один запит на воркер
Стан
Поза процесом
Стандарти
PSR, від PHP-FIG

Що насправді відбувається, коли приходить запит

PHP майже завжди запускають як PHP-FPM: пул робочих процесів за вебсервером. Запит віддають вільному воркеру, той будує весь застосунок з нуля, віддає відповідь — і викидає кожен створений об’єкт, змінну й з’єднання. Наступний запит отримує воркер у тому самому порожньому стані. Це називають моделлю shared-nothing, і це найважливіше, що варто розуміти про цю мову.

Воркер PHP піднімає застосунок, обробляє рівно один запит, віддає відповідь — і викидає все, що створив: кожен об’єкт, кожну змінну, кожне з’єднання. Наступний запит починається в порожньому процесі, тож у пам’яті не переноситься нічого, а все, що має пережити запит, живе в базі даних, кеші чи сховищі сесій. Саме тому PHP масштабують додаванням воркерів, витік у межах одного запиту нешкідливий, а кеш усередині процесу тут не дає нічого.

Плюс у тому, що цілий клас помилок просто не може існувати. Витік об’єкта, зіпсована глобальна змінна, з’єднання в поламаному стані — усе це викидається за мілісекунди, тож запит здатен отруїти лише сам себе. Масштабування так само прямолінійне: воркери не ділять нічого, тож нарощування потужності означає нарощування воркерів, а падіння забирає один запит, а не сервер.

Ціна в тому, що не виживає нічого. Немає ані вартого уваги кешу всередині процесу, ані пулу з’єднань усередині застосунку, ані фонового потоку, що працює після надсилання відповіді, ані спільного лічильника. Кожне з цього доводиться виносити назовні: стан — у базу даних, кеш — у 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 для контейнерів. Саме цей спільний шар робить так, що бібліотека кешу чи логер, написані під один фреймворк, зазвичай приживаються в іншому.

LaravelSymfony
ПозиціяВирішує за вас. Один ідіоматичний спосіб для більшості речей, і він доступний одразу.Дає вам компоненти. Ви збираєте й налаштовуєте; менше мається на увазі.
Доступ до даних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 — це витік даних між користувачами.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.