Pular para o conteúdo
FrankenPHP
Esc
↑↓navegar↵abrir⌘Jpré-visualizar
Nesta página

Workers de extensão

Os Workers de Extensão permitem que sua extensão FrankenPHP gerencie um pool dedicado de threads PHP para executar tarefas em segundo plano, lidar com eventos assíncronos ou implementar protocolos personalizados. Útil para sistemas de fila, listeners de eventos, agendadores, etc.

Registrando o worker

Registro estático

Se você não precisa que o worker seja configurável pelo usuário (caminho de script fixo, número de threads fixo), você pode simplesmente registrar o worker na função init().

package myextension

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

// Handle global para comunicar com o pool de workers
var worker frankenphp.Workers

func init() {
	// Registra o worker quando o módulo é carregado.
	worker = caddy.RegisterWorkers(
		"my-internal-worker", // Nome único
		"worker.php",         // Caminho do script (relativo à execução ou absoluto)
		2,                    // Contagem fixa de threads
		// Hooks de ciclo de vida opcionais
		frankenphp.WithWorkerOnServerStartup(func() {
			// Lógica de configuração global...
		}),
	)
}

Em um módulo Caddy (configurável pelo usuário)

Se você planeja compartilhar sua extensão (como uma fila genérica ou um listener de eventos), você deve encapsulá-la em um módulo Caddy. Isso permite que os usuários configurem o caminho do script e a contagem de threads através do seu Caddyfile. Isso exige a implementação da interface caddy.Provisioner e a análise do Caddyfile (veja um exemplo).

Em uma aplicação Go pura (incorporação)

Se você está incorporando o FrankenPHP em uma aplicação Go padrão sem caddy, você pode registrar workers de extensão usando frankenphp.WithExtensionWorkers ao inicializar as opções.

Interagindo com workers

Assim que o pool de workers estiver ativo, você pode despachar tarefas para ele. Isso pode ser feito dentro de funções nativas exportadas para PHP, ou de qualquer lógica Go, como um agendador cron, um listener de eventos (MQTT, Kafka), ou qualquer outra goroutine.

Modo headless: SendMessage

Use SendMessage para passar dados brutos diretamente para o seu script worker. Isso é ideal para filas ou comandos simples.

Exemplo: uma extensão de fila assíncrona

// #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. Garante que o worker esteja pronto
	if worker == nil {
		return false
	}

	// 2. Despacha para o worker em segundo plano
	_, err := worker.SendMessage(
		context.Background(), // Contexto Go padrão
		unsafe.Pointer(data), // Dados a serem passados para o worker
		nil, // http.ResponseWriter opcional
	)

	return err == nil
}

Emulação HTTP: SendRequest

Use SendRequest se sua extensão precisar invocar um script PHP que espera um ambiente web padrão (populando $_SERVER, $_GET, etc.).

// #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. Prepara a requisição e o gravador
	url := frankenphp.GoString(unsafe.Pointer(path))
	req, _ := http.NewRequest("GET", url, http.NoBody)
	rr := httptest.NewRecorder()

	// 2. Despacha para o worker
	if err := worker.SendRequest(rr, req); err != nil {
		return nil
	}

	// 3. Retorna a resposta capturada
	return frankenphp.PHPString(rr.Body.String(), false)
}

Script do worker

O script PHP do worker é executado em um loop e pode lidar tanto com mensagens brutas quanto com requisições HTTP.

<?php
// Lida tanto com mensagens brutas quanto com requisições HTTP no mesmo loop
$handler = function ($payload = null) {
    // Caso 1: Modo de Mensagem
    if ($payload !== null) {
        return "Received payload: " . $payload;
    }

    // Caso 2: Modo HTTP (superglobais PHP padrão são populadas)
    echo "Hello from page: " . $_SERVER['REQUEST_URI'];
};

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

Hooks de ciclo de vida

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.

Exemplo

package myextension

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

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

        // Inicialização do Servidor (Global)
        frankenphp.WithWorkerOnServerStartup(func() {
            fmt.Println("Extensão: Servidor iniciando...")
        }),

        // Thread Pronta (Por Thread)
        // Nota: A função aceita um inteiro representando o ID do Thread
        frankenphp.WithWorkerOnReady(func(id int) {
            fmt.Printf("Extensão: Thread worker #%d está pronta.\n", id)
        }),
    )
}