From f458ebff58ce46321d49e414aff91bd1f4a9373f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 8 Apr 2026 17:11:57 +0200 Subject: [PATCH 1/4] docs: improver worker docs, and add internals docs --- docs/internals.md | 240 ++++++++++++++++++++++++++++++++++++++++++++++ docs/worker.md | 35 +++++++ 2 files changed, 275 insertions(+) create mode 100644 docs/internals.md diff --git a/docs/internals.md b/docs/internals.md new file mode 100644 index 0000000000..1a75a8b99d --- /dev/null +++ b/docs/internals.md @@ -0,0 +1,240 @@ +# Internals + +This document explains FrankenPHP's internal architecture, focusing on thread management, the state machine, and the CGO boundary between Go and C/PHP. + +## Overview + +FrankenPHP embeds the PHP interpreter directly into Go via CGO. Each PHP execution runs on a real POSIX thread (not a goroutine) because PHP's ZTS (Zend Thread Safety) model requires it. Go orchestrates these threads through a state machine, while C handles the PHP SAPI lifecycle. + +The main layers are: + +1. **Go layer** (`frankenphp.go`, `phpthread.go`, `threadworker.go`, `threadregular.go`, `scaling.go`): Thread pool management, request routing, auto-scaling +2. **C layer** (`frankenphp.c`): PHP SAPI implementation, script execution loop, superglobal management +3. **State machine** (`internal/state/`): Synchronization between Go goroutines and C threads + +## Thread Types + +### Main Thread (`phpmainthread.go`) + +The main PHP thread (`phpMainThread`) initializes the PHP runtime: + +1. Applies `php.ini` overrides +2. Takes a snapshot of the environment (`main_thread_env`) for sandboxing +3. Starts the PHP SAPI module +4. Signals readiness to the Go side + +It stays alive for the lifetime of the server. All other threads are started after it signals `Ready`. + +### Regular Threads (`threadregular.go`) + +Handle classic one-request-per-invocation PHP scripts. Each request: + +1. Receives a request via `requestChan` or the shared `regularRequestChan` +2. Returns the script filename from `beforeScriptExecution()` +3. The C layer executes the PHP script +4. `afterScriptExecution()` closes the request context + +### Worker Threads (`threadworker.go`) + +Keep a PHP script alive across multiple requests. The PHP script calls `frankenphp_handle_request()` in a loop: + +1. `beforeScriptExecution()` returns the worker script filename +2. The C layer starts executing the PHP script +3. The PHP script calls `frankenphp_handle_request()`, which calls `waitForWorkerRequest()` in Go +4. Go blocks until a request arrives, then sets up the request context +5. The PHP callback handles the request +6. `go_frankenphp_finish_worker_request()` cleans up the request context +7. The PHP script loops back to step 3 + +Worker threads are restarted when the script exits (exit code 0), with exponential backoff on failure. + +## Thread State Machine + +Each thread has a `ThreadState` (defined in `internal/state/state.go`) that governs its lifecycle. The state machine uses a `sync.RWMutex` for all state transitions and a channel-based subscriber pattern for blocking waits. + +### States + +``` +Lifecycle: Reserved → Booting → Inactive → Ready ⇄ (processing) + ↓ +Shutdown: ShuttingDown → Done → Reserved + ↑ +Restart: Restarting → Yielding → Ready + ↑ +Handler transition: TransitionRequested → TransitionInProgress → TransitionComplete +``` + +| State | Description | +|-------|-------------| +| `Reserved` | Thread slot allocated but not booted. Can be booted on demand. | +| `Booting` | Underlying POSIX thread is starting up. | +| `Inactive` | Thread is alive but has no handler assigned. Minimal memory footprint. | +| `Ready` | Thread has a handler and is ready to accept work. | +| `ShuttingDown` | Thread is shutting down. | +| `Done` | Thread has completely shut down. Transitions back to `Reserved` for potential reuse. | +| `Restarting` | Worker thread is being restarted (e.g., via admin API or file watcher). | +| `Yielding` | Worker thread has yielded control and is waiting to be re-activated. | +| `TransitionRequested` | A handler change has been requested from the Go side. | +| `TransitionInProgress` | The C thread has acknowledged the transition request. | +| `TransitionComplete` | The Go side has installed the new handler. | + +### Key Operations + +**`RequestSafeStateChange(nextState)`**: The primary way external goroutines request state changes. It: +- Atomically succeeds from `Ready` or `Inactive` (under mutex) +- Returns `false` immediately from `ShuttingDown`, `Done`, or `Reserved` +- Blocks and retries from any other state, waiting for `Ready`, `Inactive`, or `ShuttingDown` + +This guarantees mutual exclusion: only one of `shutdown()`, `setHandler()`, or `drainWorkerThreads()` can succeed at a time on a given thread. + +**`WaitFor(states...)`**: Blocks until the thread reaches one of the specified states. Uses a channel-based subscriber pattern so waiters are efficiently notified. + +**`Set(nextState)`**: Unconditional state change. Used by the thread itself (from C callbacks) to signal state transitions. + +**`CompareAndSwap(compareTo, swapTo)`**: Atomic compare-and-swap. Used for boot initialization. + +### Handler Transition Protocol + +When a thread needs to change its handler (e.g., from inactive to worker): + +``` +Go side (setHandler) C side (PHP thread) +───────────────── ───────────────── +RequestSafeStateChange( + TransitionRequested) +close(drainChan) + detects drain + Set(TransitionInProgress) +WaitFor(TransitionInProgress) + → unblocked WaitFor(TransitionComplete) +handler = newHandler +drainChan = make(chan struct{}) +Set(TransitionComplete) + → unblocked + newHandler.beforeScriptExecution() +``` + +This protocol ensures the handler pointer is never read and written concurrently. + +### Worker Restart Protocol + +When workers are restarted (e.g., via admin API): + +``` +Go side (RestartWorkers) C side (worker thread) +───────────────── ───────────────── +RequestSafeStateChange( + Restarting) +close(drainChan) + detects drain in waitForWorkerRequest() + returns false → PHP script exits + beforeScriptExecution(): + state is Restarting → + Set(Yielding) +WaitFor(Yielding) + → unblocked WaitFor(Ready, ShuttingDown) +drainChan = make(chan struct{}) +Set(Ready) + → unblocked + beforeScriptExecution() recurse: + state is Ready → normal execution +``` + +## CGO Boundary + +### Exported Go Functions + +C code calls Go functions via CGO exports. The main callbacks are: + +| Function | Called when | +|----------|-----------| +| `go_frankenphp_before_script_execution` | C loop needs the next script to execute | +| `go_frankenphp_after_script_execution` | PHP script has finished executing | +| `go_frankenphp_worker_handle_request_start` | Worker's `frankenphp_handle_request()` is called | +| `go_frankenphp_finish_worker_request` | Worker request handler has returned | +| `go_ub_write` | PHP produces output (`echo`, etc.) | +| `go_read_post` | PHP reads POST body (`php://input`) | +| `go_read_cookies` | PHP reads cookies | +| `go_write_headers` | PHP sends response headers | +| `go_sapi_flush` | PHP flushes output | +| `go_log_attrs` | PHP logs a structured message | + +All these functions receive a `threadIndex` parameter identifying the calling thread. This is a thread-local variable in C (`__thread uintptr_t thread_index`) set during thread initialization. + +### C Thread Main Loop + +Each PHP thread runs `php_thread()` in `frankenphp.c`: + +```c +while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { + php_request_startup(); + php_execute_script(&file_handle); + php_request_shutdown(); + go_frankenphp_after_script_execution(thread_index, exit_status); +} +``` + +Bailouts (fatal PHP errors) are caught by `zend_catch`, which marks the thread as unhealthy and forces cleanup. + +### Memory Management + +- **Go → C strings**: `C.CString()` allocates with `malloc()`. The C side is responsible for freeing (e.g., `frankenphp_free_request_context()` frees cookie data). +- **Go string pinning**: `thread.Pin()` / `thread.Unpin()` pins Go memory so C can safely reference it during script execution without copying. Unpinned after each script execution. +- **PHP memory**: Managed by Zend's memory manager (`emalloc`/`efree`). Automatically freed at request shutdown. + +## Auto-Scaling + +FrankenPHP can automatically scale the number of PHP threads based on demand (`scaling.go`). + +### Configuration + +- `num_threads`: Initial number of threads started at boot +- `max_threads`: Maximum number of threads allowed (includes auto-scaled) + +### Upscaling + +A single goroutine (`startUpscalingThreads`) reads from an unbuffered `scaleChan`: + +1. A request handler can't find an available thread +2. It sends the request context to `scaleChan` +3. The scaling goroutine checks: + - Has the request been stalled long enough? (minimum 5ms) + - Is CPU usage below the threshold? (80%) + - Is the thread limit reached? +4. If all checks pass, a new thread is booted and assigned + +### Downscaling + +A separate goroutine (`startDownScalingThreads`) periodically checks (every 5s) for idle auto-scaled threads. Threads idle longer than `maxIdleTime` (default 5s) are shut down, up to 10 per cycle. + +## Environment Sandboxing + +FrankenPHP sandboxes environment variables per-thread: + +1. At startup, the main thread snapshots `os.Environ()` into `main_thread_env` (a PHP `HashTable`) +2. Each request copies `main_thread_env` into `$_SERVER` +3. `frankenphp_putenv()` / `frankenphp_getenv()` use a thread-local `sandboxed_env` copy, preventing race conditions on the global environment +4. The sandboxed environment is reset between requests via `reset_sandboxed_environment()` + +## Request Flow (Regular Mode) + +1. HTTP request arrives at Caddy +2. FrankenPHP's Caddy module resolves the PHP script path +3. A `frankenPHPContext` is created with the request and script info +4. The context is sent to an available regular thread via `requestChan` +5. The thread's `beforeScriptExecution()` returns the script filename +6. The C layer executes the PHP script +7. During execution, Go callbacks handle I/O (`go_ub_write`, `go_read_post`, etc.) +8. After execution, `afterScriptExecution()` signals completion +9. The response is sent to the client + +## Request Flow (Worker Mode) + +1. HTTP request arrives at Caddy +2. FrankenPHP's Caddy module resolves the worker for this request +3. A `frankenPHPContext` is created +4. The context is sent to the worker's `requestChan` or a specific thread's `requestChan` +5. The worker thread's `waitForWorkerRequest()` receives it +6. PHP's `frankenphp_handle_request()` callback is invoked +7. After the callback returns, `go_frankenphp_finish_worker_request()` cleans up +8. The worker loops back to `waitForWorkerRequest()` diff --git a/docs/worker.md b/docs/worker.md index 65e7e3b0d9..69b461220f 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -168,3 +168,38 @@ $handler = static function () use ($workerServer) { // ... ``` + +Most superglobals (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `$_SERVER`, `$_REQUEST`) are automatically reset between requests. +However, **`$_ENV` is not reset between requests** for performance reasons. +This means that any modifications made to `$_ENV` during a request will persist and be visible to subsequent requests handled by the same worker thread. +Avoid storing request-specific or sensitive data in `$_ENV`. + +## State Persistence + +Because worker mode keeps the PHP process alive between requests, the following state persists across requests: + +- **Static variables**: Variables declared with `static` inside functions or methods retain their values between requests. +- **Class static properties**: Static properties on classes persist between requests. +- **Global variables**: Variables in the global scope of the worker script persist between requests. +- **In-memory caches**: Any data stored in memory (arrays, objects) outside the request handler persists. + +This is by design and is what makes worker mode fast. However, it requires attention to avoid unintended side effects: + +```php + Date: Wed, 8 Apr 2026 17:55:51 +0200 Subject: [PATCH 2/4] fix --- CONTRIBUTING.md | 4 +- docs/cn/extension-workers.md | 12 +++--- docs/cn/hot-reload.md | 6 +-- docs/cn/worker.md | 1 + docs/es/classic.md | 4 +- docs/es/compile.md | 10 ++--- docs/es/embed.md | 2 +- docs/es/extension-workers.md | 10 ++--- docs/es/extensions.md | 30 +++++++------- docs/es/known-issues.md | 12 +++--- docs/es/worker.md | 2 +- docs/extensions.md | 3 +- docs/fr/extension-workers.md | 12 +++--- docs/fr/hot-reload.md | 10 +++-- docs/fr/worker.md | 1 + docs/hot-reload.md | 2 +- docs/internals.md | 71 +++++++++++++++++---------------- docs/ja/extension-workers.md | 12 +++--- docs/ja/hot-reload.md | 6 +-- docs/ja/worker.md | 1 + docs/migrate.md | 16 ++++---- docs/pt-br/extension-workers.md | 12 +++--- docs/pt-br/hot-reload.md | 2 +- docs/pt-br/worker.md | 1 + docs/ru/extension-workers.md | 10 ++--- docs/ru/hot-reload.md | 2 +- docs/ru/worker.md | 1 + docs/tr/extension-workers.md | 11 ++--- docs/tr/hot-reload.md | 2 +- docs/tr/worker.md | 1 + 30 files changed, 139 insertions(+), 130 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 260d076c3a..c8f35ff678 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -299,7 +299,7 @@ The steps assume the following environment: 4. Debug Go files from CLion - - Right click on a *.go file in the Project view on the left + - Right click on a \*.go file in the Project view on the left - Override file type → C/C++ Now you can place breakpoints in C, C++ and Go files. @@ -348,7 +348,7 @@ Use GoLand for primary Go development, but the debugger cannot debug C code. To debug C files from GoLand -- Right click on a *.c file in the Project view on the left +- Right click on a \*.c file in the Project view on the left - Override file type → Go Now you can place breakpoints in C, C++ and Go files. diff --git a/docs/cn/extension-workers.md b/docs/cn/extension-workers.md index 2c2d0c645a..98cef730ef 100644 --- a/docs/cn/extension-workers.md +++ b/docs/cn/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP 提供了钩子,用于在生命周期的特定点执行 Go 代码。 -| 钩子类型 | 选项名称 | 签名 | 上下文与用例 | -| :------- | :--------------------------- | :----------------------- | :--------------------------------------------------- | -| **服务器** | `WithWorkerOnServerStartup` | `func()` | 全局设置。**只运行一次**。示例:连接到 NATS/Redis。 | -| **服务器** | `WithWorkerOnServerShutdown` | `func()` | 全局清理。**只运行一次**。示例:关闭共享连接。 | -| **线程** | `WithWorkerOnReady` | `func(threadID int)` | 每线程设置。在线程启动时调用。接收线程 ID。 | -| **线程** | `WithWorkerOnShutdown` | `func(threadID int)` | 每线程清理。接收线程 ID。 | +| 钩子类型 | 选项名称 | 签名 | 上下文与用例 | +| :--------- | :--------------------------- | :------------------- | :-------------------------------------------------- | +| **服务器** | `WithWorkerOnServerStartup` | `func()` | 全局设置。**只运行一次**。示例:连接到 NATS/Redis。 | +| **服务器** | `WithWorkerOnServerShutdown` | `func()` | 全局清理。**只运行一次**。示例:关闭共享连接。 | +| **线程** | `WithWorkerOnReady` | `func(threadID int)` | 每线程设置。在线程启动时调用。接收线程 ID。 | +| **线程** | `WithWorkerOnShutdown` | `func(threadID int)` | 每线程清理。接收线程 ID。 | ### 示例 diff --git a/docs/cn/hot-reload.md b/docs/cn/hot-reload.md index e83a6e5f36..ffca766dbe 100644 --- a/docs/cn/hot-reload.md +++ b/docs/cn/hot-reload.md @@ -25,7 +25,7 @@ FrankenPHP 包含一个内置的**热重载**功能,旨在极大改善开发 > > 此功能仅适用于**开发环境**。 > 请勿在生产环境中启用 `hot_reload`,因为此功能不安全(会暴露敏感的内部细节)并且会降低应用程序的速度。 -> + ```caddyfile localhost @@ -145,5 +145,5 @@ php_server { 4. **接收**:浏览器通过 JavaScript 库监听,接收 Mercure 事件。 5. **更新**: - - 如果检测到 **Idiomorph**,它会获取更新的内容并修改当前的 HTML 以匹配新状态,即时应用更改而不会丢失状态。 - - 否则,将调用 `window.location.reload()` 来刷新页面。 + - 如果检测到 **Idiomorph**,它会获取更新的内容并修改当前的 HTML 以匹配新状态,即时应用更改而不会丢失状态。 + - 否则,将调用 `window.location.reload()` 来刷新页面。 diff --git a/docs/cn/worker.md b/docs/cn/worker.md index 33a040bbac..f39dd4a9f1 100644 --- a/docs/cn/worker.md +++ b/docs/cn/worker.md @@ -184,3 +184,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/es/classic.md b/docs/es/classic.md index 1176723ac3..c597141c62 100644 --- a/docs/es/classic.md +++ b/docs/es/classic.md @@ -3,9 +3,9 @@ Sin ninguna configuración adicional, FrankenPHP opera en modo clásico. En este modo, FrankenPHP funciona como un servidor PHP tradicional, sirviendo directamente archivos PHP. Esto lo convierte en un reemplazo directo para PHP-FPM o Apache con mod_php. Al igual que Caddy, FrankenPHP acepta un número ilimitado de conexiones y utiliza un [número fijo de hilos](config.md#caddyfile-config) para atenderlas. La cantidad de conexiones aceptadas y en cola está limitada únicamente por los recursos disponibles del sistema. -El *pool* de hilos de PHP opera con un número fijo de hilos inicializados al inicio, comparable al modo estático de PHP-FPM. También es posible permitir que los hilos [escale automáticamente en tiempo de ejecución](performance.md#max_threads), similar al modo dinámico de PHP-FPM. +El _pool_ de hilos de PHP opera con un número fijo de hilos inicializados al inicio, comparable al modo estático de PHP-FPM. También es posible permitir que los hilos [escale automáticamente en tiempo de ejecución](performance.md#max_threads), similar al modo dinámico de PHP-FPM. Las conexiones en cola esperarán indefinidamente hasta que un hilo de PHP esté disponible para atenderlas. Para evitar esto, puedes usar la configuración `max_wait_time` en la [configuración global de FrankenPHP](config.md#caddyfile-config) para limitar la duración que una petición puede esperar por un hilo de PHP libre antes de ser rechazada. Adicionalmente, puedes establecer un [tiempo límite de escritura razonable en Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts). -Cada instancia de Caddy iniciará solo un *pool* de hilos de FrankenPHP, el cual será compartido entre todos los bloques `php_server`. +Cada instancia de Caddy iniciará solo un _pool_ de hilos de FrankenPHP, el cual será compartido entre todos los bloques `php_server`. diff --git a/docs/es/compile.md b/docs/es/compile.md index 269b0ff629..540e31ac81 100644 --- a/docs/es/compile.md +++ b/docs/es/compile.md @@ -79,11 +79,11 @@ sudo make install Algunas características de FrankenPHP dependen de dependencias opcionales del sistema que deben instalarse. Alternativamente, estas características pueden deshabilitarse pasando etiquetas de compilación al compilador Go. -| Característica | Dependencia | Etiqueta de compilación para deshabilitarla | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| Compresión Brotli | [Brotli](https://github.com/google/brotli) | nobrotli | -| Reiniciar workers al cambiar archivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | -| [Mercure](mercure.md) | [Biblioteca Mercure Go](https://pkg.go.dev/github.com/dunglas/mercure) (instalada automáticamente, licencia AGPL) | nomercure | +| Característica | Dependencia | Etiqueta de compilación para deshabilitarla | +| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| Compresión Brotli | [Brotli](https://github.com/google/brotli) | nobrotli | +| Reiniciar workers al cambiar archivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | +| [Mercure](mercure.md) | [Biblioteca Mercure Go](https://pkg.go.dev/github.com/dunglas/mercure) (instalada automáticamente, licencia AGPL) | nomercure | ## Compilar la aplicación Go diff --git a/docs/es/embed.md b/docs/es/embed.md index fa324609b4..5841ce3414 100644 --- a/docs/es/embed.md +++ b/docs/es/embed.md @@ -66,7 +66,7 @@ La forma más fácil de crear un binario para Linux es usar el constructor basad RUN EMBED=dist/app/ ./build-static.sh ``` - > [!CAUTION] + > [!CAUTION] > > Algunos archivos `.dockerignore` (por ejemplo, el [`.dockerignore` predeterminado de Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)) > ignorarán el directorio `vendor/` y los archivos `.env`. Asegúrese de ajustar o eliminar el archivo `.dockerignore` antes de la construcción. diff --git a/docs/es/extension-workers.md b/docs/es/extension-workers.md index fa7eecfc40..656917b25e 100644 --- a/docs/es/extension-workers.md +++ b/docs/es/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP proporciona hooks para ejecutar código Go en puntos específicos del ciclo de vida. -| Tipo de Hook | Nombre de Opción | Firma | Contexto y Caso de Uso | -| :----------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------------- | -| **Server** | `WithWorkerOnServerStartup` | `func()` | Configuración global. Se ejecuta **Una vez**. Ejemplo: Conectar a NATS/Redis. | +| Tipo de Hook | Nombre de Opción | Firma | Contexto y Caso de Uso | +| :----------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------- | +| **Server** | `WithWorkerOnServerStartup` | `func()` | Configuración global. Se ejecuta **Una vez**. Ejemplo: Conectar a NATS/Redis. | | **Server** | `WithWorkerOnServerShutdown` | `func()` | Limpieza global. Se ejecuta **Una vez**. Ejemplo: Cerrar conexiones compartidas. | -| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuración por hilo. Llamado cuando un hilo inicia. Recibe el ID del hilo. | -| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpieza por hilo. Recibe el ID del hilo. | +| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuración por hilo. Llamado cuando un hilo inicia. Recibe el ID del hilo. | +| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpieza por hilo. Recibe el ID del hilo. | ### Ejemplo diff --git a/docs/es/extensions.md b/docs/es/extensions.md index da85623a24..cf22542f45 100644 --- a/docs/es/extensions.md +++ b/docs/es/extensions.md @@ -87,21 +87,21 @@ Mientras que el primer punto se explica por sí mismo, el segundo puede ser más Aunque algunos tipos de variables tienen la misma representación en memoria entre C/PHP y Go, algunos tipos requieren más lógica para ser usados directamente. Esta es quizá la parte más difícil cuando se trata de escribir extensiones porque requiere entender los internos del motor Zend y cómo se almacenan las variables internamente en PHP. Esta tabla resume lo que necesitas saber: -| Tipo PHP | Tipo Go | Conversión directa | Helper de C a Go | Helper de Go a C | Soporte para Métodos de Clase | -|---------------------|--------------------------------|---------------------|---------------------------------------|----------------------------------------|-------------------------------| -| `int` | `int64` | ✅ | - | - | ✅ | -| `?int` | `*int64` | ✅ | - | - | ✅ | -| `float` | `float64` | ✅ | - | - | ✅ | -| `?float` | `*float64` | ✅ | - | - | ✅ | -| `bool` | `bool` | ✅ | - | - | ✅ | -| `?bool` | `*bool` | ✅ | - | - | ✅ | -| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ | -| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ | -| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ | -| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ | -| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ | -| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | -| `object` | `struct` | ❌ | _Aún no implementado_ | _Aún no implementado_ | ❌ | +| Tipo PHP | Tipo Go | Conversión directa | Helper de C a Go | Helper de Go a C | Soporte para Métodos de Clase | +| ------------------ | ----------------------------- | ------------------ | --------------------------------- | ---------------------------------- | ----------------------------- | +| `int` | `int64` | ✅ | - | - | ✅ | +| `?int` | `*int64` | ✅ | - | - | ✅ | +| `float` | `float64` | ✅ | - | - | ✅ | +| `?float` | `*float64` | ✅ | - | - | ✅ | +| `bool` | `bool` | ✅ | - | - | ✅ | +| `?bool` | `*bool` | ✅ | - | - | ✅ | +| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ | +| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ | +| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ | +| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ | +| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ | +| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | +| `object` | `struct` | ❌ | _Aún no implementado_ | _Aún no implementado_ | ❌ | > [!NOTE] > diff --git a/docs/es/known-issues.md b/docs/es/known-issues.md index bf7e637b0e..e4ce1cf56a 100644 --- a/docs/es/known-issues.md +++ b/docs/es/known-issues.md @@ -4,17 +4,17 @@ Las siguientes extensiones se sabe que no son compatibles con FrankenPHP: -| Nombre | Razón | Alternativas | -| ----------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- | -| [imap](https://www.php.net/manual/es/imap.installation.php) | No es thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) | -| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | No es thread-safe | - | +| Nombre | Razón | Alternativas | +| ----------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- | +| [imap](https://www.php.net/manual/es/imap.installation.php) | No es thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) | +| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | No es thread-safe | - | ## Extensiones PHP con Errores Las siguientes extensiones tienen errores conocidos y comportamientos inesperados cuando se usan con FrankenPHP: -| Nombre | Problema | -| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Nombre | Problema | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ext-openssl](https://www.php.net/manual/es/book.openssl.php) | Cuando se usa musl libc, la extensión OpenSSL puede fallar bajo cargas pesadas. El problema no ocurre cuando se usa la más popular GNU libc. Este error está [siendo rastreado por PHP](https://github.com/php/php-src/issues/13648). | ## get_browser diff --git a/docs/es/worker.md b/docs/es/worker.md index a2d49f4439..b841c628d0 100644 --- a/docs/es/worker.md +++ b/docs/es/worker.md @@ -152,7 +152,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart ### Fallos en Workers Si un script de worker falla con un código de salida distinto de cero, FrankenPHP lo reiniciará con una estrategia de retroceso exponencial. -Si el script de worker permanece activo más tiempo que el último retroceso * 2, +Si el script de worker permanece activo más tiempo que el último retroceso \* 2, no penalizará al script de worker y lo reiniciará nuevamente. Sin embargo, si el script de worker continúa fallando con un código de salida distinto de cero en un corto período de tiempo (por ejemplo, tener un error tipográfico en un script), FrankenPHP fallará con el error: `too many consecutive failures`. diff --git a/docs/extensions.md b/docs/extensions.md index 2fe78035cb..e47b9882b4 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -103,8 +103,7 @@ If everything went well, your project directory should contain the following fil - **`my_extension.c`** - C implementation file - **`README.md`** - Documentation -> [!IMPORTANT] -> **Your source file (`my_extension.go`) is never modified.** The generator creates a separate `_generated.go` file containing CGO wrappers that call your original functions. This means you can safely version control your source file without worrying about generated code polluting it. +> [!IMPORTANT] > **Your source file (`my_extension.go`) is never modified.** The generator creates a separate `_generated.go` file containing CGO wrappers that call your original functions. This means you can safely version control your source file without worrying about generated code polluting it. ### Integrating the Generated Extension into FrankenPHP diff --git a/docs/fr/extension-workers.md b/docs/fr/extension-workers.md index 936049c7bc..bfa463a75b 100644 --- a/docs/fr/extension-workers.md +++ b/docs/fr/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP fournit des hooks pour exécuter du code Go à des points spécifiques du cycle de vie. -| Type de Hook | Nom de l'Option | Signature | Contexte et Cas d'Utilisation | -| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- | -| **Serveur** | `WithWorkerOnServerStartup` | `func()` | Configuration globale. Exécuté **Une fois**. Exemple : Connexion à NATS/Redis. | -| **Serveur** | `WithWorkerOnServerShutdown` | `func()` | Nettoyage global. Exécuté **Une fois**. Exemple : Fermeture des connexions partagées. | -| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuration par thread. Appelé lorsqu'un thread démarre. Reçoit l'ID du Thread. | -| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Nettoyage par thread. Reçoit l'ID du Thread. | +| Type de Hook | Nom de l'Option | Signature | Contexte et Cas d'Utilisation | +| :----------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------------ | +| **Serveur** | `WithWorkerOnServerStartup` | `func()` | Configuration globale. Exécuté **Une fois**. Exemple : Connexion à NATS/Redis. | +| **Serveur** | `WithWorkerOnServerShutdown` | `func()` | Nettoyage global. Exécuté **Une fois**. Exemple : Fermeture des connexions partagées. | +| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuration par thread. Appelé lorsqu'un thread démarre. Reçoit l'ID du Thread. | +| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Nettoyage par thread. Reçoit l'ID du Thread. | ### Exemple diff --git a/docs/fr/hot-reload.md b/docs/fr/hot-reload.md index fc26a6541f..cc5e5cd674 100644 --- a/docs/fr/hot-reload.md +++ b/docs/fr/hot-reload.md @@ -57,7 +57,7 @@ php_server { } ``` -Utilisez la forme longue de `hot_reload` pour spécifier le *topic* Mercure à utiliser ainsi que les répertoires ou fichiers à surveiller : +Utilisez la forme longue de `hot_reload` pour spécifier le _topic_ Mercure à utiliser ainsi que les répertoires ou fichiers à surveiller : ```caddyfile localhost @@ -84,7 +84,7 @@ Le serveur détecte les modifications et publie les modifications automatiquemen FrankenPHP expose l'URL du Hub Mercure à utiliser pour s'abonner aux modifications de fichiers via la variable d'environnement `$_SERVER['FRANKENPHP_HOT_RELOAD']`. La bibliothèque JavaScript [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload) gére la logique côté client. -Pour l'utiliser, ajoutez le code suivant à votre gabarit (*layout*) principal : +Pour l'utiliser, ajoutez le code suivant à votre gabarit (_layout_) principal : ```php @@ -103,12 +103,14 @@ Alternativement, vous pouvez implémenter votre propre logique côté client en ### Conserver les nœuds DOM existants -Dans de rares cas, comme lors de l'utilisation d'outils de développement tels que [la *web debug toolbar* de Symfony](https://github.com/symfony/symfony/pull/62970), +Dans de rares cas, comme lors de l'utilisation d'outils de développement tels que [la _web debug toolbar_ de Symfony](https://github.com/symfony/symfony/pull/62970), vous pouvez souhaiter conserver des nœuds DOM spécifiques. Pour ce faire, ajoutez l'attribut `data-frankenphp-hot-reload-preserve` à l'élément HTML concerné : ```html -
+
+ +
``` ## Mode Worker diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 954d91c7db..01b6c74930 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -186,3 +186,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/hot-reload.md b/docs/hot-reload.md index 0ae4ca7ef3..49790d1e68 100644 --- a/docs/hot-reload.md +++ b/docs/hot-reload.md @@ -26,7 +26,7 @@ To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive > > This feature is intended for **development environments only**. > Do not enable `hot_reload` in production, as this feature is not secure (exposes sensitive internal details) and slows down the application. -> + ```caddyfile localhost diff --git a/docs/internals.md b/docs/internals.md index 1a75a8b99d..783839d93c 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -64,23 +64,24 @@ Restart: Restarting → Yielding → Ready Handler transition: TransitionRequested → TransitionInProgress → TransitionComplete ``` -| State | Description | -|-------|-------------| -| `Reserved` | Thread slot allocated but not booted. Can be booted on demand. | -| `Booting` | Underlying POSIX thread is starting up. | -| `Inactive` | Thread is alive but has no handler assigned. Minimal memory footprint. | -| `Ready` | Thread has a handler and is ready to accept work. | -| `ShuttingDown` | Thread is shutting down. | -| `Done` | Thread has completely shut down. Transitions back to `Reserved` for potential reuse. | -| `Restarting` | Worker thread is being restarted (e.g., via admin API or file watcher). | -| `Yielding` | Worker thread has yielded control and is waiting to be re-activated. | -| `TransitionRequested` | A handler change has been requested from the Go side. | -| `TransitionInProgress` | The C thread has acknowledged the transition request. | -| `TransitionComplete` | The Go side has installed the new handler. | +| State | Description | +| ---------------------- | ------------------------------------------------------------------------------------ | +| `Reserved` | Thread slot allocated but not booted. Can be booted on demand. | +| `Booting` | Underlying POSIX thread is starting up. | +| `Inactive` | Thread is alive but has no handler assigned. Minimal memory footprint. | +| `Ready` | Thread has a handler and is ready to accept work. | +| `ShuttingDown` | Thread is shutting down. | +| `Done` | Thread has completely shut down. Transitions back to `Reserved` for potential reuse. | +| `Restarting` | Worker thread is being restarted (e.g., via admin API or file watcher). | +| `Yielding` | Worker thread has yielded control and is waiting to be re-activated. | +| `TransitionRequested` | A handler change has been requested from the Go side. | +| `TransitionInProgress` | The C thread has acknowledged the transition request. | +| `TransitionComplete` | The Go side has installed the new handler. | ### Key Operations **`RequestSafeStateChange(nextState)`**: The primary way external goroutines request state changes. It: + - Atomically succeeds from `Ready` or `Inactive` (under mutex) - Returns `false` immediately from `ShuttingDown`, `Done`, or `Reserved` - Blocks and retries from any other state, waiting for `Ready`, `Inactive`, or `ShuttingDown` @@ -100,16 +101,16 @@ When a thread needs to change its handler (e.g., from inactive to worker): ``` Go side (setHandler) C side (PHP thread) ───────────────── ───────────────── -RequestSafeStateChange( - TransitionRequested) -close(drainChan) +RequestSafeStateChange( + TransitionRequested) +close(drainChan) detects drain Set(TransitionInProgress) -WaitFor(TransitionInProgress) +WaitFor(TransitionInProgress) → unblocked WaitFor(TransitionComplete) -handler = newHandler +handler = newHandler drainChan = make(chan struct{}) -Set(TransitionComplete) +Set(TransitionComplete) → unblocked newHandler.beforeScriptExecution() ``` @@ -123,18 +124,18 @@ When workers are restarted (e.g., via admin API): ``` Go side (RestartWorkers) C side (worker thread) ───────────────── ───────────────── -RequestSafeStateChange( - Restarting) -close(drainChan) +RequestSafeStateChange( + Restarting) +close(drainChan) detects drain in waitForWorkerRequest() returns false → PHP script exits beforeScriptExecution(): state is Restarting → Set(Yielding) -WaitFor(Yielding) +WaitFor(Yielding) → unblocked WaitFor(Ready, ShuttingDown) drainChan = make(chan struct{}) -Set(Ready) +Set(Ready) → unblocked beforeScriptExecution() recurse: state is Ready → normal execution @@ -146,18 +147,18 @@ Set(Ready) C code calls Go functions via CGO exports. The main callbacks are: -| Function | Called when | -|----------|-----------| -| `go_frankenphp_before_script_execution` | C loop needs the next script to execute | -| `go_frankenphp_after_script_execution` | PHP script has finished executing | +| Function | Called when | +| ------------------------------------------- | ------------------------------------------------ | +| `go_frankenphp_before_script_execution` | C loop needs the next script to execute | +| `go_frankenphp_after_script_execution` | PHP script has finished executing | | `go_frankenphp_worker_handle_request_start` | Worker's `frankenphp_handle_request()` is called | -| `go_frankenphp_finish_worker_request` | Worker request handler has returned | -| `go_ub_write` | PHP produces output (`echo`, etc.) | -| `go_read_post` | PHP reads POST body (`php://input`) | -| `go_read_cookies` | PHP reads cookies | -| `go_write_headers` | PHP sends response headers | -| `go_sapi_flush` | PHP flushes output | -| `go_log_attrs` | PHP logs a structured message | +| `go_frankenphp_finish_worker_request` | Worker request handler has returned | +| `go_ub_write` | PHP produces output (`echo`, etc.) | +| `go_read_post` | PHP reads POST body (`php://input`) | +| `go_read_cookies` | PHP reads cookies | +| `go_write_headers` | PHP sends response headers | +| `go_sapi_flush` | PHP flushes output | +| `go_log_attrs` | PHP logs a structured message | All these functions receive a `threadIndex` parameter identifying the calling thread. This is a thread-local variable in C (`__thread uintptr_t thread_index`) set during thread initialization. diff --git a/docs/ja/extension-workers.md b/docs/ja/extension-workers.md index 573d831474..3c7c0aee0e 100644 --- a/docs/ja/extension-workers.md +++ b/docs/ja/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHPは、ライフサイクルの特定の時点でGoコードを実行するためのフックを提供します。 -| フックタイプ | オプション名 | シグネチャ | コンテキストと使用例 | -| :--------- | :-------------------------- | :------------------ | :--------------------------------------------------------------------- | -| **サーバー** | `WithWorkerOnServerStartup` | `func()` | グローバルなセットアップ。**一度だけ**実行されます。例: NATS/Redisへの接続。 | -| **サーバー** | `WithWorkerOnServerShutdown` | `func()` | グローバルなクリーンアップ。**一度だけ**実行されます。例: 共有接続のクローズ。 | -| **スレッド** | `WithWorkerOnReady` | `func(threadID int)` | スレッドごとのセットアップ。スレッドが開始したときに呼び出されます。スレッドIDを受け取ります。 | -| **スレッド** | `WithWorkerOnShutdown` | `func(threadID int)` | スレッドごとのクリーンアップ。スレッドIDを受け取ります。 | +| フックタイプ | オプション名 | シグネチャ | コンテキストと使用例 | +| :----------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------------------------------- | +| **サーバー** | `WithWorkerOnServerStartup` | `func()` | グローバルなセットアップ。**一度だけ**実行されます。例: NATS/Redisへの接続。 | +| **サーバー** | `WithWorkerOnServerShutdown` | `func()` | グローバルなクリーンアップ。**一度だけ**実行されます。例: 共有接続のクローズ。 | +| **スレッド** | `WithWorkerOnReady` | `func(threadID int)` | スレッドごとのセットアップ。スレッドが開始したときに呼び出されます。スレッドIDを受け取ります。 | +| **スレッド** | `WithWorkerOnShutdown` | `func(threadID int)` | スレッドごとのクリーンアップ。スレッドIDを受け取ります。 | ### 例 diff --git a/docs/ja/hot-reload.md b/docs/ja/hot-reload.md index 2ee3f6b358..0c1f4ebbcc 100644 --- a/docs/ja/hot-reload.md +++ b/docs/ja/hot-reload.md @@ -26,7 +26,7 @@ FrankenPHPはページの内容をリアルタイムで更新します。 > > この機能は**開発環境のみ**を対象としています。 > `hot_reload`を本番環境で有効にしないでください。この機能は安全ではなく(機密性の高い内部詳細を公開します)、アプリケーションの速度を低下させます。 -> + ```caddyfile localhost @@ -145,5 +145,5 @@ php_server { 4. **受信**: JavaScriptライブラリを介してリッスンしているブラウザがMercureイベントを受信します。 5. **更新**: - - **Idiomorph**が検出された場合、更新されたコンテンツをフェッチし、現在のHTMLを新しい状態に合わせてモーフィングし、状態を失うことなく即座に変更を適用します。 - - それ以外の場合、`window.location.reload()`が呼び出されてページがリフレッシュされます。 + - **Idiomorph**が検出された場合、更新されたコンテンツをフェッチし、現在のHTMLを新しい状態に合わせてモーフィングし、状態を失うことなく即座に変更を適用します。 + - それ以外の場合、`window.location.reload()`が呼び出されてページがリフレッシュされます。 diff --git a/docs/ja/worker.md b/docs/ja/worker.md index aa71b6d3b4..ffda677b71 100644 --- a/docs/ja/worker.md +++ b/docs/ja/worker.md @@ -186,3 +186,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/migrate.md b/docs/migrate.md index 8e5d55983a..b3f9add8da 100644 --- a/docs/migrate.md +++ b/docs/migrate.md @@ -5,14 +5,14 @@ This guide covers a basic migration for a typical PHP application. ## Key Differences -| PHP-FPM setup | FrankenPHP equivalent | -|---|---| -| Nginx/Apache + PHP-FPM | Single `frankenphp` binary | -| `php-fpm.conf` pool settings | [`frankenphp` global option](config.md#caddyfile-config) | -| Nginx `server {}` block | `Caddyfile` site block | -| `php_value` / `php_admin_value` | [`php_ini` Caddyfile directive](config.md#php-config) | -| `pm = static` / `pm.max_children` | `num_threads` | -| `pm = dynamic` | [`max_threads auto`](performance.md#max_threads) | +| PHP-FPM setup | FrankenPHP equivalent | +| --------------------------------- | -------------------------------------------------------- | +| Nginx/Apache + PHP-FPM | Single `frankenphp` binary | +| `php-fpm.conf` pool settings | [`frankenphp` global option](config.md#caddyfile-config) | +| Nginx `server {}` block | `Caddyfile` site block | +| `php_value` / `php_admin_value` | [`php_ini` Caddyfile directive](config.md#php-config) | +| `pm = static` / `pm.max_children` | `num_threads` | +| `pm = dynamic` | [`max_threads auto`](performance.md#max_threads) | ## Step 1: Replace Your Web Server Config diff --git a/docs/pt-br/extension-workers.md b/docs/pt-br/extension-workers.md index b7ddc1bc8e..bb6623de95 100644 --- a/docs/pt-br/extension-workers.md +++ b/docs/pt-br/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP oferece hooks para executar código Go em pontos específicos do ciclo de vida. -| Tipo de Hook | Nome da Opção | Assinatura | Contexto e Caso de Uso | -| :--------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------- | -| **Servidor** | `WithWorkerOnServerStartup` | `func()` | Configuração global. Executado **Uma Vez**. Exemplo: Conectar ao NATS/Redis. | -| **Servidor** | `WithWorkerOnServerShutdown` | `func()` | Limpeza global. Executado **Uma Vez**. Exemplo: Fechar conexões compartilhadas. | -| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuração por thread. Chamado quando um thread inicia. Recebe o ID do Thread. | -| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpeza por thread. Recebe o ID do Thread. | +| Tipo de Hook | Nome da Opção | Assinatura | Contexto e Caso de Uso | +| :----------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------- | +| **Servidor** | `WithWorkerOnServerStartup` | `func()` | Configuração global. Executado **Uma Vez**. Exemplo: Conectar ao NATS/Redis. | +| **Servidor** | `WithWorkerOnServerShutdown` | `func()` | Limpeza global. Executado **Uma Vez**. Exemplo: Fechar conexões compartilhadas. | +| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuração por thread. Chamado quando um thread inicia. Recebe o ID do Thread. | +| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpeza por thread. Recebe o ID do Thread. | ### Exemplo diff --git a/docs/pt-br/hot-reload.md b/docs/pt-br/hot-reload.md index c60f546d1e..f94c1ac4db 100644 --- a/docs/pt-br/hot-reload.md +++ b/docs/pt-br/hot-reload.md @@ -24,7 +24,7 @@ Para ativar o recarregamento instantâneo, habilite o Mercure e, em seguida, adi > > Este recurso é destinado **apenas para ambientes de desenvolvimento**. > Não ative `hot_reload` em produção, pois este recurso não é seguro (expõe detalhes internos sensíveis) e desacelera a aplicação. -> + ```caddyfile localhost diff --git a/docs/pt-br/worker.md b/docs/pt-br/worker.md index 64c442264e..7a0a2ddd6b 100644 --- a/docs/pt-br/worker.md +++ b/docs/pt-br/worker.md @@ -182,3 +182,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/ru/extension-workers.md b/docs/ru/extension-workers.md index af9adcaa65..5ee8b5d148 100644 --- a/docs/ru/extension-workers.md +++ b/docs/ru/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP предоставляет хуки для выполнения Go-кода в определенные моменты жизненного цикла. -| Тип хука | Имя опции | Подпись | Контекст и вариант использования | -| :-------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------- | +| Тип хука | Имя опции | Подпись | Контекст и вариант использования | +| :--------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------------------- | | **Сервер** | `WithWorkerOnServerStartup` | `func()` | Глобальная настройка. Выполняется **один раз**. Пример: Подключение к NATS/Redis. | -| **Сервер** | `WithWorkerOnServerShutdown` | `func()` | Глобальная очистка. Выполняется **один раз**. Пример: Закрытие общих соединений. | -| **Поток** | `WithWorkerOnReady` | `func(threadID int)` | Настройка для каждого потока. Вызывается при запуске потока. Получает ID потока. | -| **Поток** | `WithWorkerOnShutdown` | `func(threadID int)` | Очистка для каждого потока. Получает ID потока. | +| **Сервер** | `WithWorkerOnServerShutdown` | `func()` | Глобальная очистка. Выполняется **один раз**. Пример: Закрытие общих соединений. | +| **Поток** | `WithWorkerOnReady` | `func(threadID int)` | Настройка для каждого потока. Вызывается при запуске потока. Получает ID потока. | +| **Поток** | `WithWorkerOnShutdown` | `func(threadID int)` | Очистка для каждого потока. Получает ID потока. | ### Пример diff --git a/docs/ru/hot-reload.md b/docs/ru/hot-reload.md index 4443b5fd52..a7af1f2ba5 100644 --- a/docs/ru/hot-reload.md +++ b/docs/ru/hot-reload.md @@ -25,7 +25,7 @@ FrankenPHP включает встроенную функцию **горячей > > Эта функция предназначена **только для сред разработки**. > Не включайте `hot_reload` в продакшене, так как эта функция небезопасна (раскрывает конфиденциальные внутренние детали) и замедляет работу приложения. -> + ```caddyfile localhost diff --git a/docs/ru/worker.md b/docs/ru/worker.md index dba8517db1..a53019e12d 100644 --- a/docs/ru/worker.md +++ b/docs/ru/worker.md @@ -181,3 +181,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/tr/extension-workers.md b/docs/tr/extension-workers.md index ea8590d1fb..1ccbf07052 100644 --- a/docs/tr/extension-workers.md +++ b/docs/tr/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP, yaşam döngüsünün belirli noktalarında Go kodunu yürütmek için kancalar sağlar. -| Kanca Türü | Seçenek Adı | İmza | Bağlam ve Kullanım Durumu | -| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- | -| **Sunucu** | `WithWorkerOnServerStartup` | `func()` | Genel kurulum. **Bir Kez** çalışır. Örnek: NATS/Redis'e bağlanma. | -| **Sunucu** | `WithWorkerOnServerShutdown` | `func()` | Genel temizleme. **Bir Kez** çalışır. Örnek: Paylaşılan bağlantıları kapatma. | +| Kanca Türü | Seçenek Adı | İmza | Bağlam ve Kullanım Durumu | +| :--------------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------------------------ | +| **Sunucu** | `WithWorkerOnServerStartup` | `func()` | Genel kurulum. **Bir Kez** çalışır. Örnek: NATS/Redis'e bağlanma. | +| **Sunucu** | `WithWorkerOnServerShutdown` | `func()` | Genel temizleme. **Bir Kez** çalışır. Örnek: Paylaşılan bağlantıları kapatma. | | **İş Parçacığı** | `WithWorkerOnReady` | `func(threadID int)` | İş parçacığı başına kurulum. Bir iş parçacığı başladığında çağrılır. İş Parçacığı Kimliğini alır. | -| **İş Parçacığı** | `WithWorkerOnShutdown` | `func(threadID int)` | İş parçacığı başına temizleme. İş Parçacığı Kimliğini alır. | +| **İş Parçacığı** | `WithWorkerOnShutdown` | `func(threadID int)` | İş parçacığı başına temizleme. İş Parçacığı Kimliğini alır. | ### Örnek @@ -169,3 +169,4 @@ func init() { }), ) } +``` diff --git a/docs/tr/hot-reload.md b/docs/tr/hot-reload.md index a6450728bd..cb1af4c9f4 100644 --- a/docs/tr/hot-reload.md +++ b/docs/tr/hot-reload.md @@ -26,7 +26,7 @@ Sıcak yeniden yüklemeyi etkinleştirmek için Mercure'ü etkinleştirin, ardı > > Bu özellik **yalnızca geliştirme ortamları** içindir. > `hot_reload`'u üretimde etkinleştirmeyin, zira bu özellik güvenli değildir (hassas dahili ayrıntıları açığa çıkarır) ve uygulamanın yavaşlamasına neden olur. -> + ```caddyfile localhost diff --git a/docs/tr/worker.md b/docs/tr/worker.md index ebca47ebcb..1aa65ba411 100644 --- a/docs/tr/worker.md +++ b/docs/tr/worker.md @@ -179,3 +179,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` From 3d4071be1a3da1324205fd1c34cbaa9a54b5afa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 9 Apr 2026 10:17:09 +0200 Subject: [PATCH 3/4] CS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated notes regarding the `GEN_STUB_SCRIPT` environment variable and clarified that the source file is never modified. Added emphasis on naming conventions for PHP constants. Signed-off-by: Kévin Dunglas --- docs/extensions.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/extensions.md b/docs/extensions.md index e47b9882b4..528774e778 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -91,6 +91,7 @@ GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extensio ``` > [!NOTE] +> > Don't forget to set the `GEN_STUB_SCRIPT` environment variable to the path of the `gen_stub.php` file in the PHP sources you downloaded earlier. This is the same `gen_stub.php` script mentioned in the manual implementation section. If everything went well, your project directory should contain the following files for your extension: @@ -103,7 +104,9 @@ If everything went well, your project directory should contain the following fil - **`my_extension.c`** - C implementation file - **`README.md`** - Documentation -> [!IMPORTANT] > **Your source file (`my_extension.go`) is never modified.** The generator creates a separate `_generated.go` file containing CGO wrappers that call your original functions. This means you can safely version control your source file without worrying about generated code polluting it. +> [!IMPORTANT] +> +> **Your source file (`my_extension.go`) is never modified.** The generator creates a separate `_generated.go` file containing CGO wrappers that call your original functions. This means you can safely version control your source file without worrying about generated code polluting it. ### Integrating the Generated Extension into FrankenPHP @@ -470,6 +473,7 @@ const ( ``` > [!NOTE] +> > PHP constants will take the name of the Go constant, thus using upper case letters is recommended. #### Class Constants @@ -497,6 +501,7 @@ const ( ``` > [!NOTE] +> > Just like global constants, the class constants will take the name of the Go constant. Class constants are accessible using the class name scope in PHP: From 4409a2ed11b6df7a567483ce9a05ae544da5ef43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 9 Apr 2026 10:21:24 +0200 Subject: [PATCH 4/4] grammar fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Dunglas --- docs/extensions.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index 528774e778..54f0e3fb8d 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -17,10 +17,10 @@ We'll start with the generator approach as it's the easiest way to get started, ## Using the Extension Generator -FrankenPHP is bundled with a tool that allows you **to create a PHP extension** only using Go. **No need to write C code** or use CGO directly: FrankenPHP also includes a **public types API** to help you write your extensions in Go without having to worry about **the type juggling between PHP/C and Go**. +FrankenPHP is bundled with a tool that lets you **create a PHP extension** using only Go. **No need to write C code** or use CGO directly: FrankenPHP also includes a **public types API** to help you write your extensions in Go without having to worry about **the type juggling between PHP/C and Go**. > [!TIP] -> If you want to understand how extensions can be written in Go from scratch, you can read the manual implementation section below demonstrating how to write a PHP extension in Go without using the generator. +> If you want to understand how extensions can be written in Go from scratch, you can read the manual implementation section below, demonstrating how to write a PHP extension in Go without using the generator. Keep in mind that this tool is **not a full-fledged extension generator**. It is meant to help you write simple extensions in Go, but it does not provide the most advanced features of PHP extensions. If you need to write a more **complex and optimized** extension, you may need to write some C code or use CGO directly. @@ -44,7 +44,7 @@ tar xf php-* ### Writing the Extension -Everything is now setup to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this: +Everything is now set up to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this: ```go package example @@ -77,7 +77,7 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer { There are two important things to note here: -- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type; +- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type. - The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go. While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive to type juggling later in this guide. @@ -144,7 +144,7 @@ Once you've integrated your extension into FrankenPHP as demonstrated in the pre ### Type Juggling -While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP. +While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding the internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know: | PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support | @@ -169,7 +169,7 @@ This table summarizes what you need to know: > > For class methods specifically, primitive types and arrays are currently supported. Objects cannot be used as method parameters or return types yet. -If you refer to the code snippet of the previous section, you can see that helpers are used to convert the first parameter and the return value. The second and third parameter of our `repeat_this()` function don't need to be converted as memory representation of the underlying types are the same for both C and Go. +If you refer to the code snippet of the previous section, you can see that helpers are used to convert the first parameter and the return value. The second and third parameters of our `repeat_this()` function don't need to be converted, as the memory representation of the underlying types is the same for both C and Go. #### Working with Arrays @@ -260,8 +260,8 @@ func process_data_packed(arr *C.zend_array) unsafe.Pointer { - **Ordered key-value pairs** - Option to keep the order of the associative array - **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice -- **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap -- **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`) +- **Automatic list detection** - When converting to PHP, automatically detects if the array should be a packed list or a hashmap +- **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`, `float64`, `string`, `bool`, `nil`, `AssociativeArray`, `map[string]any`, `[]any`) - **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array. ##### Available methods: Packed and Associative @@ -519,7 +519,7 @@ echo User::ROLE_ADMIN; // "admin" echo Order::STATE_PENDING; // 0 ``` -The directive supports various value types including strings, integers, booleans, floats, and iota constants. When using `iota`, the generator automatically assigns sequential values (0, 1, 2, etc.). Global constants become available in your PHP code as global constants, while class constants are scoped to their respective classes using the public visibility. When using integers, different possible notation (binary, hex, octal) are supported and dumped as is in the PHP stub file. +The directive supports various value types, including strings, integers, booleans, floats, and iota constants. When using `iota`, the generator automatically assigns sequential values (0, 1, 2, etc.). Global constants become available in your PHP code as global constants, while class constants are scoped to their respective classes using the public visibility. When using integers, different possible notations (binary, hex, octal) are supported and dumped as is in the PHP stub file. You can use constants just like you are used to in the Go code. For example, let's take the `repeat_this()` function we declared earlier and change the last argument to an integer: