---
title: "PHP на сервері: FPM, Swoole, воркери та асинхронні середовища | DevSense"
description: "Вивчаємо архітектуру оточення PHP: порівнюємо PHP-FPM, довготривалі сервери додатків (Swoole, RoadRunner, FrankenPHP) та асинхронні цикли подій ReactPHP/AMPHP."
faq:
  - question: "У чому різниця між PHP-FPM та Swoole?"
    answer: "PHP-FPM запускає новий процес або очищає стан процесу для кожного вхідного запиту, звільняючи пам'ять після його завершення. Swoole — це довготривалий сервер додатків, який завантажує код додатку в пам'ять один раз і обробляє запити без перезавантаження середовища."
  - question: "Що таке витік пам'яті в довготривалих процесах PHP?"
    answer: "Оскільки процеси не завершуються після кожного запиту, будь-які статичні властивості, синглтони або глобальні змінні, що накопичують дані, постійно зростатимуть в обсязі, що призведе до вичерпання ліміту пам'яті та падіння воркера."
  - question: "Як RoadRunner керує процесами PHP?"
    answer: "RoadRunner написаний на Go і виконує роль балансувальника навантаження та диспетчера процесів. Він спілкується з довготривалими PHP-воркерами за бінарним протоколом Goridge, виносячи обробку HTTP, gRPC та черг назовні."
  - question: "Чому блокуючі функції «вішають» ReactPHP або AMPHP?"
    answer: "ReactPHP та AMPHP працюють в однопоточному циклі подій (event loop). Будь-який блокуючий виклик (наприклад, `sleep()` або синхронний запит до БД) повністю зупиняє потік виконання, заморожуючи сервер для всіх інших клієнтів."
---

# PHP на сервері: FPM, Swoole, воркери та цикли подій

Багато розробників досі вважають, що PHP вміє працювати тільки за класичною схемою «share-nothing» (без спільного використання ресурсів), де стан скидається, а всі змінні знищуються відразу після віддачі сторінки користувачеві. Проте сучасний PHP успішно тримає високонавантажені WebSockets-з'єднання, обробляє тисячі HTTP-запитів на секунду в одному потоці за допомогою Go-воркерів і функціонує в асинхронних подієвих циклах. Але якщо запустити такий додаток, спроектований за старими лекалами, звичайний витік пам'яті в статичній властивості може миттєво обрушити весь ваш продакшн-кластер.

З появою Swoole, RoadRunner, FrankenPHP та ReactPHP мова PHP вийшла далеко за рамки класичного PHP-FPM. Але довготривалі середовища принесли нові ризики — витоки пам'яті, взаємне забруднення стану між користувачами та блокування потоку введення-виведення (I/O), що паралізують асинхронний цикл.

> [!IMPORTANT]
> Сучасний PHP більше не прив'язаний до однієї моделі виконання. Вибір правильного середовища вимагає розуміння різниці між короткоживучими запитами, постійними воркерами та асинхронними циклами подій.

---

