From 2c144074eca74adfb4793b0e72025c32c06f3e50 Mon Sep 17 00:00:00 2001 From: Kevin MARTINS Date: Thu, 30 Oct 2025 11:35:21 +0100 Subject: [PATCH 1/8] (Fr)Add warning on stateful services and shared memory model --- docs/fr/worker.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 177448f160..eb815ca46d 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -3,6 +3,22 @@ Démarrez votre application une fois et gardez-la en mémoire. FrankenPHP traitera les requêtes entrantes en quelques millisecondes. +## Un changement de paradigme : Le modèle "Shared Memory" + +Attention, ce mode ajoute un nouveau paradigme de développement. Si votre code n'est pas stateless (sans état), l'activation du mode worker risque d'introduire des comportements inattendus. + +Contrairement au modèle PHP-FPM traditionnel ("Shared Nothing"), où la mémoire est effacée après chaque requête, le mode worker de FrankenPHP utilise un modèle de mémoire partagée ("Shared Memory Model"). + +L'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données ou des états incohérents si votre application n'est pas conçue pour cela. + +L'article suivant résume très bien ce problème et explique comment y remédier, notamment pour les applications Symfony en utilisant ResetInterface pour garantir que vos services sont "propres" à chaque nouvelle requête. + +**Ressources supplémentaires :** + +* **Article (EN)** : [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explique le problème en détail et présente des solutions. +* **Symfony** : La [documentation de Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) aborde également ce concept de "stateless worker". +* **Outil** : [phanalist](https://github.com/denzyldick/phanalist) est un analyseur statique (mentionné dans l'article) qui peut vous aider à détecter les services "stateful" dans votre code. + ## Démarrage des scripts workers ### Docker From a7adc67d53a91d09efef624e2d6fdd85a4483b23 Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Thu, 30 Oct 2025 13:56:30 +0100 Subject: [PATCH 2/8] (En)Add warning on stateful services and shared memory model --- docs/worker.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/worker.md b/docs/worker.md index e055436d30..8ce58b7574 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -3,6 +3,21 @@ Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds. +## A Paradigm Shift: The "Shared Memory" Model + +Warning: This mode introduces a new development paradigm. If your code is not stateless, enabling worker mode risks introducing unexpected behavior. + +Unlike the traditional PHP-FPM model ("Shared Nothing"), where memory is cleared after every request, FrankenPHP's worker mode uses a "Shared Memory Model". + +The application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks or inconsistent states if your application is not designed for it. + +The following article summarizes this issue very well and explains how to fix it, notably for Symfony applications using ResetInterface to ensure your services are "clean" for every new request. + +**Additional Resources:** + +* **Article**: [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explains the problem in detail and presents solutions. +* **Symfony**: [The Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) documentation also discusses this "stateless worker" concept. +* **Tool**: [phanalist](https://github.com/denzyldick/phanalist) is a static analyzer (mentioned in the article) that can help you detect "stateful" services in your code. ## Starting Worker Scripts ### Docker From fb80e182a1c5cfdc8598c2461da01668526a8e6e Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Thu, 30 Oct 2025 14:03:59 +0100 Subject: [PATCH 3/8] wording correction --- docs/fr/worker.md | 4 ++-- docs/worker.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index eb815ca46d..18d2fb2a35 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -3,11 +3,11 @@ Démarrez votre application une fois et gardez-la en mémoire. FrankenPHP traitera les requêtes entrantes en quelques millisecondes. -## Un changement de paradigme : Le modèle "Shared Memory" +## Un changement de paradigme Attention, ce mode ajoute un nouveau paradigme de développement. Si votre code n'est pas stateless (sans état), l'activation du mode worker risque d'introduire des comportements inattendus. -Contrairement au modèle PHP-FPM traditionnel ("Shared Nothing"), où la mémoire est effacée après chaque requête, le mode worker de FrankenPHP utilise un modèle de mémoire partagée ("Shared Memory Model"). +Contrairement au modèle PHP-FPM traditionnel ("Shared Nothing"), où la mémoire est effacée après chaque requête. L'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données ou des états incohérents si votre application n'est pas conçue pour cela. diff --git a/docs/worker.md b/docs/worker.md index 8ce58b7574..fd4f8a529f 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -3,11 +3,11 @@ Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds. -## A Paradigm Shift: The "Shared Memory" Model +## A Paradigm Shift Warning: This mode introduces a new development paradigm. If your code is not stateless, enabling worker mode risks introducing unexpected behavior. -Unlike the traditional PHP-FPM model ("Shared Nothing"), where memory is cleared after every request, FrankenPHP's worker mode uses a "Shared Memory Model". +Unlike the traditional PHP-FPM model ("Shared Nothing"), where memory is cleared after every request. The application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks or inconsistent states if your application is not designed for it. From f9a297addd76f174f7aad9138d7575c16a91c5e0 Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Thu, 30 Oct 2025 14:09:58 +0100 Subject: [PATCH 4/8] markdown syntax correction --- docs/fr/worker.md | 6 +++--- docs/worker.md | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 18d2fb2a35..3d74f7bbca 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -15,9 +15,9 @@ L'article suivant résume très bien ce problème et explique comment y remédie **Ressources supplémentaires :** -* **Article (EN)** : [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explique le problème en détail et présente des solutions. -* **Symfony** : La [documentation de Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) aborde également ce concept de "stateless worker". -* **Outil** : [phanalist](https://github.com/denzyldick/phanalist) est un analyseur statique (mentionné dans l'article) qui peut vous aider à détecter les services "stateful" dans votre code. +- Article (EN) : [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explique le problème en détail et présente des solutions. +- Symfony : La [documentation de Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) aborde également ce concept de "stateless worker". +- Outil : [phanalist](https://github.com/denzyldick/phanalist) est un analyseur statique (mentionné dans l'article) qui peut vous aider à détecter les services "stateful" dans votre code. ## Démarrage des scripts workers diff --git a/docs/worker.md b/docs/worker.md index fd4f8a529f..f83d07cd78 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -15,9 +15,10 @@ The following article summarizes this issue very well and explains how to fix it **Additional Resources:** -* **Article**: [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explains the problem in detail and presents solutions. -* **Symfony**: [The Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) documentation also discusses this "stateless worker" concept. -* **Tool**: [phanalist](https://github.com/denzyldick/phanalist) is a static analyzer (mentioned in the article) that can help you detect "stateful" services in your code. +- Article: [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explains the problem in detail and presents solutions. +- Symfony: [The Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) documentation also discusses this "stateless worker" concept. +- Tool: [phanalist](https://github.com/denzyldick/phanalist) is a static analyzer (mentioned in the article) that can help you detect "stateful" services in your code. + ## Starting Worker Scripts ### Docker From 6c8008690ab85e2e345e7ad2f69591774e315468 Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Thu, 30 Oct 2025 14:18:25 +0100 Subject: [PATCH 5/8] change in wording --- docs/fr/worker.md | 5 +---- docs/worker.md | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 3d74f7bbca..814225465d 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -7,10 +7,7 @@ FrankenPHP traitera les requêtes entrantes en quelques millisecondes. Attention, ce mode ajoute un nouveau paradigme de développement. Si votre code n'est pas stateless (sans état), l'activation du mode worker risque d'introduire des comportements inattendus. -Contrairement au modèle PHP-FPM traditionnel ("Shared Nothing"), où la mémoire est effacée après chaque requête. - -L'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données ou des états incohérents si votre application n'est pas conçue pour cela. - +Contrairement au modèle PHP-FPM traditionnel, l'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données ou des états incohérents si votre application n'est pas conçue pour cela. L'article suivant résume très bien ce problème et explique comment y remédier, notamment pour les applications Symfony en utilisant ResetInterface pour garantir que vos services sont "propres" à chaque nouvelle requête. **Ressources supplémentaires :** diff --git a/docs/worker.md b/docs/worker.md index f83d07cd78..bb09b53a83 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -7,10 +7,7 @@ FrankenPHP will handle incoming requests in a few milliseconds. Warning: This mode introduces a new development paradigm. If your code is not stateless, enabling worker mode risks introducing unexpected behavior. -Unlike the traditional PHP-FPM model ("Shared Nothing"), where memory is cleared after every request. - -The application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks or inconsistent states if your application is not designed for it. - +Unlike with the traditional PHP-FPM model, the application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks or inconsistent states if your application is not designed for it. The following article summarizes this issue very well and explains how to fix it, notably for Symfony applications using ResetInterface to ensure your services are "clean" for every new request. **Additional Resources:** From 911ca08122a5bbe6c8e5da28059148ff74d4816c Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Thu, 30 Oct 2025 18:38:22 +0100 Subject: [PATCH 6/8] wording changes --- docs/fr/worker.md | 8 +++----- docs/worker.md | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 814225465d..c87eba25e5 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -3,18 +3,16 @@ Démarrez votre application une fois et gardez-la en mémoire. FrankenPHP traitera les requêtes entrantes en quelques millisecondes. -## Un changement de paradigme - -Attention, ce mode ajoute un nouveau paradigme de développement. Si votre code n'est pas stateless (sans état), l'activation du mode worker risque d'introduire des comportements inattendus. +## Avertissement sur la conception Stateful Contrairement au modèle PHP-FPM traditionnel, l'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données ou des états incohérents si votre application n'est pas conçue pour cela. L'article suivant résume très bien ce problème et explique comment y remédier, notamment pour les applications Symfony en utilisant ResetInterface pour garantir que vos services sont "propres" à chaque nouvelle requête. **Ressources supplémentaires :** -- Article (EN) : [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explique le problème en détail et présente des solutions. +- Article (EN) : [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Symfony : La [documentation de Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) aborde également ce concept de "stateless worker". -- Outil : [phanalist](https://github.com/denzyldick/phanalist) est un analyseur statique (mentionné dans l'article) qui peut vous aider à détecter les services "stateful" dans votre code. +- Outil : [phanalist](https://github.com/denzyldick/phanalist) est un analyseur statique qui peut vous aider à détecter les services "stateful" dans votre code. ## Démarrage des scripts workers diff --git a/docs/worker.md b/docs/worker.md index bb09b53a83..784bba0f1f 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -3,18 +3,16 @@ Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds. -## A Paradigm Shift - -Warning: This mode introduces a new development paradigm. If your code is not stateless, enabling worker mode risks introducing unexpected behavior. +## Warning about stateful design Unlike with the traditional PHP-FPM model, the application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks or inconsistent states if your application is not designed for it. The following article summarizes this issue very well and explains how to fix it, notably for Symfony applications using ResetInterface to ensure your services are "clean" for every new request. **Additional Resources:** -- Article: [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Explains the problem in detail and presents solutions. +- Article: [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) - Symfony: [The Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) documentation also discusses this "stateless worker" concept. -- Tool: [phanalist](https://github.com/denzyldick/phanalist) is a static analyzer (mentioned in the article) that can help you detect "stateful" services in your code. +- Tool: [phanalist](https://github.com/denzyldick/phanalist) is a static analyzer that can help you detect "stateful" services in your code. ## Starting Worker Scripts From 8533696319ba51dacdaad00333bc9b5c0643c970 Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Mon, 3 Nov 2025 09:05:24 +0100 Subject: [PATCH 7/8] wording changes --- docs/fr/worker.md | 4 ++-- docs/worker.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index c87eba25e5..621a02db11 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -5,8 +5,8 @@ FrankenPHP traitera les requêtes entrantes en quelques millisecondes. ## Avertissement sur la conception Stateful -Contrairement au modèle PHP-FPM traditionnel, l'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données ou des états incohérents si votre application n'est pas conçue pour cela. -L'article suivant résume très bien ce problème et explique comment y remédier, notamment pour les applications Symfony en utilisant ResetInterface pour garantir que vos services sont "propres" à chaque nouvelle requête. +Contrairement au modèle PHP-FPM traditionnel, l'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données et de mémoires ou des états incohérents si votre application n'est pas conçue pour cela. +L'article suivant résume ce problème et explique comment y remédier, notamment pour les applications Symfony en utilisant ResetInterface pour garantir que vos services sont "réinitialisés" à chaque nouvelle requête. **Ressources supplémentaires :** diff --git a/docs/worker.md b/docs/worker.md index 784bba0f1f..0f54b70493 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -5,8 +5,8 @@ FrankenPHP will handle incoming requests in a few milliseconds. ## Warning about stateful design -Unlike with the traditional PHP-FPM model, the application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks or inconsistent states if your application is not designed for it. -The following article summarizes this issue very well and explains how to fix it, notably for Symfony applications using ResetInterface to ensure your services are "clean" for every new request. +Unlike with the traditional PHP-FPM model, the application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks and memory issues, or inconsistent states if your application is not designed for it. +The following article summarizes this issue and explains how to fix it, notably for Symfony applications using ResetInterface to ensure your services are "reset" for every new request. **Additional Resources:** From 4483413c299a4ff4f92b72f4a0ee35869f573fbe Mon Sep 17 00:00:00 2001 From: KevinMartinsDev Date: Tue, 4 Nov 2025 17:42:03 +0100 Subject: [PATCH 8/8] implementation of recommendations --- docs/fr/worker.md | 32 ++++++++++++++++++++++++++------ docs/worker.md | 34 +++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 621a02db11..04c6a93316 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -5,14 +5,34 @@ FrankenPHP traitera les requêtes entrantes en quelques millisecondes. ## Avertissement sur la conception Stateful -Contrairement au modèle PHP-FPM traditionnel, l'application reste chargée en mémoire entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données et de mémoires ou des états incohérents si votre application n'est pas conçue pour cela. -L'article suivant résume ce problème et explique comment y remédier, notamment pour les applications Symfony en utilisant ResetInterface pour garantir que vos services sont "réinitialisés" à chaque nouvelle requête. +Contrairement au modèle PHP-FPM traditionnel, l'application reste **chargée en mémoire** entre les requêtes. Par conséquent, tout état stocké dans vos services (propriétés d'objet, singletons, etc.) sera conservé et partagé entre les requêtes successives traitées par le même worker. Cela peut entraîner des fuites de données et de mémoires ou des états incohérents si votre application n'est pas conçue pour cela. -**Ressources supplémentaires :** +### Concevoir une application Stateless -- Article (EN) : [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) -- Symfony : La [documentation de Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) aborde également ce concept de "stateless worker". -- Outil : [phanalist](https://github.com/denzyldick/phanalist) est un analyseur statique qui peut vous aider à détecter les services "stateful" dans votre code. +Le défi est de gérer le cycle de vie de vos objets, en particulier ceux qui sont des instances partagées par le conteneur de dépendances. + +Principes à respecter : + +- **Éviter l'état global :** Les variables globales et les propriétés statiques ne doivent pas être modifiées. +- **Prudence avec les services :** Évitez de stocker des valeurs via des setters ou de modifier les propriétés publiques d'un service partagé, car ces changements affecteront la prochaine requête. +- **Priorité aux nouveaux objets :** Tout ce qui est lié à la requête ou aux paramètres utilisateur doit être retourné comme un nouvel objet pour chaque requête. + +### Détection des problèmes (Analyse Statique) + +Des outils d'analyse statique peuvent vous aider à identifier les services potentiellement stateful. Ces outils vérifient notamment : + +- L'utilisation de propriétés publiques ou statiques mutables dans les services partagés. +- L'usage de fonctions comme `die()` ou `exit()`. + +Un outil notable est **[denzyldick/phanalist](https://github.com/denzyldick/phanalist)**. Après installation, vous pouvez l'exécuter spécifiquement avec la règle E0012 (pour "Service compatibility with Shared Memory Model") pour obtenir une liste des endroits problématiques dans votre code. + +### L'interface de réinitialisation (Symfony) + +L'approche idéale est de concevoir vos services pour qu'ils soient naturellement `stateless`. + +Toutefois, dans les cas où il vous est difficile de rendre un service partagé complètement stateless (par exemple, un service avec un cache interne ou une configuration spécifique à la requête), vous pouvez utiliser l'interface `Symfony\Contracts\Service\ResetInterface`. + +Lorsqu'un service implémente cette interface, sa méthode reset() est automatiquement appelée par le conteneur de services à la fin de chaque requête. Cela permet de nettoyer spécifiquement l'état interne du service (par exemple, vider un cache interne, réinitialiser des propriétés...). ## Démarrage des scripts workers diff --git a/docs/worker.md b/docs/worker.md index 0f54b70493..41be39b32d 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -3,16 +3,36 @@ Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds. -## Warning about stateful design +## Designing for Stateful Applications -Unlike with the traditional PHP-FPM model, the application remains loaded in memory between requests. Consequently, any state stored in your services (object properties, singletons, etc.) will be preserved and shared across successive requests handled by the same worker. This can lead to data leaks and memory issues, or inconsistent states if your application is not designed for it. -The following article summarizes this issue and explains how to fix it, notably for Symfony applications using ResetInterface to ensure your services are "reset" for every new request. +Unlike the traditional PHP-FPM model, the application remains **loaded in memory** between requests. Consequently, any state stored within your services (object properties, singletons, etc.) will be retained and shared between successive requests handled by the same worker. This can lead to data and memory leaks or inconsistent states if your application is not designed for this. -**Additional Resources:** +### Designing a Stateless Application -- Article: [Getting your Symfony app ready for Swoole, RoadRunner, and FrankenPHP](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g) -- Symfony: [The Messenger](https://symfony.com/doc/current/messenger.html#stateless-worker) documentation also discusses this "stateless worker" concept. -- Tool: [phanalist](https://github.com/denzyldick/phanalist) is a static analyzer that can help you detect "stateful" services in your code. +The challenge is managing the lifecycle of your objects, especially those that are shared instances by the dependency container. + +**Key Principles to Follow:** + +- **Avoid Global State:** Global variables and static properties must not be modified. +- **Caution with Services:** Avoid storing values via setters or modifying public properties of a shared service, as these changes will affect the next request. +- **Prioritize New Objects:** Anything tied to the request or user parameters must be returned as a new object for each request. + +### Issue Detection (Static Analysis) + +Static analysis tools can help you identify potentially stateful services. These tools primarily check for: + +- The use of mutable public or static properties in shared services. +- The use of functions like `die()` or `exit()`. + +A notable tool is **[denzyldick/phanalist](https://github.com/denzyldick/phanalist)**. After installation, you can run it specifically with rule E0012 (for "Service compatibility with Shared Memory Model") to get a list of problematic areas in your code. + +### The Reset Interface (Symfony) + +The ideal approach is to design your services to be naturally **`stateless`**. + +However, in cases where it is difficult to make a shared service completely stateless (e.g., a service with an internal cache or request-specific configuration), you can use the **`Symfony\Contracts\Service\ResetInterface`**. + +When a service implements this interface, its `reset()` method is automatically called by the service container at the end of each request. This allows you to specifically clean up the service's internal state (e.g., emptying an internal cache, resetting properties...). ## Starting Worker Scripts