Собирает PHP-расширение folk.so из Rust-кода. Folk — это application server для PHP, аналог Swoole/RoadRunner/FrankenPHP, но реализованный как нативное PHP-расширение на Rust.
folk-builder — CLI-утилита. Она генерирует Cargo-проект с выбранными плагинами и компилирует его в folk.so (cdylib). Результат — файл расширения, который PHP загружает через extension=folk.so.
# Установить Rust если нет:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Установить folk-builder:
cargo install folk-buildergit clone https://github.com/Folk-Project/folk-builder
cd folk-builder
cargo build --release
./target/release/folk-builder --help- Rust 1.88+ —
rustup update - PHP 8.2+ с
php-configв PATH - clang/libclang — для bindgen (генерация FFI-биндингов к PHP)
- pkg-config — для поиска PHP headers
На Ubuntu/Debian:
apt install php8.4-dev pkg-config libclang-dev clangНа macOS (Homebrew):
brew install php@8.4Для запуска нескольких worker-потоков (count > 1 в folk.toml) PHP должен быть собран с Thread Safety (ZTS):
- Docker: используйте образ
php:8.4-zts— ZTS уже включён - Ubuntu:
apt install php8.4-zts(если есть в репо) - Из исходников:
./configure --enable-zts && make && make install - Проверить:
php -r "echo PHP_ZTS;"— должно вывести1
С NTS (обычным) PHP Folk работает, но только с одним worker-потоком.
Файл folk.build.toml:
[build]
output = "folk" # Имя расширения (-> folk.so)
[[plugin]]
crate_name = "folk-plugin-http" # HTTP-сервер (axum + hyper + tokio)
version = "0.2"
config_key = "http"folk-builder build --config folk.build.toml --output-dir .Результат: файл folk.so в текущей директории.
php -d extension=./folk.so -r "echo folk_version();"
# folk-ext 0.2.0Файл folk.toml:
[workers]
script = "server.php" # PHP-скрипт воркера
count = 4 # Кол-во worker-потоков (нужен ZTS)
max_jobs = 100000 # Рестарт после N запросов
[http]
listen = "0.0.0.0:8080"
[log]
filter = "warn" # trace, debug, info, warn, error<?php
// server.php
require __DIR__ . '/vendor/autoload.php';
use Folk\Sdk\Worker\WorkerLoop;
use Folk\Sdk\Http\HttpModeHandler;
use Folk\Sdk\Http\HttpRequest;
use Folk\Sdk\Http\HttpResponse;
class MyHandler implements HttpModeHandler
{
public function handle(HttpRequest $request): HttpResponse
{
return new HttpResponse(
status: 200,
headers: ['Content-Type' => 'application/json'],
body: json_encode(['status' => 'ok', 'uri' => $request->uri]),
);
}
}
// ZTS worker threads уже инициализированы — пропускаем start().
if (!folk_is_worker_thread()) {
$server = new Folk\Server('folk.toml');
$server->start();
}
$loop = new WorkerLoop();
$loop->registerHttpHandler(new MyHandler());
$loop->run();Запуск:
composer require folk/sdk
php -d extension=./folk.so server.phpcomposer require folk/laravelФайл server.php:
<?php
require __DIR__ . '/vendor/autoload.php';
if (!folk_is_worker_thread()) {
$server = new Folk\Server('folk.toml');
$server->start();
}
// Laravel bootstrap.
$app = require __DIR__ . '/bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
// WorkerLoop с Laravel handler (регистрируется через ServiceProvider).
$loop = new \Folk\Sdk\Worker\WorkerLoop();
if (isset($GLOBALS['folk_worker_boot_hook'])) {
($GLOBALS['folk_worker_boot_hook'])($loop);
}
$loop->run();php -d extension=./folk.so server.php
# Сервер слушает на :8080Не нужно ставить Rust и PHP ZTS локально — всё собирается в Docker.
# === Сборка folk.so ===
FROM php:8.4-zts AS builder
# Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain 1.88.0
ENV PATH="/root/.cargo/bin:${PATH}"
# Системные зависимости для сборки
RUN apt-get update && apt-get install -y \
pkg-config libclang-dev clang protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
# Собираем folk-builder и folk.so
WORKDIR /build
RUN cargo install folk-builder
COPY folk.build.toml folk.build.toml
RUN folk-builder build --config folk.build.toml --output-dir /build/
# === Runtime ===
FROM php:8.4-zts
RUN apt-get update && apt-get install -y \
unzip curl sqlite3 \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& rm -rf /var/lib/apt/lists/*
# folk.so
COPY --from=builder /build/folk.so /usr/local/lib/php/extensions/folk.so
RUN echo "extension=/usr/local/lib/php/extensions/folk.so" > /usr/local/etc/php/conf.d/folk.ini
# Приложение
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY . .
RUN composer install --no-dev --optimize-autoloader
EXPOSE 8080
CMD ["php", "server.php"]Для разработки код не копируется в образ — монтируется через volume. Образ содержит только runtime + folk.so.
docker/app.Dockerfile:
# Stage 1: Build folk.so
FROM php:8.4-zts AS builder
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.88.0
ENV PATH="/root/.cargo/bin:${PATH}"
RUN apt-get update && apt-get install -y \
pkg-config libclang-dev clang protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
RUN cargo install folk-builder
COPY folk.build.toml folk.build.toml
RUN folk-builder build --config folk.build.toml --output-dir /build/
# Stage 2: PHP ZTS runtime
FROM php:8.4-zts
RUN apt-get update && apt-get install -y \
unzip curl sqlite3 \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY --from=builder /build/folk.so /usr/local/lib/php/extensions/folk.so
RUN echo "extension=/usr/local/lib/php/extensions/folk.so" > /usr/local/etc/php/conf.d/folk.ini
WORKDIR /app
EXPOSE 8080
CMD ["php", "server.php"]compose.yaml:
services:
app:
build:
context: .
dockerfile: docker/app.Dockerfile
ports:
- "8080:8080"
- "9090:9090"
working_dir: /app
environment:
- APP_ENV=local
- APP_DEBUG=true
- DB_CONNECTION=sqlite
- REDIS_HOST=redis
- CACHE_STORE=redis
- SESSION_DRIVER=redis
- QUEUE_CONNECTION=redis
- RUST_LOG=info
volumes:
- ./:/app
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"Запуск:
# Собрать образ (folk.so компилируется один раз)
docker compose build
# Установить PHP-зависимости (выполняется внутри контейнера)
docker compose run --rm app composer require folk/laravel
# Запустить
docker compose upКод приложения (app/, routes/, config/, resources/) монтируется через volume — изменения применяются при рестарте воркера (по max_jobs или docker compose restart app).
folk.build.toml (все плагины):
[build]
output = "folk"
[[plugin]]
crate_name = "folk-plugin-http"
version = "0.2"
config_key = "http"
[[plugin]]
crate_name = "folk-plugin-jobs"
version = "0.2"
config_key = "jobs"
[[plugin]]
crate_name = "folk-plugin-grpc"
version = "0.2"
config_key = "grpc"
[[plugin]]
crate_name = "folk-plugin-metrics"
version = "0.2"
config_key = "metrics"
[[plugin]]
crate_name = "folk-plugin-process"
version = "0.2"
config_key = "process"folk.toml:
[server]
shutdown_timeout = "10s"
[workers]
script = "server.php"
count = 4
max_jobs = 100000
[http]
listen = "0.0.0.0:8080"
[jobs]
driver = "memory"
[[jobs.queues]]
name = "default"
concurrency = 2
[metrics]
listen = "0.0.0.0:9090"
[log]
filter = "info"
format = "text"| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
output |
string | да | Имя расширения (без .so) |
folk_ext_path |
string | нет | Путь к локальному folk-ext (для разработки) |
folk_api_path |
string | нет | Путь к локальному folk-api (для разработки) |
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
crate_name |
string | да | Имя Rust-крейта |
version |
string | нет | Версия с crates.io (по умолчанию "0.2") |
path |
string | нет | Локальный путь (вместо crates.io) |
git |
string | нет | Git URL (вместо crates.io) |
config_key |
string | да | Ключ секции в folk.toml |
| Плагин | Крейт | Версия | Описание |
|---|---|---|---|
| HTTP | folk-plugin-http |
0.2 | HTTP/1.1 сервер (axum) |
| gRPC | folk-plugin-grpc |
0.2 | gRPC сервер (tonic) |
| Jobs | folk-plugin-jobs |
0.2 | Очередь фоновых задач (memory/redis) |
| Metrics | folk-plugin-metrics |
0.2 | Prometheus /metrics и /health |
| Process | folk-plugin-process |
0.2 | Supervisor сайдкар-процессов |
PHP загружает folk.so
|
+-- Main thread (worker #1)
| Rust вызывает PHP handler напрямую через call_user_function
| Данные передаются как PHP-массивы (zval), без JSON
|
+-- folk-tokio thread
| axum HTTP-сервер, пул воркеров, реестр плагинов
|
+-- ZTS workers #2..N (если count > 1 и PHP собран с ZTS)
Каждый — отдельный PHP-контекст со своим Zend Engine
Docker, Mac (OrbStack), 4 ZTS workers:
| Нагрузка | Req/sec |
|---|---|
Raw JSON ({"status":"ok"}) |
~55,000 |
| Laravel Livewire starter | ~4,200 |
Стабильно держит 500 concurrent connections без socket errors.
MIT