frankenphp

package module
v1.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2025 License: MIT Imports: 33 Imported by: 17

README

FrankenPHP: Modern App Server for PHP

FrankenPHP

FrankenPHP is a modern application server for PHP built on top of the Caddy web server.

FrankenPHP gives superpowers to your PHP apps thanks to its stunning features: Early Hints, worker mode, real-time capabilities, automatic HTTPS, HTTP/2, and HTTP/3 support...

FrankenPHP works with any PHP app and makes your Laravel and Symfony projects faster than ever thanks to their official integrations with the worker mode.

FrankenPHP can also be used as a standalone Go library to embed PHP in any app using net/http.

Learn more on frankenphp.dev and in this slide deck:

Slides

Getting Started

Standalone Binary

We provide static FrankenPHP binaries for Linux and macOS containing PHP 8.4 and most popular PHP extensions.

On Windows, use WSL to run FrankenPHP.

Download FrankenPHP or copy this line into your terminal to automatically install the version appropriate for your platform:

curl https://frankenphp.dev/install.sh | sh
mv frankenphp /usr/local/bin/

To serve the content of the current directory, run:

frankenphp php-server

You can also run command-line scripts with:

frankenphp php-cli /path/to/your/script.php
Docker

Alternatively, Docker images are available:

docker run -v .:/app/public \
    -p 80:80 -p 443:443 -p 443:443/udp \
    dunglas/frankenphp

Go to https://localhost, and enjoy!

[!TIP]

Do not attempt to use https://127.0.0.1. Use https://localhost and accept the self-signed certificate. Use the SERVER_NAME environment variable to change the domain to use.

Homebrew

FrankenPHP is also available as a Homebrew package for macOS and Linux.

To install it:

brew install dunglas/frankenphp/frankenphp

To serve the content of the current directory, run:

frankenphp php-server

Docs

Examples and Skeletons

Documentation

Overview

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.

Index

Examples

Constants

View Source
const (
	StopReasonCrash = iota
	StopReasonRestart
)

Variables

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")
	ErrRequestContextCreation = errors.New("error during request context creation")
	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")
)
View Source
var EmbeddedAppPath string

EmbeddedAppPath contains the path of the embedded PHP application (empty if none)

View Source
var (
	ErrMaxThreadsReached = errors.New("max amount of overall threads reached")
)

Functions

func DrainWorkers added in v1.5.0

func DrainWorkers()

EXPERIMENTAL: DrainWorkers finishes all worker scripts before a graceful shutdown

func ExecutePHPCode added in v1.6.0

func ExecutePHPCode(phpCode string) int

func ExecuteScriptCLI

func ExecuteScriptCLI(script string, args []string) int

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

Example
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))
}

func GoMap added in v1.9.1

func GoMap(arr unsafe.Pointer) map[string]any

EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map

func GoPackedArray added in v1.9.1

func GoPackedArray(arr unsafe.Pointer) []any

EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice

func GoString added in v1.8.0

func GoString(s unsafe.Pointer) string

EXPERIMENTAL: GoString copies a zend_string to a Go string.

func Init

func Init(options ...Option) error

Init starts the PHP runtime and the configured workers.

func NewRequestWithContext

func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error)

NewRequestWithContext creates a new FrankenPHP request context.

func PHPAssociativeArray added in v1.9.1

func PHPAssociativeArray(arr AssociativeArray) unsafe.Pointer

EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zend_array.

func PHPMap added in v1.9.1

func PHPMap(arr map[string]any) unsafe.Pointer

EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array.

func PHPPackedArray added in v1.9.1

func PHPPackedArray(slice []any) unsafe.Pointer

EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zend_array.

func PHPString added in v1.8.0

func PHPString(s string, persistent bool) unsafe.Pointer

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.

func RegisterExtension added in v1.8.0

func RegisterExtension(me unsafe.Pointer)

RegisterExtension registers a new PHP extension.

func RestartWorkers added in v1.5.0

func RestartWorkers()

RestartWorkers attempts to restart all workers gracefully

func ServeHTTP

func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error

ServeHTTP executes a PHP script according to the given context.

Example
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))
}
Example (Workers)
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))
}

func Shutdown

func Shutdown()

Shutdown stops the workers and the PHP runtime.

Types

type AssociativeArray added in v1.9.1

type AssociativeArray struct {
	Map   map[string]any
	Order []string
}

AssociativeArray represents a PHP array with ordered key-value pairs

func GoAssociativeArray added in v1.9.1

func GoAssociativeArray(arr unsafe.Pointer) AssociativeArray

EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray

type FrankenPHPDebugState added in v1.5.0

type FrankenPHPDebugState struct {
	ThreadDebugStates   []ThreadDebugState
	ReservedThreadCount int
}

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

func DebugState added in v1.5.0

func DebugState() FrankenPHPDebugState

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

type Metrics added in v1.3.0

type Metrics interface {
	// StartWorker collects started workers
	StartWorker(name string)
	// ReadyWorker collects ready workers
	ReadyWorker(name string)
	// StopWorker collects stopped workers
	StopWorker(name string, reason StopReason)
	// TotalWorkers collects expected workers
	TotalWorkers(name string, num int)
	// TotalThreads collects total threads
	TotalThreads(num int)
	// StartRequest collects started requests
	StartRequest()
	// StopRequest collects stopped requests
	StopRequest()
	// StopWorkerRequest collects stopped worker requests
	StopWorkerRequest(name string, duration time.Duration)
	// StartWorkerRequest collects started worker requests
	StartWorkerRequest(name string)
	Shutdown()
	QueuedWorkerRequest(name string)
	DequeuedWorkerRequest(name string)
	QueuedRequest()
	DequeuedRequest()
}

type Option

