用 FrankenPHP 运行 Symfony 应用:Docker 部署、Worker 模式、热重载与独立二进制打包全指南
2026/9/15 14:06:23 网站建设 项目流程

用 FrankenPHP 运行 Symfony 应用:Docker 部署、Worker 模式、热重载与独立二进制打包全指南

【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp

本指南以 FrankenPHP 仓库的 docs/symfony.md 为核心,系统讲解在 Symfony 项目中使用 FrankenPHP 的完整方案:从官方推荐的 Symfony Docker 一键环境,到本地 Caddyfile 配置、常驻内存的 Worker 模式、开发期热重载、AssetMapper 资源预压缩、X-Sendfile 大文件下发,再到把 Symfony 应用整体打包成独立静态二进制。读完本文,你将能独立完成 Symfony 应用在 FrankenPHP 上的开发、部署与分发。

FrankenPHP 与 Symfony 的契合点

FrankenPHP 是一个基于 Caddy 构建的现代 PHP 应用服务器,内置了自动 HTTPS、HTTP/2、HTTP/3、Brotli/Zstandard 压缩、Mercure 实时推送等能力。对 Symfony 开发者而言,其核心价值在于三点:

  • Worker 模式:让 PHP 应用常驻内存、只启动一次,请求处理达到毫秒级;
  • 热重载:PHP 代码、模板、前端资源变更后浏览器自动更新,接近现代前端工具链的 HMR 体验;
  • 独立二进制:把应用、PHP 解释器、Caddy 服务器打包成单个可执行文件直接分发。

下面依次介绍每条实践路径。

方式一:用 Symfony Docker 快速搭建完整环境

对于 Symfony 项目,官方推荐直接使用Symfony Docker——这是由 FrankenPHP 作者维护的 Symfony 官方 Docker 方案。它开箱即用地提供了基于 Docker 的完整运行环境,包含:

  • FrankenPHP 作为 Web 服务器与 PHP 运行时;
  • 自动 HTTPS 证书(本地默认自签名);
  • HTTP/2、HTTP/3 协议支持;
  • Worker 模式支持;
  • 开箱即用的热重载(默认开启)。

你只需在项目根目录运行docker compose up -d即可获得完整的开发环境,无需手工编写 Caddyfile。这是开发 Symfony 应用时成本最低的入门路径。

方式二:在本地机器上直接运行 Symfony

如果你不想依赖 Docker,也可以在本机直接运行。步骤如下:

  1. 安装 FrankenPHP:参考仓库根目录 README.md 的安装章节(下载预编译二进制、使用 Docker 镜像或自行编译均可)。
  2. 编写 Caddyfile:在 Symfony 项目根目录创建名为Caddyfile的文件,内容如下:
# Caddyfile # 服务器域名 localhost root public/ php_server { # 可选:启用 worker 模式以获得更好的性能 worker ./public/index.php }
  1. 启动服务:在 Symfony 项目根目录执行:
frankenphp run

从源码实现看,php_server指令(见 caddy/php-server.go)会自动生成一整套路由:把请求重写到index.php、将.php路径交给 PHP 处理器、其余静态文件交给file_server,并默认叠加 zstd/br/gzip 压缩编码——这也解释了为什么一个极简的php_server块就能跑起完整的 Symfony 应用。

更进一步的性能调优可参考 性能文档。

Symfony 的 Worker 模式

原理与版本要求

Worker 模式的核心思想是:应用只启动一次并常驻内存,之后的每个请求由 FrankenPHP 直接喂给这个常驻进程处理,从而省去每次请求的引导(bootstrap)开销。更完整的原理说明见 Worker 模式文档。

由于 Worker 模式下进程跨请求存活,框架必须负责在请求间重置状态。从 Symfony 7.4 开始,FrankenPHP Worker 模式得到原生支持,无需任何额外依赖。如果使用更早的 Symfony 版本,则需要安装 PHP Runtime 项目提供的 FrankenPHP Symfony 运行时包:

composer require runtime/frankenphp-symfony

通过 Docker 启动 Worker

