|
| 1 | +# Workers d'extension |
| 2 | + |
| 3 | +Les Workers d'extension permettent à votre [extension FrankenPHP](https://frankenphp.dev/docs/extensions/) de gérer un pool dédié de threads PHP pour exécuter des tâches en arrière-plan, gérer des événements asynchrones ou implémenter des protocoles personnalisés. Cela se révèle utile pour les systèmes de files d'attente, les event listeners, les planificateurs, etc. |
| 4 | + |
| 5 | +## Enregistrement du Worker |
| 6 | + |
| 7 | +### Enregistrement statique |
| 8 | + |
| 9 | +Si vous n'avez pas besoin de rendre le worker configurable par l'utilisateur (chemin de script fixe, nombre de threads fixe), vous pouvez simplement enregistrer le worker dans la fonction `init()`. |
| 10 | + |
| 11 | +```go |
| 12 | +package myextension |
| 13 | + |
| 14 | +import ( |
| 15 | + "github.com/dunglas/frankenphp" |
| 16 | + "github.com/dunglas/frankenphp/caddy" |
| 17 | +) |
| 18 | + |
| 19 | +// Handle global pour communiquer avec le pool de workers |
| 20 | +var worker frankenphp.Workers |
| 21 | + |
| 22 | +func init() { |
| 23 | + // Enregistre le worker lorsque le module est chargé. |
| 24 | + worker = caddy.RegisterWorkers( |
| 25 | + "my-internal-worker", // Nom unique |
| 26 | + "worker.php", // Chemin du script (relatif à l'exécution ou absolu) |
| 27 | + 2, // Nombre de threads fixe |
| 28 | + // Hooks de cycle de vie optionnels |
| 29 | + frankenphp.WithWorkerOnServerStartup(func() { |
| 30 | + // Logique de configuration globale... |
| 31 | + }), |
| 32 | + ) |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +### Dans un module Caddy (configurable par l'utilisateur) |
| 37 | + |
| 38 | +Si vous prévoyez de partager votre extension (comme une file d'attente générique ou un écouteur d'événements), vous devriez l'envelopper dans un module Caddy. Cela permet aux utilisateurs de configurer le chemin du script et le nombre de threads via leur `Caddyfile`. Cela nécessite d'implémenter l'interface `caddy.Provisioner` et de parser le Caddyfile ([voir un exemple](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)). |
| 39 | + |
| 40 | +### Dans une application Go pure (intégration) |
| 41 | + |
| 42 | +Si vous [intégrez FrankenPHP dans une application Go standard sans Caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), vous pouvez enregistrer des workers d'extension en utilisant `frankenphp.WithExtensionWorkers` lors de l'initialisation des options. |
| 43 | + |
| 44 | +## Interaction avec les Workers |
| 45 | + |
| 46 | +Une fois le pool de workers actif, vous pouvez lui envoyer des tâches. Cela peut être fait à l'intérieur de [fonctions natives exportées vers PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), ou à partir de toute logique Go telle qu'un planificateur cron, un écouteur d'événements (MQTT, Kafka), ou toute autre goroutine. |
| 47 | + |
| 48 | +### Mode sans tête : `SendMessage` |
| 49 | + |
| 50 | +Utilisez `SendMessage` pour passer des données brutes directement à votre script worker. C'est idéal pour les files d'attente ou les commandes simples. |
| 51 | + |
| 52 | +#### Exemple : Une extension de file d'attente asynchrone |
| 53 | + |
| 54 | +```go |
| 55 | +// #include <Zend/zend_types.h> |
| 56 | +import "C" |
| 57 | +import ( |
| 58 | + "context" |
| 59 | + "unsafe" |
| 60 | + "github.com/dunglas/frankenphp" |
| 61 | +) |
| 62 | + |
| 63 | +//export_php:function my_queue_push(mixed $data): bool |
| 64 | +func my_queue_push(data *C.zval) bool { |
| 65 | + // 1. S'assurer que le worker est prêt |
| 66 | + if worker == nil { |
| 67 | + return false |
| 68 | + } |
| 69 | + |
| 70 | + // 2. Envoyer au worker en arrière-plan |
| 71 | + _, err := worker.SendMessage( |
| 72 | + context.Background(), // Contexte Go standard |
| 73 | + unsafe.Pointer(data), // Données à passer au worker |
| 74 | + nil, // http.ResponseWriter optionnel |
| 75 | + ) |
| 76 | + |
| 77 | + return err == nil |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +### Émulation HTTP : `SendRequest` |
| 82 | + |
| 83 | +Utilisez `SendRequest` si votre extension doit invoquer un script PHP qui s'attend à un environnement web standard (remplir `$_SERVER`, `$_GET`, etc.). |
| 84 | + |
| 85 | +```go |
| 86 | +// #include <Zend/zend_types.h> |
| 87 | +import "C" |
| 88 | +import ( |
| 89 | + "net/http" |
| 90 | + "net/http/httptest" |
| 91 | + "unsafe" |
| 92 | + "github.com/dunglas/frankenphp" |
| 93 | +) |
| 94 | + |
| 95 | +//export_php:function my_worker_http_request(string $path): string |
| 96 | +func my_worker_http_request(path *C.zend_string) unsafe.Pointer { |
| 97 | + // 1. Préparer la requête et l'enregistreur |
| 98 | + url := frankenphp.GoString(unsafe.Pointer(path)) |
| 99 | + req, _ := http.NewRequest("GET", url, http.NoBody) |
| 100 | + rr := httptest.NewRecorder() |
| 101 | + |
| 102 | + // 2. Envoyer au worker |
| 103 | + if err := worker.SendRequest(rr, req); err != nil { |
| 104 | + return nil |
| 105 | + } |
| 106 | + |
| 107 | + // 3. Retourner la réponse capturée |
| 108 | + return frankenphp.PHPString(rr.Body.String(), false) |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +## Script Worker |
| 113 | + |
| 114 | +Le script worker PHP s'exécute dans une boucle et peut gérer à la fois les messages bruts et les requêtes HTTP. |
| 115 | + |
| 116 | +```php |
| 117 | +<?php |
| 118 | +// Gérer à la fois les messages bruts et les requêtes HTTP dans la même boucle |
| 119 | +$handler = function ($payload = null) { |
| 120 | + // Cas 1 : Mode Message |
| 121 | + if ($payload !== null) { |
| 122 | + return "Received payload: " . $payload; |
| 123 | + } |
| 124 | + |
| 125 | + // Cas 2 : Mode HTTP (les superglobales PHP standards sont peuplées) |
| 126 | + echo "Hello from page: " . $_SERVER['REQUEST_URI']; |
| 127 | +}; |
| 128 | + |
| 129 | +while (frankenphp_handle_request($handler)) { |
| 130 | + gc_collect_cycles(); |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +## Hooks de Cycle de Vie |
| 135 | + |
| 136 | +FrankenPHP fournit des hooks pour exécuter du code Go à des points spécifiques du cycle de vie. |
| 137 | + |
| 138 | +| Type de Hook | Nom de l'Option | Signature | Contexte et Cas d'Utilisation | |
| 139 | +| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- | |
| 140 | +| **Serveur** | `WithWorkerOnServerStartup` | `func()` | Configuration globale. Exécuté **Une fois**. Exemple : Connexion à NATS/Redis. | |
| 141 | +| **Serveur** | `WithWorkerOnServerShutdown` | `func()` | Nettoyage global. Exécuté **Une fois**. Exemple : Fermeture des connexions partagées. | |
| 142 | +| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuration par thread. Appelé lorsqu'un thread démarre. Reçoit l'ID du Thread. | |
| 143 | +| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Nettoyage par thread. Reçoit l'ID du Thread. | |
| 144 | + |
| 145 | +### Exemple |
| 146 | + |
| 147 | +```go |
| 148 | +package myextension |
| 149 | + |
| 150 | +import ( |
| 151 | + "fmt" |
| 152 | + "github.com/dunglas/frankenphp" |
| 153 | + frankenphpCaddy "github.com/dunglas/frankenphp/caddy" |
| 154 | +) |
| 155 | + |
| 156 | +func init() { |
| 157 | + workerHandle = frankenphpCaddy.RegisterWorkers( |
| 158 | + "my-worker", "worker.php", 2, |
| 159 | + |
| 160 | + // Démarrage du Serveur (Global) |
| 161 | + frankenphp.WithWorkerOnServerStartup(func() { |
| 162 | + fmt.Println("Extension : Démarrage du serveur...") |
| 163 | + }), |
| 164 | + |
| 165 | + // Thread Prêt (Par Thread) |
| 166 | + // Note : La fonction accepte un entier représentant l'ID du Thread |
| 167 | + frankenphp.WithWorkerOnReady(func(id int) { |
| 168 | + fmt.Printf("Extension : Le thread worker #%d est prêt.\n", id) |
| 169 | + }), |
| 170 | + ) |
| 171 | +} |
| 172 | +``` |
0 commit comments