Skip to content

Commit 52a0be5

Browse files
authored
feat(ext): expose GoValue() and PHPValue() functions (#1877)
* feat(ext): expose a GoValue function * GoValue()
1 parent 960dd20 commit 52a0be5

25 files changed

Lines changed: 430 additions & 414 deletions

docs/cn/CONTRIBUTING.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -115,22 +115,22 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
115115

116116
1. 从 GitHub 下载 FrankenPHP 二进制文件的调试版本或创建包含调试符号的自定义静态构建:
117117

118-
```console
119-
docker buildx bake \
120-
--load \
121-
--set static-builder.args.DEBUG_SYMBOLS=1 \
122-
--set "static-builder.platform=linux/amd64" \
123-
static-builder
124-
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
125-
```
118+
```console
119+
docker buildx bake \
120+
--load \
121+
--set static-builder.args.DEBUG_SYMBOLS=1 \
122+
--set "static-builder.platform=linux/amd64" \
123+
static-builder
124+
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
125+
```
126126

127127
2. 将当前版本的 `frankenphp` 替换为 debug FrankenPHP 可执行文件
128128
3. 照常启动 FrankenPHP(或者,你可以直接使用 GDB 启动 FrankenPHP: `gdb --args frankenphp run`
129129
4. 使用 GDB 附加到进程:
130130

131-
```console
132-
gdb -p `pidof frankenphp`
133-
```
131+
```console
132+
gdb -p `pidof frankenphp`
133+
```
134134

135135
5. 如有必要,请在 GDB shell 中输入 `continue`
136136
6. 使 FrankenPHP 崩溃
@@ -142,13 +142,13 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
142142
1. 打开 `.github/workflows/tests.yml`
143143
2. 启用 PHP 调试符号
144144

145-
```patch
146-
- uses: shivammathur/setup-php@v2
147-
# ...
148-
env:
149-
phpts: ts
150-
+ debug: true
151-
```
145+
```patch
146+
- uses: shivammathur/setup-php@v2
147+
# ...
148+
env:
149+
phpts: ts
150+
+ debug: true
151+
```
152152

153153
3. 启用 `tmate` 以连接到容器
154154

@@ -166,18 +166,18 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
166166
5. 打开 `frankenphp.go`
167167
6. 启用 `cgosymbolizer`
168168

169-
```patch
170-
- //_ "github.com/ianlancetaylor/cgosymbolizer"
171-
+ _ "github.com/ianlancetaylor/cgosymbolizer"
172-
```
169+
```patch
170+
- //_ "github.com/ianlancetaylor/cgosymbolizer"
171+
+ _ "github.com/ianlancetaylor/cgosymbolizer"
172+
```
173173

174174
7. 下载模块: `go get`
175175
8. 在容器中,可以使用 GDB 和以下:
176176

177-
```console
178-
go test -tags watcher -c -ldflags=-w
179-
gdb --args frankenphp.test -test.run ^MyTest$
180-
```
177+
```console
178+
go test -tags watcher -c -ldflags=-w
179+
gdb --args frankenphp.test -test.run ^MyTest$
180+
```
181181

182182
9. 当错误修复后,恢复所有这些更改
183183

docs/cn/compile.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ sudo make install
7979
某些 FrankenPHP 功能依赖于必须安装的可选系统依赖项。
8080
或者,可以通过向 Go 编译器传递构建标签来禁用这些功能。
8181

82-
| 功能 | 依赖项 | 用于禁用的构建标签 |
83-
|--------------------------|------------------------------------------------------------------------|-------------------|
84-
| Brotli 压缩 | [Brotli](https://github.com/google/brotli) | nobrotli |
85-
| 文件更改时重启 worker | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
82+
| 功能 | 依赖项 | 用于禁用的构建标签 |
83+
| --------------------- | --------------------------------------------------------------------- | ------------------ |
84+
| Brotli 压缩 | [Brotli](https://github.com/google/brotli) | nobrotli |
85+
| 文件更改时重启 worker | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
8686

8787
## 编译 Go 应用
8888

docs/cn/embed.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ FrankenPHP 能够将 PHP 应用程序的源代码和资源文件嵌入到静态
1414

1515
例如,你可能希望:
1616

17-
* 给应用安装生产环境的依赖
18-
* 导出 autoloader
19-
* 如果可能,为应用启用生产模式
20-
* 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
17+
- 给应用安装生产环境的依赖
18+
- 导出 autoloader
19+
- 如果可能,为应用启用生产模式
20+
- 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
2121

2222
例如,对于 Symfony 应用程序,你可以使用以下命令:
2323

@@ -53,34 +53,34 @@ composer dump-env prod
5353

5454
1. 在准备好的应用的存储库中创建一个名为 `static-build.Dockerfile` 的文件。
5555

56-
```dockerfile
57-
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
56+
```dockerfile
57+
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
5858

59-
# 复制应用代码
60-
WORKDIR /go/src/app/dist/app
61-
COPY . .
59+
# 复制应用代码
60+
WORKDIR /go/src/app/dist/app
61+
COPY . .
6262

63-
# 构建静态二进制文件
63+
# 构建静态二进制文件
6464
WORKDIR /go/src/app/
6565
RUN EMBED=dist/app/ ./build-static.sh
6666
```
6767

68-
> [!CAUTION]
69-
>
70-
> 某些 `.dockerignore` 文件(例如默认的 [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)
71-
> 会忽略 `vendor/` 文件夹和 `.env` 文件。在构建之前,请务必调整或删除 `.dockerignore` 文件。
68+
> [!CAUTION]
69+
>
70+
> 某些 `.dockerignore` 文件(例如默认的 [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)
71+
> 会忽略 `vendor/` 文件夹和 `.env` 文件。在构建之前,请务必调整或删除 `.dockerignore` 文件。
7272
7373
2. 构建:
7474

75-
```console
76-
docker build -t static-app -f static-build.Dockerfile .
77-
```
75+
```console
76+
docker build -t static-app -f static-build.Dockerfile .
77+
```
7878

7979
3. 提取二进制文件
8080

81-
```console
82-
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
83-
```
81+
```console
82+
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
83+
```
8484

8585
生成的二进制文件是当前目录中名为 `my-app` 的文件。
8686

docs/cn/extensions.md

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
7272

7373
这里有两个重要的事情要注意:
7474

75-
* 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
76-
* 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
75+
- 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
76+
- 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
7777

7878
虽然第一点不言自明,但第二点可能更难理解。让我们在下一节中深入了解类型转换。
7979

@@ -82,16 +82,17 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
8282
虽然一些变量类型在 C/PHP 和 Go 之间具有相同的内存表示,但某些类型需要更多逻辑才能直接使用。这可能是编写扩展时最困难的部分,因为它需要了解 Zend 引擎的内部结构以及变量在 PHP 中的内部存储方式。此表总结了你需要知道的内容:
8383

8484
| PHP 类型 | Go 类型 | 直接转换 | C 到 Go 助手 | Go 到 C 助手 | 类方法支持 |
85-
|--------------------|---------------------|----------|----------------------|----------------------|------------|
86-
| `int` | `int64` || - | - ||
87-
| `?int` | `*int64` || - | - ||
88-
| `float` | `float64` || - | - ||
89-
| `?float` | `*float64` || - | - ||
90-
| `bool` | `bool` || - | - ||
91-
| `?bool` | `*bool` || - | - ||
92-
| `string`/`?string` | `*C.zend_string` || frankenphp.GoString() | frankenphp.PHPString() ||
93-
| `array` | `*frankenphp.Array` || frankenphp.GoArray() | frankenphp.PHPArray() ||
94-
| `object` | `struct` || _尚未实现_ | _尚未实现_ ||
85+
| ------------------ | ------------------- | -------- | --------------------- | ---------------------- | ---------- |
86+
| `int` | `int64` || - | - ||
87+
| `?int` | `*int64` || - | - ||
88+
| `float` | `float64` || - | - ||
89+
| `?float` | `*float64` || - | - ||
90+
| `bool` | `bool` || - | - ||
91+
| `?bool` | `*bool` || - | - ||
92+
| `string`/`?string` | `*C.zend_string` || frankenphp.GoString() | frankenphp.PHPString() ||
93+
| `array` | `*frankenphp.Array` || frankenphp.GoArray() | frankenphp.PHPArray() ||
94+
| `mixed` | `any` || `GoValue()` | `PHPValue()` ||
95+
| `object` | `struct` || _尚未实现_ | _尚未实现_ ||
9596

9697
> [!NOTE]
9798
> 此表尚不详尽,将随着 FrankenPHP 类型 API 变得更加完整而完善。
@@ -111,16 +112,16 @@ FrankenPHP 通过 `frankenphp.Array` 类型为 PHP 数组提供原生支持。
111112
func process_data(arr *C.zval) unsafe.Pointer {
112113
// 将 PHP 数组转换为 Go
113114
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
114-
115+
115116
result := &frankenphp.Array{}
116-
117+
117118
result.SetInt(0, "first")
118119
result.SetInt(1, "second")
119120
result.Append("third") // 自动分配下一个整数键
120-
121+
121122
result.SetString("name", "John")
122123
result.SetString("age", int64(30))
123-
124+
124125
for i := uint32(0); i < goArray.Len(); i++ {
125126
key, value := goArray.At(i)
126127
if key.Type == frankenphp.PHPStringKey {
@@ -129,28 +130,28 @@ func process_data(arr *C.zval) unsafe.Pointer {
129130
result.SetInt(key.Int+100, value)
130131
}
131132
}
132-
133+
133134
// 转换回 PHP 数组
134135
return frankenphp.PHPArray(result)
135136
}
136137
```
137138

138139
**`frankenphp.Array` 的关键特性:**
139140

140-
* **有序键值对** - 像 PHP 数组一样维护插入顺序
141-
* **混合键类型** - 在同一数组中支持整数和字符串键
142-
* **类型安全** - `PHPKey` 类型确保正确的键处理
143-
* **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
144-
* **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
141+
- **有序键值对** - 像 PHP 数组一样维护插入顺序
142+
- **混合键类型** - 在同一数组中支持整数和字符串键
143+
- **类型安全** - `PHPKey` 类型确保正确的键处理
144+
- **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
145+
- **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
145146

146147
**可用方法:**
147148

148-
* `SetInt(key int64, value interface{})` - 使用整数键设置值
149-
* `SetString(key string, value interface{})` - 使用字符串键设置值
150-
* `Append(value interface{})` - 使用下一个可用整数键添加值
151-
* `Len() uint32` - 获取元素数量
152-
* `At(index uint32) (PHPKey, interface{})` - 获取索引处的键值对
153-
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
149+
- `SetInt(key int64, value interface{})` - 使用整数键设置值
150+
- `SetString(key string, value interface{})` - 使用字符串键设置值
151+
- `Append(value interface{})` - 使用下一个可用整数键添加值
152+
- `Len() uint32` - 获取元素数量
153+
- `At(index uint32) (PHPKey, interface{})` - 获取索引处的键值对
154+
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
154155

155156
### 声明原生 PHP 类
156157

@@ -168,11 +169,11 @@ type UserStruct struct {
168169

169170
**不透明类**是内部结构(属性)对 PHP 代码隐藏的类。这意味着:
170171

171-
* **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
172-
* **仅方法接口** - 所有交互必须通过你定义的方法进行
173-
* **更好的封装** - 内部数据结构完全由 Go 代码控制
174-
* **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
175-
* **更清晰的 API** - 强制设计适当的公共接口
172+
- **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
173+
- **仅方法接口** - 所有交互必须通过你定义的方法进行
174+
- **更好的封装** - 内部数据结构完全由 Go 代码控制
175+
- **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
176+
- **更清晰的 API** - 强制设计适当的公共接口
176177

177178
这种方法提供了更好的封装,并防止 PHP 代码意外破坏 Go 对象的内部状态。与对象的所有交互都必须通过你明确定义的方法进行。
178179

@@ -219,12 +220,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
219220
if name != nil {
220221
us.Name = frankenphp.GoString(unsafe.Pointer(name))
221222
}
222-
223+
223224
// 检查是否提供了 age(不为 null)
224225
if age != nil {
225226
us.Age = int(*age)
226227
}
227-
228+
228229
// 检查是否提供了 active(不为 null)
229230
if active != nil {
230231
us.Active = *active
@@ -234,10 +235,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
234235

235236
**关于可空参数的要点:**
236237

237-
* **可空原始类型**`?int``?float``?bool`)在 Go 中变成指针(`*int64``*float64``*bool`
238-
* **可空字符串**`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
239-
* **在解引用指针值之前检查 `nil`**
240-
* **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
238+
- **可空原始类型**`?int``?float``?bool`)在 Go 中变成指针(`*int64``*float64``*bool`
239+
- **可空字符串**`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
240+
- **在解引用指针值之前检查 `nil`**
241+
- **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
241242

242243
> [!WARNING]
243244
> 目前,类方法有以下限制。**不支持对象**作为参数类型或返回类型。**完全支持数组**作为参数和返回类型。支持的类型:`string``int``float``bool``array``void`(用于返回类型)。**完全支持可空参数类型**,适用于所有标量类型(`?string``?int``?float``?bool`)。
@@ -356,7 +357,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
356357
str := frankenphp.GoString(unsafe.Pointer(s))
357358

358359
result := strings.Repeat(str, int(count))
359-
if mode == STR_REVERSE {
360+
if mode == STR_REVERSE {
360361
// 反转字符串
361362
}
362363

@@ -375,14 +376,14 @@ type StringProcessorStruct struct {
375376
//export_php:method StringProcessor::process(string $input, int $mode): string
376377
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
377378
str := frankenphp.GoString(unsafe.Pointer(input))
378-
379+
379380
switch mode {
380381
case MODE_LOWERCASE:
381382
str = strings.ToLower(str)
382383
case MODE_UPPERCASE:
383384
str = strings.ToUpper(str)
384385
}
385-
386+
386387
return frankenphp.PHPString(str, false)
387388
}
388389
```
@@ -437,17 +438,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
437438

438439
#### 重要说明
439440

440-
* 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
441-
* 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
442-
* 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
443-
* 如果没有声明命名空间,符号将照常导出到全局命名空间。
441+
- 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
442+
- 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
443+
- 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
444+
- 如果没有声明命名空间,符号将照常导出到全局命名空间。
444445

445446
### 生成扩展
446447

447448
这就是魔法发生的地方,现在可以生成你的扩展。你可以使用以下命令运行生成器:
448449

449450
```console
450-
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
451+
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
451452
```
452453

453454
> [!NOTE]
@@ -567,9 +568,9 @@ extern zend_module_entry ext_module_entry;
567568

568569
接下来,创建一个名为 `extension.c` 的文件,该文件将执行以下步骤:
569570

570-
* 包含 PHP 头文件;
571-
* 声明我们的新原生 PHP 函数 `go_print()`
572-
* 声明扩展元数据。
571+
- 包含 PHP 头文件;
572+
- 声明我们的新原生 PHP 函数 `go_print()`
573+
- 声明扩展元数据。
573574

574575
让我们首先包含所需的头文件:
575576

@@ -701,9 +702,9 @@ import "strings"
701702
//export go_upper
702703
func go_upper(s *C.zend_string) *C.zend_string {
703704
str := frankenphp.GoString(unsafe.Pointer(s))
704-
705+
705706
upper := strings.ToUpper(str)
706-
707+
707708
return (*C.zend_string)(frankenphp.PHPString(upper, false))
708709
}
709710
```

0 commit comments

Comments
 (0)