设置FRANKENPHP_CONFIG环境变量并指定入口脚本即可:

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

这里有两个关键点:

  • FRANKENPHP_CONFIG="worker ./public/index.php"告诉 FrankenPHP 以 Worker 模式运行,入口为public/index.php
  • APP_RUNTIME=Runtime\FrankenPhpSymfony\Runtime让 Symfony 使用 FrankenPHP 专用运行时来接管请求循环。在反斜杠被 shell 解析前务必正确转义(如\\)。

Worker 数量与生命周期控制

从 Worker 模式文档 可以看到几个与 Symfony 部署直接相关的默认行为:

  • 默认启动 2 个 worker / CPU;如需调整,可在FRANKENPHP_CONFIG中追加数字,例如worker ./public/index.php 42表示启动 42 个 worker;
  • 按请求数重启:由于 PHP 并非为长驻进程设计,部分库与旧代码存在内存泄漏,可通过环境变量MAX_REQUESTS限制单个 worker 处理的最大请求数,达到阈值后由脚本自行退出、由 FrankenPHP 拉起新进程;
  • 手动重启:启用 Caddy 管理接口后,可 POST 到http://localhost:2019/frankenphp/workers/restart优雅重启所有 worker;
  • 失败保护:worker 以非零码退出时 FrankenPHP 会按指数退避策略重启;若短时间连续失败次数过多(例如脚本存在语法错误),会以too many consecutive failures错误退出。最大连续失败次数可通过 Caddyfile 全局配置max_consecutive_failures调整(对应源码见 caddy/workerconfig.go)。

使用命令行方式启动 Worker

使用独立二进制时,可直接用php-server命令的--worker选项:

frankenphp php-server --worker ./public/index.php

配合--watch可在文件变化时自动重启 worker:

frankenphp php-server --worker ./public/index.php --watch="/path/to/your/app/**/*.php"

--watch的 glob 模式匹配到.php文件变更即触发 worker 重启,这一能力常用于开发期与热重载配合使用。

Worker 模式下的状态管理要点

Worker 模式下,静态变量、类静态属性、全局变量以及内存缓存都会跨请求持久化(这是其高性能的来源,但也是风险所在)。Superglobals 方面:$_GET$_POST$_COOKIE$_FILES$_SERVER$_REQUEST会在请求间自动重置,但$_ENV目前不会在请求间重置,因此不要把请求相关的敏感数据塞进$_ENV

对于 Symfony 开发者,正确做法是:持有请求态的服务应实现Symfony\Contracts\Service\ResetInterface,这样 Symfony kernel 会在每个请求结束后调用其reset()方法完成状态清理。框架自身的大部分状态会自动重置,但你自己业务代码中的服务仍需按此约定实现。

审计 Worker 兼容性:Igor PHP

在把 Worker 模式推向生产前,建议先用Igor PHP做静态审计。它是一款专门扫描 Symfony 项目"状态泄漏"问题的静态检查器,能发现:

  • 缺少ResetInterface的服务;
  • 未重置的有状态属性;
  • 可变的局部静态变量;
  • exit()/die()调用;
  • 对 superglobals 的写入。

它既审计你的应用代码,也审计vendor/中声明的服务。安装与使用:

composer require --dev igor-php/igor-php vendor/bin/igor-php .

为 Symfony 启用热重载

什么是热重载

FrankenPHP 内置热重载功能(完整说明见 热重载文档):它监听工作目录的文件变化(PHP、模板、JS、CSS 等),通过内置的 Mercure hub 向浏览器推送更新。浏览器端若加载了 Idiomorph,则会做 DOM 变形(保留滚动位置与输入状态);否则退化为整页刷新。

注意:该功能仅用于开发环境,切勿在生产环境开启——它既不安全(会暴露内部细节),也会拖慢应用。

在 Symfony Docker 中使用

Symfony Docker 中热重载默认已启用,开箱即用,无需任何配置。

不使用 Symfony Docker 时手动启用

需要在 Caddyfile 中同时启用 Mercure 与hot_reload子指令:

localhost mercure { anonymous } root public/ php_server { hot_reload worker ./public/index.php }

