Files
archived-frankenphp/docs/cn/extension-workers.md
github-actions[bot] c1e30cd638 docs: update translations (#2216)
Translation updates for: extension-workers.md .

---------

Co-authored-by: alexandre-daubois <2144837+alexandre-daubois@users.noreply.github.com>
Co-authored-by: Alexandre Daubois <alex.daubois@gmail.com>
2026-03-09 15:13:46 +01:00

5.6 KiB
Raw Permalink Blame History

扩展 Worker

扩展 Worker 使您的 FrankenPHP 扩展 能够管理专用的 PHP 线程池,用于执行后台任务、处理异步事件或实现自定义协议。适用于队列系统、事件监听器、调度器等。

注册 Worker

静态注册

如果您的 worker 不需要用户配置(固定的脚本路径、固定的线程数),您可以直接在 init() 函数中注册 worker。

package myextension

import (
	"github.com/dunglas/frankenphp"
	"github.com/dunglas/frankenphp/caddy"
)

// 与 worker 池通信的全局句柄
var worker frankenphp.Workers

func init() {
	// 模块加载时注册 worker。
	worker = caddy.RegisterWorkers(
		"my-internal-worker", // 唯一名称
		"worker.php",         // 脚本路径(相对于执行目录或绝对路径)
		2,                    // 固定线程数
		// 可选的生命周期钩子
		frankenphp.WithWorkerOnServerStartup(func() {
			// 全局设置逻辑...
		}),
	)
}

在 Caddy 模块中(用户可配置)

如果您计划共享您的扩展(例如通用的队列或事件监听器),您应该将其封装在一个 Caddy 模块中。这允许用户通过 Caddyfile 配置脚本路径和线程数。这需要实现 caddy.Provisioner 接口并解析 Caddyfile (查看示例)。

在纯 Go 应用程序中(嵌入式)

如果您 在没有 Caddy 的标准 Go 应用程序中嵌入 FrankenPHP,您可以在初始化选项时使用 frankenphp.WithExtensionWorkers 注册扩展 worker。

与 Worker 交互

一旦 worker 池激活,您就可以向其分派任务。这可以在 导出到 PHP 的原生函数 中完成,也可以从任何 Go 逻辑中完成,例如 cron 调度器、事件监听器 (MQTT、Kafka) 或任何其他 goroutine。

无头模式:SendMessage

使用 SendMessage 将原始数据直接传递给您的 worker 脚本。这非常适合队列或简单命令。

示例:一个异步队列扩展

// #include <Zend/zend_types.h>
import "C"
import (
	"context"
	"unsafe"
	"github.com/dunglas/frankenphp"
)

//export_php:function my_queue_push(mixed $data): bool
func my_queue_push(data *C.zval) bool {
	// 1. 确保 worker 已准备就绪
	if worker == nil {
		return false
	}

	// 2. 分派给后台 worker
	_, err := worker.SendMessage(
		context.Background(), // 标准 Go 上下文
		unsafe.Pointer(data), // 要传递给 worker 的数据
		nil, // 可选的 http.ResponseWriter
	)

	return err == nil
}

HTTP 模拟:SendRequest

如果您的扩展需要调用一个期望标准 Web 环境(填充 $_SERVER$_GET 等)的 PHP 脚本,请使用 SendRequest

// #include <Zend/zend_types.h>
import "C"
import (
	"net/http"
	"net/http/httptest"
	"unsafe"
	"github.com/dunglas/frankenphp"
)

//export_php:function my_worker_http_request(string $path): string
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
	// 1. 准备请求和记录器
	url := frankenphp.GoString(unsafe.Pointer(path))
	req, _ := http.NewRequest("GET", url, http.NoBody)
	rr := httptest.NewRecorder()

	// 2. 分派给 worker
	if err := worker.SendRequest(rr, req); err != nil {
		return nil
	}

	// 3. 返回捕获的响应
	return frankenphp.PHPString(rr.Body.String(), false)
}

Worker 脚本

PHP worker 脚本在一个循环中运行,可以处理原始消息和 HTTP 请求。

<?php
// 在同一个循环中处理原始消息和 HTTP 请求
$handler = function ($payload = null) {
    // 情况 1消息模式
    if ($payload !== null) {
        return "Received payload: " . $payload;
    }

    // 情况 2HTTP 模式(标准 PHP 超全局变量会被填充)
    echo "Hello from page: " . $_SERVER['REQUEST_URI'];
};

while (frankenphp_handle_request($handler)) {
    gc_collect_cycles();
}

生命周期钩子

FrankenPHP 提供了钩子,用于在生命周期的特定点执行 Go 代码。

钩子类型 选项名称 签名 上下文与用例
服务器 WithWorkerOnServerStartup func() 全局设置。只运行一次。示例:连接到 NATS/Redis。
服务器 WithWorkerOnServerShutdown func() 全局清理。只运行一次。示例:关闭共享连接。
线程 WithWorkerOnReady func(threadID int) 每线程设置。在线程启动时调用。接收线程 ID。
线程 WithWorkerOnShutdown func(threadID int) 每线程清理。接收线程 ID。

示例

package myextension

import (
    "fmt"
    "github.com/dunglas/frankenphp"
    frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
)

func init() {
    workerHandle = frankenphpCaddy.RegisterWorkers(
        "my-worker", "worker.php", 2,

        // 服务器启动(全局)
        frankenphp.WithWorkerOnServerStartup(func() {
            fmt.Println("扩展:服务器正在启动...")
        }),

        // 线程就绪(每线程)
        // 注意:此函数接受一个表示线程 ID 的整数
        frankenphp.WithWorkerOnReady(func(id int) {
            fmt.Printf("扩展Worker 线程 #%d 已就绪。\n", id)
        }),
    )
}