FrankenPHP is not only a Caddy module: it can be embedded as a library in any Go program to execute PHP scripts with net/http.
The compilation requirements are the same as for building FrankenPHP from source: a PHP built with the embed SAPI and ZTS enabled, and the matching CGO flags.
Create a Server, register it while initializing FrankenPHP, and use it as an http.Handler:
For a minimal example see https://pkg.go.dev
NewServer() takes a human-readable name used to attribute workers, metrics and logs to the server (defaults to server_<idx> at registration when empty), the document root, the split path suffixes (defaults to [".php"]), environment variables made available to every request, and a *slog.Logger (defaults to the global logger).
Init() starts the PHP runtime and must be called exactly once before serving requests; Shutdown() stops it. Calling Server.ServeHTTP() before Init() or after Shutdown() returns ErrNotRunning. The same *Server may be passed to Init() again after a Shutdown(), for instance to reload the configuration.
Several servers can be registered at once, each with its own document root, environment and logger. This mirrors what multiple php_server blocks do in a Caddyfile:
// Registering two servers with separate document roots
api, _ := frankenphp.NewServer("api/public/")
admin, _ := frankenphp.NewServer("admin/public/")
err := frankenphp.Init(
frankenphp.WithServer(api),
frankenphp.WithServer(admin),
)
Requests served through api.ServeHTTP() only see the configuration (and, see below, the workers) of that server.
Worker scripts are declared with WithWorkers(). A worker can be scoped to a server with WithWorkerServerScope(): only requests handled by this server instance will reach the worker. Requests are matched by script path, or by a custom matcher registered with WithWorkerMatcher():
// Scoping workers to a server
server, _ := frankenphp.NewServer("public/")
err := frankenphp.Init(
frankenphp.WithServer(server),
frankenphp.WithWorkers("app", "public/index.php", 4,
frankenphp.WithWorkerServerScope(server),
),
frankenphp.WithWorkers("api", "public/api.php", 2,
frankenphp.WithWorkerServerScope(server),
frankenphp.WithWorkerMatcher(func(r *http.Request) bool {
return strings.HasPrefix(r.URL.Path, "/api/")
}),
),
)
Workers declared without a server scope are global: they match by file path on any server. Since a global worker has no set of requests to match against, combining WithWorkerMatcher() with a global worker is a configuration error and Init() rejects it.
Server.ServeHTTP() accepts RequestOptions to override the server configuration for a single request, e.g. WithRequestDocumentRoot(), WithRequestSplitPath(), WithRequestEnv() or WithRequestLogger().
The package-level frankenphp.ServeHTTP() function keeps working without registering any server: requests prepared with frankenphp.NewRequestWithContext() are executed on an internal fallback server carrying the global configuration. New code should prefer explicit Server instances.