---
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 връзки, обработва хиляди заявки в секунда в една нишка с помощта на 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. Записване на данни от заявки в статични свойства
Записването на сесия или потребителски данни в статични свойства ще доведе до изтичане на лична информация към други concurrent заявки.

```php
// app/Services/AuthService.php
// ❌ Опасно: Потребител Б може да получи достъп до сесията на Потребител А!
class AuthService
{
    public static ?User $currentUser = null;
}
```

---

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

### Безопасно рестартиране на воркери
За справяне с възможни микроизтичания на памет, настройте периодично рестартиране на воркерите при достигане на определен брой обработени заявки или определен обем заета RAM памет.

```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>
