通过 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\MyExtensionimport ("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 扩展

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/922071.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/922071.shtml
英文地址,请注明出处:http://en.pswp.cn/news/922071.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完美解决:应用版本更新,增加字段导致 Redis 旧数据反序列化报错

完美解决&#xff1a;应用版本更新&#xff0c;增加字段导致 Redis 旧数据反序列化报错 前言 在敏捷开发和快速迭代的今天&#xff0c;我们经常需要为现有的业务模型增加新的字段。但一个看似简单的操作&#xff0c;却可能给正在稳定运行的系统埋下“地雷”。 一个典型的场景是…

66-python中的文件操作

1. 文件的编码 UTF-8 GBK GB2312 Big5 GB18030 2. 文件读取 文件操作步骤: 打开文件 读\写文件 关闭文件 open(name,mode,encoding) name:文件名字符串 “D:/haha.txt” mode: 只读、写入、追加 r:以只读方式打开 w: 只用于写 a :用于追加 encoding:编码方式 # -*- coding: utf…

FPGA实例源代码集锦:27个实战项目

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;FPGA是一种可编程逻辑器件&#xff0c;允许用户根据需求配置硬件功能。本压缩包提供27个不同的FPGA应用实例源代码&#xff0c;旨在帮助初学者深入学习FPGA设计&#xff0c;并为专业工程师提供灵感。内容涵盖了…

基于 Vue+Mapbox 的智慧矿山可视化功能的技术拆解

01、项目背景 在全球矿业加速向 “高端化、智能化、绿色化” 转型的浪潮下&#xff0c;传统矿业面临的深地开采难题、效率瓶颈与安全隐患日益凸显。 在矿业转型的迫切需求与政策、技术支撑的背景下依托 GIS 技术&#xff0c;开展了 “中国智矿” GIS 开发项目&#xff0c;旨在…

进程状态(Linux)

进程状态Linux进程状态Linux进程状态进程描述R运行状态S睡眠状态D磁盘休眠状态T停止状态t被追踪状态(调试状态)X死亡状态Z僵死状态其实大致也就可以分为三种运行&#xff0c;阻塞&#xff0c;挂起。运行状态每个cpu里都有一个运行队列&#xff0c;进程在运行队列里&#xff0c;…

物联网领域中PHP框架的最佳选择有哪些?

物联网&#xff08;IoT&#xff09;作为近年来快速发展的技术领域&#xff0c;已经渗透到智能家居、工业自动化、智慧城市等方方面面。作为Web开发中广泛使用的语言&#xff0c;PHP凭借其易学易用、开发效率高和生态丰富的特点&#xff0c;也在物联网领域找到了用武之地。 本文…

java反射(详细教程)

我们平常创建类的实例并调用类中成员需要建立在一个前提下&#xff0c;就是已经知道类名和类中成员的信息&#xff0c;灵活性大大降低。甚至在一些项目中还需要修改源码来满足使用条件&#xff0c;大大降低了操作的灵活性。Java 反射&#xff08;Reflection&#xff09;是 Java…

消息队列-初识kafka

优缺点 消息队列的优点&#xff1a; 实现系统解耦&#xff1a; :::color5 系统解耦解释 有 MQ 时是 “服务 A 发消息到队列&#xff0c;其他服务从队列拿消息&#xff0c;新增服务接队列就行”&#xff1b;无 MQ 时是 “服务 A 直接调其他服务的接口 / 依赖&#xff0c;新增 / …

实践《数字图像处理》之Canny边缘检测、霍夫变换与主动二值化处理在短线段清除应用中的实践

在最近的图像处理项目中&#xff0c;其中一个环节&#xff1a;图片中大量短线&#xff08;不是噪声&#xff09;&#xff0c;需要在下一步处理前进行清除。在确定具体实现时&#xff0c;碰到了Canny边缘检测、霍夫变换与主动二值化处理的辩证使用&#xff0c;相关逻辑从图片灰度…

vue3与ue5通信-工具类

工具 ue5-simple.js /*** UE5 通信工具* 两个核心方法&#xff1a;发送消息和接收消息*/// 确保全局对象存在 if (typeof window ! undefined) {window.ue window.ue || {};window.ue.interface window.ue.interface || {}; }/*** 生成 UUID*/ function generateUUID() {retu…

在kotlin中如何使用像java中的static

在 Kotlin 中&#xff0c;没有直接的 static 关键字&#xff0c;但有几种等效的方式来实现 Java 中静态成员的功能&#xff1a; 1. 伴生对象 (Companion Object) - 最常用 class MyClass {companion object {// 静态常量const val STATIC_CONSTANT "constant value"…

如何在 Spring Boot 中指定不同的配置文件?

介绍 Spring Boot 提供了多种方式来管理和加载配置文件&#xff0c;特别是在多环境配置下&#xff0c;比如开发、测试和生产环境。通过指定不同的配置文件&#xff0c;可以灵活地调整应用程序的行为&#xff0c;以适应不同的需求。本文将介绍在 Spring Boot 中如何指定使用不同…

在centOS源码编译方式安装MySQL5.7

一、前言 在生产环境中部署数据库时&#xff0c;很多人会选择直接使用 yum/apt 包管理器 安装 MySQL&#xff0c;这样简单快速&#xff0c;但缺点是版本受限&#xff0c;灵活性不足。对于需要指定版本、启用特定编译参数或优化的场景&#xff0c;源码编译安装 MySQL 就显得非常…

探讨Hyperband 等主要机器学习调优方法的机制和权衡

本篇文章Master Hyperband — An Efficient Hyperparameter Tuning Method in Machine Learning深入探讨了Hyperband这一高效的超参数调优方法。文章的技术亮点在于其结合了多臂老虎机策略和逐次减半算法&#xff0c;能够在大搜索空间中快速剔除表现不佳的配置&#xff0c;从而…

Mysql:InnoDB 关键特性

目录 一、插入缓冲&#xff08;Change Buffer&#xff09;→ 快递驿站的 “临时存放区” 二、两次写&#xff08;Double Write&#xff09;→ 重要文件的 “备份存档” 三、自适应哈希索引&#xff08;AHI&#xff09;→ 图书馆的 “热门书快捷查找区” 四、异步 IO&#x…

STM32-----SPI

SPI简介SCK:和I2C中SCL的时钟线一个作用&#xff0c;都是在高电平拿出数据&#xff0c;在低电平写数据MOSI:主机输出从机输入MISO:主机输入从机输出&#xff0c;只有当对应从机的SS为低电平&#xff0c;从机的MISO引脚才能设置推挽输出&#xff0c;当从机SS为高电平时&#xff…

华为考试:HCIE数通考试难度分析

随着信息技术的飞速发展&#xff0c;网络技术已成为支撑各行各业运转的重要基础&#xff0c;市场对高水平网络技术人才的需求持续增长。HCIE作为华为认证体系中的最高级别认证&#xff0c;代表了网络技术领域的专业顶尖水平。本文将对HCIE数通认证的考试内容、难度及备考策略进…

一些常用的激活函数及绘图

深度网络的一些常用激活函数&#xff0c;并通过matplot绘制出来&#xff1a; import matplotlib.pyplot as plt import numpy as npdef relu(x):return np.maximum(0, x)def leaky_relu(x, alpha0.01):return np.where(x > 0, x, alpha * x)def gelu(x):return 0.5 * x * (1…

AE苹果手机iPhone 17展示动画片头模板 App Promo Phone 17 Pro

专为 App 发布会、电商促销、新品宣传 打造的 iPhone 17 Pro 动画展示 AE 模板。 4K 超清分辨率 26 张可替换照片位&#xff0c;无需第三方插件&#xff0c;拖拽即可输出专业级手机宣传片。 核心亮点 4K 超清&#xff1a;38402160 分辨率&#xff0c;大屏投放与社媒高清压缩无…

基于Python的云原生TodoList Demo 项目,验证云原生核心特性

以下是一个基于 Python 的云原生 TodoList Demo 项目&#xff0c;涵盖 容器化、Kubernetes 编排、CI/CD、可观测性、弹性扩缩容 等核心云原生特性&#xff0c;代码简洁且附详细操作指南&#xff0c;适合入门学习。项目概览 目标&#xff1a;实现一个支持增删改查&#xff08;CR…