开发 PHP 扩展新途径 通过 FrankenPHP 用 Go 语言编写 PHP 扩展
开发 PHP 扩展新途径 通过 FrankenPHP 用 Go 语言编写 PHP 扩展
在 PHPVerse 2025 大会上(JetBrains 为纪念 PHP 语言 30 周年而组织的会议),FrankenPHP 开发者 Kévin Dunglas 做了一个开创性的宣布:通过 FrankenPHP,可以使用 Go 语言创建 PHP 扩展。虽然这个功能从项目诞生之初就存在,但今天要深入介绍这个小小的革命,让它变得更加容易上手。
早期探索
扩展是直接附加到 PHP 解释器的代码段,让你能够实现几乎任何想要的功能。之所以如此强大,主要是因为扩展通常用 C 语言编写,能够提供对机器的高级和低级访问能力。最著名的扩展包括 Parallel(在 PHP 中启用并行代码执行)、GD(图像处理)以及 PHP Redis(与 Redis 缓存服务器通信,还有其替代品如 Snapchat KeyDB)。
最近,也有很多使用 Rust 和 C++ 编写的扩展(虽然 C++ 已经存在很长时间,但不太流行)。如果你想了解更多,Packagist 列出了可以用 PIE 安装的扩展以及它们使用的编程语言。然而,所有这些语言都是非常底层的,学习门槛较高。对于 PHP 扩展开发来说更是如此,通常需要对 PHP 内部机制有相当深入的了解。既然 FrankenPHP 是用更高级的语言 Go 编写的,为什么不借此机会尝试用 Go 来编写 PHP 扩展呢?实际上,FrankenPHP 集成了 Go 运行时,所以完全可以使用这种语言,除了之前没人这么做过之外,没有任何特殊的技术限制。
Kévin Dunglas 团队开始探索这种可能性,并开发了一个概念验证,在其中向 PHP 添加一个新的原生函数来执行 Go 代码。经过几天的研究,结果出炉了:成功从 PHP 启动了一个 goroutine(与主代码并行执行的代码段)!这一切的关键在于 CGO 库,它让 C 代码能够调用 Go 代码,也能够从 Go 调用 C 代码。有了这个基础,就可以开发各种功能了。
FrankenPHP 作为工具库
从这些实验中发现,尽管有了这些可能性,但是仍然需要花费大量时间编写 C 代码:
- 扩展在 PHP 中的注册必须用 C 完成,因为这需要操作 Zend Engine 的一些内部指针。好消息是这段代码在所有扩展中基本相同;
- C 和 Go 之间的类型转换。许多变量类型在 C 和 Go 之间兼容,可以直接使用,比如整数、浮点数和布尔值。然而,更复杂的结构如字符串、对象和数组需要转换,不能直接使用。例如,在 PHP 解释器中,字符串由包含数据和字符串长度的结构表示。因此需要深入了解 PHP 的内部机制,而这些机制有时文档不全。LLM 在解释 PHP 引擎的某些复杂机制方面已经表现出色,但这仍然需要底层知识,并且存在内存损坏的风险。
- 必须编写 C 代码来调用 Go 代码。
FrankenPHP 在这里发挥了关键作用:作为工具库帮助开发者避免这三个问题。当然,你可以手工完成,相关文档解释了每个步骤,让你清楚了解从头开发扩展的各个阶段。但是,你也可以跳过这些步骤。
对于第一个问题,用 Go 编写的扩展将是 Go 模块,具体来说是 Caddy 模块。FrankenPHP 使用 Caddy 作为集成的 Web 服务器,这使得能够通过一行代码集成自定义模块。特别是,自定义模块可以是带有 init() 函数的 Go 文件,该函数在模块被 Caddy 启动时执行。这是注册扩展的理想位置。得益于项目的最新贡献,FrankenPHP 提供了一个 RegisterExtension() 方法,该方法抽象了注册扩展所需的所有 C 代码。不深入所有细节(如果你好奇,文档中有详细说明),在 Go 中注册扩展看起来是这样的:
package ext
/*
#include "ext.h"
*/
import "C"
import "github.com/dunglas/frankenphp"
func init() {
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
}
这里没有写一行 C 代码,扩展就成功注册了!
对于类型转换问题,FrankenPHP 再次提供了公开的功能来抽象内部类型机制。正如之前看到的,一些标量类型不需要转换。但其他类型需要,比如字符串。这就是为什么 FrankenPHP 提供了诸如 frankenphp.GoString()(从 C 字符串获取 Go 字符串)和 frankenphp.PHPString()(将 Go 字符串转换为 PHP 可用的字符串)等方法,这些方法自动处理转换。随着时间的推移,会为其他数据类型添加更多工具方法。数组已经得到支持,可调用对象正在开发中。
再次强调,这篇文章只是触及了这些新功能的表面,完整的文档已经在线,其中有详细的解释。
如你所见,FrankenPHP 在促进 PHP 扩展创建方面发挥着关键作用,提供了大大简化扩展编码的工具。然而,仍有最后一个问题需要解决:编写调用 Go 代码所需的 C 代码。接下来看看如何解决这个问题。
扩展生成器
意识到需要编写数十行甚至数百行 C 代码来实现 C 和 Go 之间的"桥接",开发团队思考了下一步。是否可能创建一个 PHP 扩展生成器,它接收一个简单的 Go 文件作为输入(可能带有一些特殊语法),然后完成其余工作?答案是肯定的!经过几周的开发,一个新工具已被集成到 FrankenPHP 中作为子命令:扩展生成器。主要目标很明确:开发者必须能够编译和集成 PHP 扩展,而无需编写一行 C 代码。
生成器定义了特定的 Go 指令。把 Go 指令想象成 PHP 中的注解或属性。这是一个与扩展生成器兼容的 Go 文件示例:
package main
// export_php:function multiply(int $a, int $b): int
func multiply(a int64, b int64) int64 {
return a * b
}
// export_php:function is_even(int $a): bool
func is_even(a int64) bool {
return a%2 == 0
}
// export_php:function float_div(float $a, float $b): float
func float_div(a float64, b float64) float64 {
return a / b
}
通过 // export_php:function 指令后跟函数签名,生成器将负责定义所有中间 C 代码。剩下的就是将此文件传递给生成器:
alex@alex-macos frankenphp % frankenphp extension-init ext-dir/ext.go
2025/06/20 09:49:09.273 INFO PHP 扩展 "ext" 在 "ext-dir/build" 中初始化成功
alex@alex-macos frankenphp % ls -la ext-dir/build
total 48
drwxr-xr-x@ 8 alex staff 256 Jun 20 11:49 .
drwxr-xr-x@ 8 alex staff 256 Jun 20 11:49 ..
-rw-r--r--@ 1 alex staff 418 Jun 20 11:49 README.md
-rw-r--r--@ 1 alex staff 1673 Jun 20 11:49 ext.c
-rw-r--r--@ 1 alex staff 396 Jun 20 11:49 ext.go
-rw-r--r--@ 1 alex staff 226 Jun 20 11:49 ext.h
-rw-r--r--@ 1 alex staff 168 Jun 20 11:49 ext.stub.php
-rw-r--r--@ 1 alex staff 865 Jun 20 11:49 ext_arginfo.h
FrankenPHP 创建了 PHP 扩展所需的所有文件:
- 包含导出元素说明的
README.md文件; - 包含想要避免编写的中间代码的 C 文件;
- Go 文件,与原始文件非常相似,但稍作修改以与 C 代码正确协作;
- 包含某些函数定义的头文件,使 C 和 Go 代码能够良好协作;
- 带有 PHP 函数签名定义的存根文件;
- 包含向 PHP 的 Zend Engine 注册新函数所需的所有指令的 arginfo 文件。
正如刚才看到的,Caddy 允许你添加额外的自定义模块来扩展其功能:只需给 Caddy 构建目录,它就会处理其余工作。扩展完全可用,无需手工编写一行 C 代码!
这个生成器的强大之处在于它对其他重要功能的支持。这是一个更完整的兼容文件示例:
package main
// export_php:namespace Go\MyExtension
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
"unsafe"
)
// export_php:const
const MY_GLOBAL_CONSTANT = "Hello, World!"
// export_php:classconst MySuperClass
const STR_REVERSE = iota
// export_php:classconst MySuperClass
const STR_NORMAL = iota
// export_php:class MySuperClass
type MyClass struct {
Name string
}
// export_php:method MySuperClass::setName(string $name): void
func (mc *MyClass) SetName(v *C.zend_string) {
// 将 C 字符串转换为 Go 字符串
mc.Name = frankenphp.GoString(unsafe.Pointer(v))
}
// export_php:method MySuperClass::getName(): string
func (mc *MyClass) GetName() unsafe.Pointer {
// 将 Go 字符串转换为 PHP 字符串
return frankenphp.PHPString(mc.Name, false)
}
// export_php:method MySuperClass::repeatName(int $count, ?int $mode): void
func (mc *MyClass) RepeatName(count int64, mode *int64) {
str := mc.Name
// 重复字符串指定次数
result := strings.Repeat(str, int(count))
if mode != nil && *mode == STR_REVERSE {
// 反转字符串
runes := []rune(result)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
result = string(runes)
}
if mode == nil || *mode == STR_NORMAL {
// 正常模式,无需操作,只是为了使用常量 :)
}
mc.Name = result
}
在这里可以看到全局常量、类、类常量和类方法的声明。另外需要注意字符串类型转换方法的使用。需要说明的是,生成器几乎无法覆盖 PHP 扩展提供的所有可能性。然而,对于没有过度高级功能的扩展项目,它可以提供真正的帮助。推荐你查看文档,其中描述了生成器支持的所有功能。
为什么选择 Go 扩展?
可能有人会问:为 PHP 编写 Go 扩展有什么意义?经过三十年的发展,看到 PHP 继续与更新的技术如此良好地集成确实令人振奋。与 Go 的接口需要 FrankenPHP。自从 PHP 基金会最近宣布正式将 FrankenPHP 纳入其管辖以来,它的使用前景将继续增长,长期发展得到了保障。
Go 扩展背后的理念可以用几个关键词概括:goroutines 和包装器。首先,goroutines 是该语言闻名的高性能并发模型。在 PHP 代码中对可能繁重和/或耗时的操作使用 goroutines 的能力带来了广阔的可能性。其次,现有库的包装器同样带来了许多新的可能性。许多 Go 库以其质量而闻名,但在 PHP 中不可用。一个例子是 etcd 缓存系统,开发者 Kévin 为此创建了一个完整的 Go 扩展供 PHP 使用。你可以在扩展仓库中找到这个示例。
提供扩展生成器是朝着 PHP 扩展创建民主化迈出的重要一步,降低了开发门槛。任何人都可以快速尝试,探索生成的代码以了解其工作原理,甚至开发出 PHP 生态系统中的下一个优秀扩展。
如果你想了解更多关于如何编写自己的扩展,文档将解释如何使用生成器,以及如何在不使用生成器的情况下编写自己的 Go 扩展。期待看到这两种语言之间独特的协作关系将如何被运用。
原文-开发 PHP 扩展新途径 通过 FrankenPHP 用 Go 语言编写 PHP 扩展
开发 PHP 扩展新途径 通过 FrankenPHP 用 Go 语言编写 PHP 扩展的更多相关文章
- 微服务扩展新途径:Messaging
[编者按]服务编排是微服务设置的一个重要方面.本文在利用 ActiveMQ 虚拟话题来实现这一目标的同时,还会提供实用性指导.文章系国内 ITOM 管理平台 OneAPM 编译呈现. 目前,微服务使用 ...
- AR/VR-VR-Info-Micron-Insight:虚拟现实开辟心理健康新途径
ylbtech-AR/VR-VR-Info-Micron-Insight:虚拟现实开辟心理健康新途径 1.返回顶部 1. 虚拟现实开辟心理健康新途径 全国心理疾病联盟最近发表的一份报告揭示了惊人的统计 ...
- unity3D实际的原始视频游戏开发系列讲座12之U3D的2D为了开发实战的新方法
U3D的2D为了开发实战的新方法 (Unity3d-4.x的打飞机2D游戏开发新的方法应用 ) 大纲介绍:不使用NGUI和TK2d插件, 使用 U3D内置强大的最大的工具. 开发过程设计到例 ...
- Android开发艺术探索——新的征程,程序人生路漫漫!
Android开发艺术探索--新的征程,程序人生路漫漫! 偶尔写点东西分享,但是我还是比较喜欢写笔记,看书,群英传看完了,是学到了点东西,开始看这本更加深入Android的书籍了,不知道适不适合自己, ...
- 【视频】使用fiddler开发工具进行新架构页面本地调试
[视频]使用fiddler开发工具进行新架构页面本地调试,视频没录制好,有些部分比较模糊...
- Spring Cloud Eureka集群 动态扩展新节点
场景描述: Eureka的集群节点有两个,互相注册形成集群,已经支持动态刷新(不知道怎么让Eureka支持动态刷新的可以参考http://www.cnblogs.com/flying607/p/845 ...
- cocos2d-x -3.81+win7+vs2013开发环境创建新的项目
cocos2d-x -3.81+win7+vs2013开发环境创建新的项目 1.准备阶段 (1) vs2013下载及安装 (2)cocos2d-x 3.8.1下载及解压 (3)python下载及安装( ...
- 开拓新途径找出新方法,上海SEO公司分享3个操作看看是否可行
开拓新途径找出新方法,上海SEO公司分享3个操作看看是否可行 内容收录,外链公布,流量点击.用户体验.这是SEO优化的几个核心和重点.也是SEO站长每天都在绞尽脑汁进行操作的SEO重心,影响着非常多人 ...
- 用Zephir编写PHP扩展
自从NodeJS,和Golang出来后,很多人都投奔过去了.不为什么,冲着那牛X的性能.那PHP的性能什么时候能提升一下呢?要不然就会被人鄙视了.其实大牛们也深刻体会到了这些威胁,于是都在秘密开发各种 ...
- 一步步入门编写PHP扩展
1.写在最前 随着互联网飞速发展,lamp架构的流行,php支持的扩展也越来越多,这样直接促进了php的发展. 但是php也有脚本语言不可避免的问题,性能比例如C等编译型语言相差甚多,所以在考虑性能问 ...
随机推荐
- vscode删除空行和注释
打开VScode,按Ctrl + H 打开替换框,切换正则模式 单行注释: //[\s\S]*?\n 多行注释:/\*(.|\r\n|\n)*?\*/ 所有注释:\/\*[\s\S]*\*\/|\/\ ...
- antd vue3 图片 手动上传
<template> <a-upload v-model:file-list="fileList" name="avatar" list-ty ...
- Eclipse WindowBuilder(SWT)插件安装及初次使用记录(萌新)
Eclipse WindowBuilder(SWT)插件安装及初次使用(萌新) 一.插件安装 (有VPN的挂VPN,服务器在外网更新下载比较慢) 1.首先更新到最新版本 点击Help,点击check ...
- bmp文件结构解析
参考博客园 虽然项目里面用的是png 但是很可惜png我没有怎么搞懂,先搞懂最容易解析的bmp数据, 个人所见bmp32位表示的是基本上是没有压缩的数据. 先用 ps 生成一个8*8黑色的数据. bm ...
- runnable & callable
简介 简单来说这两个接口都是执行多线程里面使用的东西. 参考链接 https://blog.csdn.net/qq_41357573/article/details/88887566 区别 Java多 ...
- C++ 元编程 学习二
简介 C++ 元编程 学习之二 参考资料 C++ 模板元编程实战 code #include <iostream> // 编译器分支与多种返回类型 写法1 template <boo ...
- java 代理类
简介 理解不透彻 简而言之,就是在程序运行过程中创建的类 一个代理类只有一个实例域--调用处理器.invoke? 总而言之 reflect 的理解都不是特别透彻 code package cn; im ...
- fowsniff WP
下载地址: https://download.vulnhub.com/fowsniff/Fowsniff_CTF_ova.7z category:重要 awk剪切得到字典,巩固awk使用技巧 motd ...
- POLIR-Laws-Business Insurance: 保险合同欺诈: 拼多多平台 与 中国人寿财险 的 "正品险"(套路)。
本人 已经电话联系过"ACA/北美电器"官方客服0756-3390136与4007006698, 得到明确的"官方鉴定标准"之一: "没有镭射防伪贴的 ...
- SciTech-BigDataAIML-LLM-Transformer Series-Positional Encoding: 位置编码: 统计模型(够多参数够高精度)+"够大数据"凝聚客观规律"预训练+深度NN(学习规律).
词汇 $\large MI $(Mobile Internet): 移动互联网 $\large IoT $(Internet of Things): 万物互联网 \(\large Supervised ...