frankenphp package - github.com/dunglas/frankenphp - Go Packages

Package frankenphp embeds PHP in Go projects and provides a SAPI for net/http.

This is the core of the FrankenPHP app server, and can be used in any Go program.

View Source

const (
	StopReasonCrash = iota
	StopReasonRestart
	StopReasonBootFailure 
)

View Source

var (
	ErrInvalidRequest     = errors.New("not a FrankenPHP request")
	ErrAlreadyStarted     = errors.New("FrankenPHP is already started")
	ErrInvalidPHPVersion  = errors.New("FrankenPHP is only compatible with PHP 8.2+")
	ErrMainThreadCreation = errors.New("error creating the main thread")
	ErrScriptExecution    = errors.New("error during PHP script execution")
	ErrNotRunning         = errors.New("FrankenPHP is not running. For proper configuration visit: https://frankenphp.dev/docs/config/#caddyfile-config")

	ErrInvalidRequestPath         = ErrRejected{"invalid request path", http.StatusBadRequest}
	ErrInvalidContentLengthHeader = ErrRejected{"invalid Content-Length header", http.StatusBadRequest}
	ErrMaxWaitTimeExceeded        = ErrRejected{"maximum request handling time exceeded", http.StatusServiceUnavailable}
)

EmbeddedAppPath contains the path of the embedded PHP application (empty if none). It can be set at build time using -ldflags to override the default extraction path:

go build -ldflags "-X github.com/dunglas/frankenphp.EmbeddedAppPath=/app" ...

When set, the embedded app is extracted to this fixed path instead of a temp directory with a checksum suffix. This is useful when the app contains pre-compiled artifacts (e.g. OPcache file cache) that reference absolute paths and need a predictable extraction location.

func CallPHPCallable(cb unsafe.Pointer, params []interface{}) interface{}

EXPERIMENTAL: CallPHPCallable executes a PHP callable with the given parameters. Returns the result of the callable as a Go interface{}, or nil if the call failed.

EXPERIMENTAL: DrainWorkers finishes all worker scripts before a graceful shutdown

ExecuteScriptCLI executes the PHP script passed as parameter. It returns the exit status code of the script.

package main

import (
	"log"
	"os"

	"github.com/dunglas/frankenphp"
)

func main() {
	if len(os.Args) <= 1 {
		log.Println("Usage: my-program script.php")
		os.Exit(1)
	}

	os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args))
}

EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map

EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice

EXPERIMENTAL: GoString copies a zend_string to a Go string.

EXPERIMENTAL: GoValue converts a PHP zval to a Go value

Zval having the null, bool, long, double, string and array types are currently supported. Arrays can currently only be converted to any[] and AssociativeArray[any]. Any other type will cause an error. More types may be supported in the future.

func Init(options ...Option) error

Init starts the PHP runtime and the configured workers.

IsPacked determines if the given zend_array is a packed array (list). Returns false if the array is nil or not packed.

NewRequestWithContext creates a new FrankenPHP request context.

EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a zend_array

EXPERIMENTAL: PHPMap converts an unordered Go map to a zend_array

EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zval with a zend_array value.

EXPERIMENTAL: PHPString converts a Go string to a zend_string with copy. The string can be non-persistent (automatically freed after the request by the ZMM) or persistent. If you choose the second mode, it is your repsonsability to free the allocated memory.

EXPERIMENTAL: PHPValue converts a Go any to a PHP zval

nil, bool, int, int64, float64, string, []any, and map[string]any are currently supported. Any other type will cause a panic. More types may be supported in the future.

RegisterExtension registers a new PHP extension.

RestartWorkers attempts to restart all workers gracefully All workers must be restarted at the same time to prevent issues with opcache resetting.

ServeHTTP executes a PHP script according to the given context.

package main

import (
	"log"
	"net/http"

	"github.com/dunglas/frankenphp"
)

func main() {
	if err := frankenphp.Init(); err != nil {
		panic(err)
	}
	defer frankenphp.Shutdown()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot("/path/to/document/root", false))
		if err != nil {
			panic(err)
		}

		if err := frankenphp.ServeHTTP(w, req); err != nil {
			panic(err)
		}
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}
package main

import (
	"log"
	"net/http"

	"github.com/dunglas/frankenphp"
)

func main() {
	if err := frankenphp.Init(
		frankenphp.WithWorkers("worker1", "worker1.php", 4,
			frankenphp.WithWorkerEnv(map[string]string{"ENV1": "foo"}),
			frankenphp.WithWorkerWatchMode([]string{}),
			frankenphp.WithWorkerMaxFailures(0),
		),
		frankenphp.WithWorkers("worker2", "worker2.php", 2,
			frankenphp.WithWorkerEnv(map[string]string{"ENV2": "bar"}),
			frankenphp.WithWorkerWatchMode([]string{}),
			frankenphp.WithWorkerMaxFailures(0),
		),
	); err != nil {
		panic(err)
	}
	defer frankenphp.Shutdown()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot("/path/to/document/root", false))
		if err != nil {
			panic(err)
		}

		if err := frankenphp.ServeHTTP(w, req); err != nil {
			panic(err)
		}
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Shutdown stops the workers and the PHP runtime.

func WithExtensionWorkers(name, fileName string, numThreads int, options ...WorkerOption) (Workers, Option)

EXPERIMENTAL: WithExtensionWorkers allow extensions to create workers.

A worker script with the provided name, fileName and thread count will be registered, along with additional configuration through WorkerOptions.

Workers are designed to run indefinitely and will be gracefully shut down when FrankenPHP shuts down.

Extension workers receive the lowest priority when determining thread allocations. If the requested number of threads cannot be allocated, then FrankenPHP will panic and provide this information to the user (who will need to allocate more total threads). Don't be greedy.