type Option func(h *opt) error

Option instances allow to configure FrankenPHP.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger configures the global logger to use.

func WithMaxThreads added in v1.5.0

func WithMaxThreads(maxThreads int) Option

func WithMaxWaitTime added in v1.5.0

func WithMaxWaitTime(maxWaitTime time.Duration) Option

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

func WithMetrics added in v1.3.0

func WithMetrics(m Metrics) Option

func WithNumThreads

func WithNumThreads(numThreads int) Option

WithNumThreads configures the number of PHP threads to start.

func WithPhpIni added in v1.5.0

func WithPhpIni(overrides map[string]string) Option

WithPhpIni configures user defined PHP ini settings.

func WithWorkers

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

WithWorkers configures the PHP workers to start

type PHPConfig

type PHPConfig struct {
	Version                PHPVersion
	ZTS                    bool
	ZendSignals            bool
	ZendMaxExecutionTimers bool
}

func Config

func Config() PHPConfig

type PHPVersion

type PHPVersion struct {
	MajorVersion   int
	MinorVersion   int
	ReleaseVersion int
	ExtraVersion   string
	Version        string
	VersionID      int
}

func Version

func Version() PHPVersion

Version returns infos about the PHP version.

type PreparedEnv added in v1.1.1

type PreparedEnv = map[string]string

func PrepareEnv added in v1.1.1

func PrepareEnv(env map[string]string) PreparedEnv

type PrometheusMetrics added in v1.3.0

type PrometheusMetrics struct {
	// contains filtered or unexported fields
}

func NewPrometheusMetrics added in v1.3.0

func NewPrometheusMetrics(registry prometheus.Registerer) *PrometheusMetrics

func (*PrometheusMetrics) DequeuedRequest added in v1.5.0

func (m *PrometheusMetrics) DequeuedRequest()

func (*PrometheusMetrics) DequeuedWorkerRequest added in v1.5.0

func (m *PrometheusMetrics) DequeuedWorkerRequest(name string)

func (*PrometheusMetrics) QueuedRequest added in v1.5.0

func (m *PrometheusMetrics) QueuedRequest()

func (*PrometheusMetrics) QueuedWorkerRequest added in v1.5.0

func (m *PrometheusMetrics) QueuedWorkerRequest(name string)

func (*PrometheusMetrics) ReadyWorker added in v1.3.0

func (m *PrometheusMetrics) ReadyWorker(name string)

func (*PrometheusMetrics) Shutdown added in v1.3.0

func (m *PrometheusMetrics) Shutdown()

func (*PrometheusMetrics) StartRequest added in v1.3.0

func (m *PrometheusMetrics) StartRequest()

func (*PrometheusMetrics) StartWorker added in v1.3.0

func (m *PrometheusMetrics) StartWorker(name string)

func (*PrometheusMetrics) StartWorkerRequest added in v1.3.0

func (m *PrometheusMetrics) StartWorkerRequest(name string)

func (*PrometheusMetrics) StopRequest added in v1.3.0

func (m *PrometheusMetrics) StopRequest()

func (*PrometheusMetrics) StopWorker added in v1.3.0

func (m *PrometheusMetrics) StopWorker(name string, reason StopReason)

func (*PrometheusMetrics) StopWorkerRequest added in v1.3.0

func (m *PrometheusMetrics) StopWorkerRequest(name string, duration time.Duration)

func (*PrometheusMetrics) TotalThreads added in v1.3.0

func (m *PrometheusMetrics) TotalThreads(num int)

func (*PrometheusMetrics) TotalWorkers added in v1.3.0

func (m *PrometheusMetrics) TotalWorkers(string, int)

type RequestOption

type RequestOption func(h *frankenPHPContext) error

RequestOption instances allow to configure a FrankenPHP Request.

func WithOriginalRequest added in v1.4.0

func WithOriginalRequest(r *http.Request) RequestOption

func WithRequestDocumentRoot

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.

func WithRequestEnv

func WithRequestEnv(env map[string]string) RequestOption

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

func WithRequestLogger

func WithRequestLogger(logger *slog.Logger) RequestOption

WithRequestLogger sets the logger associated with the current request

func WithRequestPreparedEnv added in v1.1.1

func WithRequestPreparedEnv(env PreparedEnv) RequestOption

func WithRequestResolvedDocumentRoot added in v1.2.5

func WithRequestResolvedDocumentRoot(documentRoot string) RequestOption

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

func WithRequestSplitPath

func WithRequestSplitPath(splitPath []string) RequestOption

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.

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.

func WithWorkerName added in v1.6.0

func WithWorkerName(name string) RequestOption

WithWorkerName sets the worker that should handle the request

type StopReason added in v1.3.0

type StopReason int

type ThreadDebugState added in v1.5.0

type ThreadDebugState struct {
	Index                    int
	Name                     string
	State                    string
	IsWaiting                bool
	IsBusy                   bool
	WaitingSinceMilliseconds int64
}

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

type WorkerOption added in v1.8.0

type WorkerOption func(*workerOpt) error

WorkerOption instances allow configuring FrankenPHP worker.

func WithWorkerEnv added in v1.8.0

func WithWorkerEnv(env map[string]string) WorkerOption

WithWorkerEnv sets environment variables for the worker

func WithWorkerMaxFailures added in v1.8.0

func WithWorkerMaxFailures(maxFailures int) WorkerOption

WithWorkerMaxFailures sets the maximum number of consecutive failures before panicking

func WithWorkerWatchMode added in v1.8.0

func WithWorkerWatchMode(watch []string) WorkerOption

WithWorkerWatchMode sets directories to watch for file changes

Directories

Path Synopsis
caddy module
internal
cpu
extgen
header.go
header.go
testcli command
testserver command