Перейти к содержимому
FrankenPHP
Esc
↑↓навигация↵открыть⌘Jпредпросмотр
На этой странице

Расширение workers

Расширение Workers позволяет вашему расширению FrankenPHP управлять выделенным пулом PHP-потоков для выполнения фоновых задач, обработки асинхронных событий или реализации пользовательских протоколов. Полезно для систем очередей, слушателей событий, планировщиков и т. д.

Регистрация worker-а

Статическая регистрация

Если вам не нужно делать Worker-а настраиваемым пользователем (фиксированный путь к скрипту, фиксированное количество потоков), вы можете просто зарегистрировать Worker в функции init().

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-приложении (встраивание)

Если вы встраиваете FrankenPHP в стандартное Go-приложение без Caddy, вы можете зарегистрировать Worker-ы расширения, используя frankenphp.WithExtensionWorkers при инициализации опций.

Взаимодействие с worker-ами

Как только пул Worker-ов активен, вы можете отправлять ему задачи. Это можно сделать внутри нативных функций, экспортированных в PHP, или из любой Go-логики, такой как планировщик cron, слушатель событий (MQTT, Kafka) или любая другая горутина.

Безголовый режим: 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

Используйте SendRequest, если ваше расширение должно вызвать PHP-скрипт, который ожидает стандартную веб-среду (заполнение $_SERVER, $_GET и т. д.).

// #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;
    }

    // Случай 2: Режим HTTP (заполняются стандартные суперглобальные переменные 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("Extension: Server starting up...")
        }),

        // Поток готов (Для каждого потока)
        // Примечание: Функция принимает целое число, представляющее ID потока
        frankenphp.WithWorkerOnReady(func(id int) {
            fmt.Printf("Extension: Worker thread #%d is ready.\n", id)
        }),
    )
}