AssociativeArray represents a PHP array with ordered key-value pairs

EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray

type ErrRejected struct {
	
}
type FrankenPHPDebugState struct {
	ThreadDebugStates   []ThreadDebugState
	ReservedThreadCount int
}

EXPERIMENTAL: FrankenPHPDebugState prints the state of all PHP threads - debugging purposes only

func DebugState() FrankenPHPDebugState

EXPERIMENTAL: DebugState prints the state of all PHP threads - debugging purposes only

type Metrics interface {
	
	StartWorker(name string)
	
	ReadyWorker(name string)
	
	StopWorker(name string, reason StopReason)
	
	TotalWorkers(name string, num int)
	
	TotalThreads(num int)
	
	StartRequest()
	
	StopRequest()
	
	StopWorkerRequest(name string, duration time.Duration)
	
	StartWorkerRequest(name string)
	Shutdown()
	QueuedWorkerRequest(name string)
	DequeuedWorkerRequest(name string)
	QueuedRequest()
	DequeuedRequest()
}
type Option func(h *opt) error

Option instances allow to configure FrankenPHP.

WithContext sets the main context to use.

WithHotReload sets files to watch for file changes to trigger a hot reload update.

WithLogger configures the global logger to use.

WithMaxIdleTime configures the max time an autoscaled thread may be idle before being deactivated.

func WithMaxThreads(maxThreads int) Option

WithMaxWaitTime configures the max time a request may be stalled waiting for a thread.

func WithMetrics(m Metrics) Option
func WithNumThreads(numThreads int) Option

WithNumThreads configures the number of PHP threads to start.

WithPhpIni configures user defined PHP ini settings.

func WithWorkers(name, fileName string, num int, options ...WorkerOption) Option

WithWorkers configures the PHP workers to start

type PHPConfig struct {
	Version                PHPVersion
	ZTS                    bool
	ZendSignals            bool
	ZendMaxExecutionTimers bool
}
type PHPVersion struct {
	MajorVersion   int
	MinorVersion   int
	ReleaseVersion int
	Version        string
	VersionID      int
}
func Version() PHPVersion

Version returns infos about the PHP version.

type PrometheusMetrics struct {
	
}
func (m *PrometheusMetrics) DequeuedRequest()
func (m *PrometheusMetrics) DequeuedWorkerRequest(name string)
func (m *PrometheusMetrics) QueuedRequest()
func (m *PrometheusMetrics) QueuedWorkerRequest(name string)
func (m *PrometheusMetrics) Shutdown()
func (m *PrometheusMetrics) StartRequest()
func (m *PrometheusMetrics) StartWorkerRequest(name string)
func (m *PrometheusMetrics) StopRequest()
func (m *PrometheusMetrics) TotalThreads(num int)
type RequestOption func(h *frankenPHPContext) error

RequestOption instances allow to configure a FrankenPHP Request.

WithMercureHub sets the mercure.Hub to use to publish updates

func WithRequestDocumentRoot(documentRoot string, resolveSymlink bool) RequestOption

WithRequestDocumentRoot sets the root directory of the PHP application. if resolveSymlink is true, oath declared as root directory will be resolved to its absolute value after the evaluation of any symbolic links. Due to the nature of PHP opcache, root directory path is cached: when using a symlinked directory as root this could generate errors when symlink is changed without PHP being restarted; enabling this directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.

WithRequestEnv set CGI-like environment variables that will be available in $_SERVER. Values set with WithEnv always have priority over automatically populated values.

WithRequestLogger sets the logger associated with the current request

func WithRequestPreparedEnv(env PreparedEnv) RequestOption
func WithRequestResolvedDocumentRoot(documentRoot string) RequestOption

WithRequestResolvedDocumentRoot is similar to WithRequestDocumentRoot but doesn't do any checks or resolving on the path to improve performance.

WithRequestSplitPath contains a list of split path strings.

The path in the URL will be split into two, with the first piece ending with the value of splitPath. The first piece will be assumed as the actual resource (CGI script) name, and the second piece will be set to PATH_INFO for the CGI script to use.

Split paths can only contain ASCII characters. Comparison is case-insensitive.

Future enhancements should be careful to avoid CVE-2019-11043, which can be mitigated with use of a try_files-like behavior that 404s if the FastCGI path info is not found.

WithWorkerName sets the worker that should handle the request

EXPERIMENTAL: ThreadDebugState prints the state of a single PHP thread - debugging purposes only

type WorkerOption func(*workerOpt) error

WorkerOption instances allow configuring FrankenPHP worker.

WithWorkerEnv sets environment variables for the worker

func WithWorkerMaxFailures(maxFailures int) WorkerOption

WithWorkerMaxFailures sets the maximum number of consecutive failures before panicking

func WithWorkerMaxThreads(num int) WorkerOption

WithWorkerMaxThreads sets the max number of threads for this specific worker

WithWorkerMercureHub sets the mercure.Hub in the worker script and used to dispatch hot reloading-related mercure.Update.

func WithWorkerOnReady(f func(int)) WorkerOption
func WithWorkerOnServerShutdown(f func()) WorkerOption

WithWorkerOnServerShutdown adds a function to be called right before server shutdown. Useful for extensions.

func WithWorkerOnServerStartup(f func()) WorkerOption

WithWorkerOnServerStartup adds a function to be called right after server startup. Useful for extensions.

func WithWorkerOnShutdown(f func(int)) WorkerOption
func WithWorkerRequestOptions(options ...RequestOption) WorkerOption

WithWorkerRequestOptions sets options for the main dummy request created for the worker

func WithWorkerWatchMode(watch []string) WorkerOption

WithWorkerWatchMode sets directories to watch for file changes

EXPERIMENTAL: Workers allows you to register a worker.