Using FrankenPHP Workers

Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds.

Starting Worker Scripts

Docker

Set the value of the FRANKENPHP_CONFIG environment variable to worker /path/to/your/worker/script.php:

  1. docker run \
  2. -e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
  3. -v $PWD:/app \
  4. -p 80:80 -p 443:443 -p 443:443/udp \
  5. dunglas/frankenphp

Standalone Binary

Use the --worker option of the php-server command to serve the content of the current directory using a worker:

  1. frankenphp php-server --worker /path/to/your/worker/script.php

If your PHP app is embedded in the binary, you can add a custom Caddyfile in the root directory of the app. It will be used automatically.

It’s also possible to restart the worker on file changes with the --watch option. The following command will trigger a restart if any file ending in .php in the /path/to/your/app/ directory or subdirectories is modified:

  1. frankenphp php-server --worker /path/to/your/worker/script.php --watch "/path/to/your/app/**/*.php"

Symfony Runtime

The worker mode of FrankenPHP is supported by the Symfony Runtime Component. To start any Symfony application in a worker, install the FrankenPHP package of PHP Runtime:

  1. composer require runtime/frankenphp-symfony

Start your app server by defining the APP_RUNTIME environment variable to use the FrankenPHP Symfony Runtime:

  1. docker run \
  2. -e FRANKENPHP_CONFIG="worker ./public/index.php" \
  3. -e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
  4. -v $PWD:/app \
  5. -p 80:80 -p 443:443 -p 443:443/udp \
  6. dunglas/frankenphp

Laravel Octane

See the dedicated documentation.

Custom Apps

The following example shows how to create your own worker script without relying on a third-party library:

  1. <?php
  2. // public/index.php
  3. // Prevent worker script termination when a client connection is interrupted
  4. ignore_user_abort(true);
  5. // Boot your app
  6. require __DIR__.'/vendor/autoload.php';
  7. $myApp = new \App\Kernel();
  8. $myApp->boot();
  9. // Handler outside the loop for better performance (doing less work)
  10. $handler = static function () use ($myApp) {
  11. // Called when a request is received,
  12. // superglobals, php://input and the like are reset
  13. echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
  14. };
  15. $maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
  16. for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
  17. $keepRunning = \frankenphp_handle_request($handler);
  18. // Do something after sending the HTTP response
  19. $myApp->terminate();
  20. // Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation
  21. gc_collect_cycles();
  22. if (!$keepRunning) break;
  23. }
  24. // Cleanup
  25. $myApp->shutdown();

Then, start your app and use the FRANKENPHP_CONFIG environment variable to configure your worker:

  1. docker run \
  2. -e FRANKENPHP_CONFIG="worker ./public/index.php" \
  3. -v $PWD:/app \
  4. -p 80:80 -p 443:443 -p 443:443/udp \
  5. dunglas/frankenphp

By default, 2 workers per CPU are started. You can also configure the number of workers to start:

  1. docker run \
  2. -e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
  3. -v $PWD:/app \
  4. -p 80:80 -p 443:443 -p 443:443/udp \
  5. dunglas/frankenphp

Restart the Worker After a Certain Number of Requests

As PHP was not originally designed for long-running processes, there are still many libraries and legacy codes that leak memory. A workaround to using this type of code in worker mode is to restart the worker script after processing a certain number of requests:

The previous worker snippet allows configuring a maximum number of request to handle by setting an environment variable named MAX_REQUESTS.

Worker Failures

If a worker script crashes with a non-zero exit code, FrankenPHP will restart it with an exponential backoff strategy. If the worker script stays up longer than the last backoff * 2, it will not penalize the worker script and restart it again. However, if the worker script continues to fail with a non-zero exit code in a short period of time (for example, having a typo in a script), FrankenPHP will crash with the error: too many consecutive failures.

Superglobals Behavior

PHP superglobals ($_SERVER, $_ENV, $_GET…) behave as follows:

  • before the first call to frankenphp_handle_request(), superglobals contain values bound to the worker script itself
  • during and after the call to frankenphp_handle_request(), superglobals contain values generated from the processed HTTP request, each call to frankenphp_handle_request() changes the superglobals values

To access the superglobals of the worker script inside the callback, you must copy them and import the copy in the scope of the callback:

  1. <?php
  2. // Copy worker's $_SERVER superglobal before the first call to frankenphp_handle_request()
  3. $workerServer = $_SERVER;
  4. $handler = static function () use ($workerServer) {
  5. var_dump($_SERVER); // Request-bound $_SERVER
  6. var_dump($workerServer); // $_SERVER of the worker script
  7. };
  8. // ...