然后在 Symfony 的templates/base.html.twig中加入如下代码:

{# templates/base.html.twig #} {% if app.request.server.has('FRANKENPHP_HOT_RELOAD') %} <meta name="frankenphp-hot-reload:url" content="{{ app.request.server.get('FRANKENPHP_HOT_RELOAD') }}"> <script src="https://cdn.jsdelivr.net/npm/idiomorph"></script> <script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script> {% endif %}

其中FRANKENPHP_HOT_RELOAD环境变量由 FrankenPHP 注入,指向可供浏览器订阅的 Mercure Hub URL;前端库frankenphp-hot-reload负责订阅、后台抓取最新页面并做 DOM 变形。最后回到项目根目录运行:

frankenphp run

与 Worker 模式的组合使用

如果同时使用 Worker 模式,需要注意:PHP 代码常驻内存意味着单纯刷新浏览器看不到代码变更。正确组合是:

  • hot_reload:文件变化时刷新浏览器
  • worker块内的watch子指令:文件变化时重启 worker以加载新代码。

两者配合才能获得完整的开发体验:

localhost mercure { anonymous } root public/ php_server { hot_reload worker { file ./public/index.php watch } }

关于浏览器端保留特定 DOM 节点:如果页面中存在需要跨刷新保留的元素(例如 Symfony Web Debug 工具栏这类调试工具),可给该元素加上data-frankenphp-hot-reload-preserve属性。

预压缩静态资源(AssetMapper + Brotli/Zstandard)

Symfony 的 AssetMapper 组件可以在部署阶段对资源做 Brotli(br)与 Zstandard(zstd)预压缩。FrankenPHP 通过 Caddy 的file_server可以直接下发这些预压缩文件,从而省去请求时的实时压缩开销。

  1. 编译并压缩资源
php bin/console asset-map:compile
  1. 更新 Caddyfile,让/assets/*路径使用预压缩文件:
# Caddyfile localhost @assets path /assets/* file_server @assets { precompressed zstd br gzip } root public/ php_server { worker ./public/index.php }

precompressed指令的作用是:当客户端声明支持对应编码时,优先查找并直接下发app.css.zstapp.css.br这类预压缩版本,否则回退到原文件。zstd/br/gzip 的优先级按列出顺序生效,这正好与 caddy/php-server.go 中默认压缩编码的优先级(zstd → br → gzip)保持一致。

高效服务大文件:X-Sendfile / X-Accel-Redirect

有些场景必须先执行 PHP 代码(访问控制、统计、自定义响应头等)再下发大文件,但用 PHP 流式读取大文件既不高效又吃内存。FrankenPHP 支持在 PHP 执行完毕后,把静态文件的下发委托给 Web 服务器完成——这就是 Apache 生态的X-Sendfile、NGINX 生态的X-Accel-Redirect(完整说明见 X-Sendfile 文档)。

以下示例假设 Symfony 项目文档根为public/,而受保护的文件存放在public/之外的private-files/目录中。

1. 配置 Caddyfile

php_server前加入如下配置(在 FrankenPHP 的 Caddyfile 中启用 X-Accel-Redirect):

root public/ # ... + # Symfony、Laravel 等使用 Symfony HttpFoundation 组件的项目需要这两行 + request_header X-Sendfile-Type x-accel-redirect + request_header X-Accel-Mapping ../private-files=/private-files + + intercept { + @accel header X-Accel-Redirect * + handle_response @accel { + root private-files/ + rewrite * {resp.header.X-Accel-Redirect} + method * GET + + # 移除 PHP 设置的 X-Accel-Redirect 头以增强安全性 + header -X-Accel-Redirect + + file_server + } + } php_server

这里X-Accel-Mapping把内部路径../private-files映射为公开别名/private-filesintercept块则拦截带X-Accel-Redirect头的响应并转交给file_server真正下发文件。

2. 在 Symfony 控制器中返回文件

Symfony HttpFoundation 组件原生支持该特性。配置好上面的 Caddyfile 后,HttpFoundation 会自动确定X-Accel-Redirect头的正确值并加入响应:

use Symfony\Component\HttpFoundation\BinaryFileResponse; BinaryFileResponse::trustXSendfileTypeHeader(); $response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt'); // ...

调用trustXSendfileTypeHeader()后,BinaryFileResponse会根据请求头中的X-Sendfile-Type(即上面配置的x-accel-redirect)和X-Accel-Mapping生成正确的转发头,从而把大文件的下发交给 Caddy,PHP 进程不承担文件传输负担。

把 Symfony 应用打包成独立二进制

借助 FrankenPHP 的应用嵌入能力(详见 应用嵌入文档),可以把 Symfony 应用连同 PHP 解释器、Caddy Web 服务器一起打包成单个静态自包含二进制,直接分发到服务器运行,无需在目标机器上安装 PHP 或 Web 服务器。

1. 准备应用

# 导出项目,去掉 .git/ 等目录 mkdir $TMPDIR/my-prepared-app git archive HEAD | tar -x -C $TMPDIR/my-prepared-app cd $TMPDIR/my-prepared-app # 设置正确的环境变量 echo APP_ENV=prod > .env.local echo APP_DEBUG=0 >> .env.local # 删除测试等非必要文件以减小体积 # 也可以在 .gitattributes 中为这些文件配置 export-ignore 属性 rm -Rf tests/ # 安装生产依赖 composer install --ignore-platform-reqs --no-dev -a # 优化 .env composer dump-env prod

2. 创建静态构建 Dockerfile

在应用仓库中创建static-build.Dockerfile

# static-build.Dockerfile FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu # 如果打算在 musl-libc 系统上运行,改用 static-builder-musl # 复制你的应用 WORKDIR /go/src/app/dist/app COPY . . # 构建静态二进制 WORKDIR /go/src/app/ RUN EMBED=dist/app/ ./build-static.sh

[!CAUTION]

注意:部分.dockerignore文件(例如 Symfony Docker 默认自带的.dockerignore)会忽略vendor/目录和.env文件,导致它们无法进入构建镜像。构建前务必调整或删除.dockerignore

3. 构建并提取二进制

docker build -t static-symfony-app -f static-build.Dockerfile .
docker cp $(docker create --name static-symfony-app-tmp static-symfony-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-symfony-app-tmp

4. 启动服务

./my-app php-server

打包后可用的能力还包括(见 应用嵌入文档):

  • 在应用根目录放置自定义Caddyfilephp.ini,二进制启动时会自动加载——php-server命令会检测并加载内嵌应用目录中的这两个文件(对应实现见 caddy/php-server.go);
  • 带 Worker 入口启动:./my-app php-server --worker public/index.php
  • 指定域名以启用自动 HTTPS(Let's Encrypt)、HTTP/2、HTTP/3:./my-app php-server --domain localhost
  • 直接运行内嵌的 PHP CLI 脚本:./my-app php-cli bin/console
  • 分发优化:Linux 下构建时可设COMPRESS=1使用 UPX 压缩二进制;macOS 下推荐用xz压缩后分发。

更完整的选项说明及为其他操作系统构建二进制的方法,见 应用嵌入文档。

小结

围绕 Symfony 与 FrankenPHP 的组合,本文覆盖了五条核心实践路径:

场景推荐方案关键配置
开箱即用的完整环境Symfony Docker自带 Worker、HTTPS、HTTP/2/3、热重载
本地运行Caddyfile +frankenphp runphp_server+worker ./public/index.php
生产性能Worker 模式FRANKENPHP_CONFIG/--workerResetInterface状态清理
开发体验热重载mercure+hot_reload,模板注入热重载脚本
静态资源与文件AssetMapper 预压缩 + X-Sendfileprecompressedintercept+BinaryFileResponse
分发部署独立静态二进制static-builder-gnu+EMBED=dist/app/

相关仓库证据索引:docs/symfony.md、Worker 模式文档、热重载文档、X-Sendfile 文档、应用嵌入文档、性能文档、php-server 命令实现、worker 配置实现。按上述步骤实践,即可完成从开发到生产再到分发的完整闭环。

【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询