## Зміст
* [PHP-FPM (FastCGI Process Manager)](#php-fpm)
* [Довготривалі сервери (Swoole та Laravel Octane)](#swoole)
* [RoadRunner (Диспетчер на Go)](#roadrunner)
* [FrankenPHP (Воркери на базі Caddy)](#frankenphp)
* [Асинхронні рушії (ReactPHP та AMPHP)](#event-loop)
* [Витоки пам'яті: прихований убивця](#memory-leaks)
* [Часті помилки](#common-mistakes)
* [Практичні рецепти](#practical-recipes)
* [🧠 Питання для самоперевірки](#self-check)

---

<a id="php-fpm"></a>
## PHP-FPM (FastCGI Process Manager)

PHP-FPM — класичний, перевірений часом менеджер процесів. Веб-сервер nginx приймає вхідний HTTP-трафік і перенаправляє запити до файлів `.php` на воркери FPM через UNIX-сокет або TCP-порт.

```nginx
# /etc/nginx/sites-available/default
location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
```

* **Принцип роботи**: Кожен процес-воркер обробляє строго один запит за раз, віддає відповідь клієнту, скидає всі змінні стану і повертається в пул вільних воркерів.
* **Чому це важливо**: Повністю відсутній ризик накопичення витоків пам'яті між запитами. Якщо скрипт падає помилково, процес перезапускається з чистого аркуша.
* **Обмеження**: Високі накладні витрати на ініціалізацію (наприклад, фреймворки Laravel або Symfony змушені повністю завантажуватися з диска при кожному новому запиті).

---

<a id="swoole"></a>
## Довготривалі сервери (Swoole та Laravel Octane)

Swoole — це розширення на C, що перетворює PHP на високопродуктивний мережевий сервер з підтримкою корутин.

```php
// app/Servers/HttpServer.php
$server = new Swoole\Http\Server("127.0.0.1", 9501);
$server->on("request", function ($request, $response) {
    $response->header("Content-Type", "text/plain");
    $response->end("Hello World");
});
$server->start();
```

* **Принцип роботи**: Код PHP зчитується та ініціалізується в пам'яті **один раз** при старті сервера. Усі наступні запити обробляються вже запущеними процесами в оперативній пам'яті.
* **Чому це важливо**: Позбавлення від ініціалізації фреймворку на кожному запиті дає приріст продуктивності до 10 разів.
* **Обмеження**: Будь-який витік пам'яті в синглтонах або статичних властивостях класів накопичуватиметься, доки процес не впаде через брак пам'яті.

---

<a id="roadrunner"></a>
## RoadRunner (Диспетчер на Go)

RoadRunner — високопродуктивний сервер додатків PHP та менеджер процесів, написаний на Go.

```yaml
# .rr.yaml
server:
  command: "php worker.php"
http:
  address: "0.0.0.0:8080"
  pool:
    num_workers: 4
```

* **Принцип роботи**: Шар на Go приймає HTTP-запити, з'єднання WebSockets або виклики gRPC, а потім передає їх довготривалим PHP-воркерам за бінарним протоколом Goridge.
* **Чому це важливо**: Дозволяє поєднувати надійність багатопотокового Go як диспетчера та простоту розробки на PHP.

---

<a id="frankenphp"></a>
## FrankenPHP (Воркери на базі Caddy)

FrankenPHP — сучасний сервер додатків PHP, інтегрований безпосередньо у веб-сервер Caddy. Його «режим воркера» (worker mode) утримує запущений додаток в оперативній пам'яті.

* **Чому це важливо**: Спрощує розгортання до запуску одного бінарного файлу. З коробки підтримує HTTP/1.1, HTTP/2, HTTP/3 та автоматичну генерацію SSL-сертифікатів Let's Encrypt.

---

<a id="event-loop"></a>
## Асинхронні рушії (ReactPHP та AMPHP)

На відміну від Swoole, ReactPHP та AMPHP реалізують асинхронність на чистому PHP з використанням механізму спільної багатозадачності без додаткових розширень C.

```php
// app/Servers/ReactServer.php
require __DIR__ . '/vendor/autoload.php';

$loop = React\EventLoop\Loop::get();
$loop->addPeriodicTimer(1.0, function () {
    echo "Tick\n";
});
$loop->run();
```

* **Чому це важливо**: Ідеальний інструмент для написання мікросервісів, чутливих до введення-виведення (I/O-bound), серверів черг повідомлень та проксі-серверів.
* **Обмеження**: Категорично заборонено використовувати блокуючий код (наприклад, стандартні запити PDO до бази даних або `file_get_contents`). Усі запити до БД та API мають здійснюватися через асинхронні клієнти.

---

<a id="memory-leaks"></a>
## Витоки пам'яті: прихований убивця

У довготривалих середовищах виконання (Swoole, RoadRunner, режим воркера FrankenPHP, CLI-демони) пам'ять після завершення обробки HTTP-запиту автоматично не звільняється.

```php
// app/Services/DataLeak.php
class DataLeak
{
    private static array $cache = [];

    public function cacheRequest(array $data)
    {
        // ❌ Витік пам'яті! Цей масив зростатиме з кожним запитом
        self::$cache[] = $data; 
    }
}
```

> [!NOTE]
> Щоб уникнути витоків пам'яті, необхідно обмежувати кеші в оперативній пам'яті (наприклад, алгоритмом витіснення LRU), повністю відмовитися від статичних властивостей класів для зберігання даних поточного запиту та використовувати інструменти контролю пам'яті на кшталт `memory_get_usage(true)`.

---

<a id="common-mistakes"></a>
## ⚠️ Часті помилки

### 1. Блокування циклу подій у ReactPHP / AMPHP
Виклик будь-якої синхронної блокуючої функції повністю зупиняє подієвий цикл, паралізуючи роботу сервера для всіх інших користувачів.

```php
// app/Http/Handler.php
// ❌ Небезпечно: зупиняє роботу всього сервера на час зовнішнього запиту
$data = file_get_contents("http://external-api.com/data");

// ✅ Правильний підхід
// Використовуйте асинхронні клієнти:
$browser = new React\Http\Browser();
$browser->get("http://external-api.com/data")->then(function ($response) {
    // Асинхронна обробка відповіді
});
```

### 2. Збереження даних запиту у статичних властивостях
Запис сесії або облікових даних користувача у статичні властивості призведе до того, че інші користувачі зможуть отримати доступ до чужих приватних даних.

```php
// app/Services/AuthService.php
// ❌ Небезпечно: Користувач Б може отримати сесію Користувача А!
class AuthService
{
    public static ?User $currentUser = null;
}
```

---

<a id="practical-recipes"></a>
## Практичні рецепти

### Безпечне перезавантаження воркерів
Для боротьби з можливими мікровитоками пам'яті налаштуйте періодичний перезапуск воркерів після досягнення певного ліміту оброблених запитів або порогу зайнятої оперативної пам'яті.

```ini
# /etc/php/8.3/fpm/pool.d/www.conf (PHP-FPM)
; Перезапускати воркер кожні 500 запитів
pm.max_requests = 500
```

```yaml
# .rr.yaml (RoadRunner)
http:
  pool:
    # Перезапускати воркер при перевищенні ліміту в 1000 запитів або 100МБ пам'яті
    max_jobs: 1000
    allocate_timeout: 60s
    destroy_timeout: 60s
    supervisor:
      watch_interval: 1s
      max_worker_memory: 100 # МБ
```

---

## 🧠 Питання для самоперевірки

1. **Правильно чи ні?** У середовищі PHP-FPM статичні властивості класів можуть стати причиною витоку пам'яті між запитами різних користувачів.
2. Що станеться, якщо виконати виклик `sleep(5)` всередині обробника запиту в ReactPHP?
3. Яким чином режим воркера FrankenPHP досягає приросту швидкості порівняно з PHP-FPM?
4. Для чого використовується директива `pm.max_requests` у налаштуваннях PHP-FPM?

<details>
<summary><b>Показати відповіді</b></summary>

1. **Неправильно.** У PHP-FPM вся пам'ять процесу повністю очищується і скидається між HTTP-запитами, тому статичні властивості не зберігаються.
2. Це заблокує однопоточний подієвий цикл на 5 секунд. Протягом цього часу сервер не зможе приймати нові підключення та відповідати іншим користувачам.
3. Він завантажує код додатку та бібліотеки фреймворку в пам'ять один раз, позбавляючи сервер потреби зчитувати файли з диска та повторно компілювати їх при кожному запиті.
4. Вона перезапускає процес-воркер PHP-FPM після обробки певної кількості запитів, що дозволяє нівелювати витоки пам'яті в нестабільних розширеннях C або коді сторонніх пакетів.
</details>
