mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-09-26 19:41:13 +08:00
feat(ext): expose GoValue() and PHPValue() functions (#1877)
* feat(ext): expose a GoValue function * GoValue()
This commit is contained in:
@@ -115,22 +115,22 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
|
||||
1. 从 GitHub 下载 FrankenPHP 二进制文件的调试版本或创建包含调试符号的自定义静态构建:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. 将当前版本的 `frankenphp` 替换为 debug FrankenPHP 可执行文件
|
||||
3. 照常启动 FrankenPHP(或者,你可以直接使用 GDB 启动 FrankenPHP: `gdb --args frankenphp run`)
|
||||
4. 使用 GDB 附加到进程:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. 如有必要,请在 GDB shell 中输入 `continue`
|
||||
6. 使 FrankenPHP 崩溃
|
||||
@@ -142,13 +142,13 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
1. 打开 `.github/workflows/tests.yml`
|
||||
2. 启用 PHP 调试符号
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. 启用 `tmate` 以连接到容器
|
||||
|
||||
@@ -166,18 +166,18 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
5. 打开 `frankenphp.go`
|
||||
6. 启用 `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. 下载模块: `go get`
|
||||
8. 在容器中,可以使用 GDB 和以下:
|
||||
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. 当错误修复后,恢复所有这些更改
|
||||
|
||||
|
@@ -79,10 +79,10 @@ sudo make install
|
||||
某些 FrankenPHP 功能依赖于必须安装的可选系统依赖项。
|
||||
或者,可以通过向 Go 编译器传递构建标签来禁用这些功能。
|
||||
|
||||
| 功能 | 依赖项 | 用于禁用的构建标签 |
|
||||
|--------------------------|------------------------------------------------------------------------|-------------------|
|
||||
| Brotli 压缩 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| 文件更改时重启 worker | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
| 功能 | 依赖项 | 用于禁用的构建标签 |
|
||||
| --------------------- | --------------------------------------------------------------------- | ------------------ |
|
||||
| Brotli 压缩 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| 文件更改时重启 worker | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## 编译 Go 应用
|
||||
|
||||
|
@@ -14,10 +14,10 @@ FrankenPHP 能够将 PHP 应用程序的源代码和资源文件嵌入到静态
|
||||
|
||||
例如,你可能希望:
|
||||
|
||||
* 给应用安装生产环境的依赖
|
||||
* 导出 autoloader
|
||||
* 如果可能,为应用启用生产模式
|
||||
* 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
|
||||
- 给应用安装生产环境的依赖
|
||||
- 导出 autoloader
|
||||
- 如果可能,为应用启用生产模式
|
||||
- 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
|
||||
|
||||
例如,对于 Symfony 应用程序,你可以使用以下命令:
|
||||
|
||||
@@ -53,34 +53,34 @@ composer dump-env prod
|
||||
|
||||
1. 在准备好的应用的存储库中创建一个名为 `static-build.Dockerfile` 的文件。
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# 复制应用代码
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
# 复制应用代码
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# 构建静态二进制文件
|
||||
# 构建静态二进制文件
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 某些 `.dockerignore` 文件(例如默认的 [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> 会忽略 `vendor/` 文件夹和 `.env` 文件。在构建之前,请务必调整或删除 `.dockerignore` 文件。
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 某些 `.dockerignore` 文件(例如默认的 [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> 会忽略 `vendor/` 文件夹和 `.env` 文件。在构建之前,请务必调整或删除 `.dockerignore` 文件。
|
||||
|
||||
2. 构建:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. 提取二进制文件
|
||||
|
||||
```console
|
||||
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
|
||||
```
|
||||
```console
|
||||
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
|
||||
```
|
||||
|
||||
生成的二进制文件是当前目录中名为 `my-app` 的文件。
|
||||
|
||||
|
@@ -72,8 +72,8 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
这里有两个重要的事情要注意:
|
||||
|
||||
* 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
|
||||
* 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
|
||||
- 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
|
||||
- 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
|
||||
|
||||
虽然第一点不言自明,但第二点可能更难理解。让我们在下一节中深入了解类型转换。
|
||||
|
||||
@@ -82,16 +82,17 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
虽然一些变量类型在 C/PHP 和 Go 之间具有相同的内存表示,但某些类型需要更多逻辑才能直接使用。这可能是编写扩展时最困难的部分,因为它需要了解 Zend 引擎的内部结构以及变量在 PHP 中的内部存储方式。此表总结了你需要知道的内容:
|
||||
|
||||
| PHP 类型 | Go 类型 | 直接转换 | C 到 Go 助手 | Go 到 C 助手 | 类方法支持 |
|
||||
|--------------------|---------------------|----------|----------------------|----------------------|------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `object` | `struct` | ❌ | _尚未实现_ | _尚未实现_ | ❌ |
|
||||
| ------------------ | ------------------- | -------- | --------------------- | ---------------------- | ---------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _尚未实现_ | _尚未实现_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> 此表尚不详尽,将随着 FrankenPHP 类型 API 变得更加完整而完善。
|
||||
@@ -111,16 +112,16 @@ FrankenPHP 通过 `frankenphp.Array` 类型为 PHP 数组提供原生支持。
|
||||
func process_data(arr *C.zval) unsafe.Pointer {
|
||||
// 将 PHP 数组转换为 Go
|
||||
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
|
||||
|
||||
|
||||
result := &frankenphp.Array{}
|
||||
|
||||
|
||||
result.SetInt(0, "first")
|
||||
result.SetInt(1, "second")
|
||||
result.Append("third") // 自动分配下一个整数键
|
||||
|
||||
|
||||
result.SetString("name", "John")
|
||||
result.SetString("age", int64(30))
|
||||
|
||||
|
||||
for i := uint32(0); i < goArray.Len(); i++ {
|
||||
key, value := goArray.At(i)
|
||||
if key.Type == frankenphp.PHPStringKey {
|
||||
@@ -129,7 +130,7 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
result.SetInt(key.Int+100, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 转换回 PHP 数组
|
||||
return frankenphp.PHPArray(result)
|
||||
}
|
||||
@@ -137,20 +138,20 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
**`frankenphp.Array` 的关键特性:**
|
||||
|
||||
* **有序键值对** - 像 PHP 数组一样维护插入顺序
|
||||
* **混合键类型** - 在同一数组中支持整数和字符串键
|
||||
* **类型安全** - `PHPKey` 类型确保正确的键处理
|
||||
* **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
|
||||
* **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
|
||||
- **有序键值对** - 像 PHP 数组一样维护插入顺序
|
||||
- **混合键类型** - 在同一数组中支持整数和字符串键
|
||||
- **类型安全** - `PHPKey` 类型确保正确的键处理
|
||||
- **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
|
||||
- **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
|
||||
|
||||
**可用方法:**
|
||||
|
||||
* `SetInt(key int64, value interface{})` - 使用整数键设置值
|
||||
* `SetString(key string, value interface{})` - 使用字符串键设置值
|
||||
* `Append(value interface{})` - 使用下一个可用整数键添加值
|
||||
* `Len() uint32` - 获取元素数量
|
||||
* `At(index uint32) (PHPKey, interface{})` - 获取索引处的键值对
|
||||
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
|
||||
- `SetInt(key int64, value interface{})` - 使用整数键设置值
|
||||
- `SetString(key string, value interface{})` - 使用字符串键设置值
|
||||
- `Append(value interface{})` - 使用下一个可用整数键添加值
|
||||
- `Len() uint32` - 获取元素数量
|
||||
- `At(index uint32) (PHPKey, interface{})` - 获取索引处的键值对
|
||||
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
|
||||
|
||||
### 声明原生 PHP 类
|
||||
|
||||
@@ -168,11 +169,11 @@ type UserStruct struct {
|
||||
|
||||
**不透明类**是内部结构(属性)对 PHP 代码隐藏的类。这意味着:
|
||||
|
||||
* **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
|
||||
* **仅方法接口** - 所有交互必须通过你定义的方法进行
|
||||
* **更好的封装** - 内部数据结构完全由 Go 代码控制
|
||||
* **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
|
||||
* **更清晰的 API** - 强制设计适当的公共接口
|
||||
- **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
|
||||
- **仅方法接口** - 所有交互必须通过你定义的方法进行
|
||||
- **更好的封装** - 内部数据结构完全由 Go 代码控制
|
||||
- **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
|
||||
- **更清晰的 API** - 强制设计适当的公共接口
|
||||
|
||||
这种方法提供了更好的封装,并防止 PHP 代码意外破坏 Go 对象的内部状态。与对象的所有交互都必须通过你明确定义的方法进行。
|
||||
|
||||
@@ -219,12 +220,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// 检查是否提供了 age(不为 null)
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// 检查是否提供了 active(不为 null)
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -234,10 +235,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**关于可空参数的要点:**
|
||||
|
||||
* **可空原始类型**(`?int`、`?float`、`?bool`)在 Go 中变成指针(`*int64`、`*float64`、`*bool`)
|
||||
* **可空字符串**(`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
|
||||
* **在解引用指针值之前检查 `nil`**
|
||||
* **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
|
||||
- **可空原始类型**(`?int`、`?float`、`?bool`)在 Go 中变成指针(`*int64`、`*float64`、`*bool`)
|
||||
- **可空字符串**(`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
|
||||
- **在解引用指针值之前检查 `nil`**
|
||||
- **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
|
||||
|
||||
> [!WARNING]
|
||||
> 目前,类方法有以下限制。**不支持对象**作为参数类型或返回类型。**完全支持数组**作为参数和返回类型。支持的类型:`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 {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// 反转字符串
|
||||
}
|
||||
|
||||
@@ -375,14 +376,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -437,17 +438,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
|
||||
|
||||
#### 重要说明
|
||||
|
||||
* 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
|
||||
* 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
|
||||
* 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
|
||||
* 如果没有声明命名空间,符号将照常导出到全局命名空间。
|
||||
- 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
|
||||
- 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
|
||||
- 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
|
||||
- 如果没有声明命名空间,符号将照常导出到全局命名空间。
|
||||
|
||||
### 生成扩展
|
||||
|
||||
这就是魔法发生的地方,现在可以生成你的扩展。你可以使用以下命令运行生成器:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -567,9 +568,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
接下来,创建一个名为 `extension.c` 的文件,该文件将执行以下步骤:
|
||||
|
||||
* 包含 PHP 头文件;
|
||||
* 声明我们的新原生 PHP 函数 `go_print()`;
|
||||
* 声明扩展元数据。
|
||||
- 包含 PHP 头文件;
|
||||
- 声明我们的新原生 PHP 函数 `go_print()`;
|
||||
- 声明扩展元数据。
|
||||
|
||||
让我们首先包含所需的头文件:
|
||||
|
||||
@@ -701,9 +702,9 @@ import "strings"
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
@@ -4,8 +4,8 @@
|
||||
|
||||
已知以下扩展与 FrankenPHP 不兼容:
|
||||
|
||||
| 名称 | 原因 | 替代方案 |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| 名称 | 原因 | 替代方案 |
|
||||
| ----------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | 不安全的线程 | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | 不安全的线程 | - |
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
以下扩展在与 FrankenPHP 一起使用时已知存在错误和意外行为:
|
||||
|
||||
| 名称 | 问题 |
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 名称 | 问题 |
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | 在使用静态构建的 FrankenPHP(使用 musl libc 构建)时,在重负载下 OpenSSL 扩展可能会崩溃。一个解决方法是使用动态链接的构建(如 Docker 镜像中使用的版本)。此错误正在由 PHP 跟踪。[查看问题](https://github.com/php/php-src/issues/13648)。 |
|
||||
|
||||
## get_browser
|
||||
|
@@ -19,23 +19,23 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
1. [下载与你的系统相对应的二进制文件](https://github.com/php/frankenphp/releases)
|
||||
2. 将以下配置添加到 Laravel 项目根目录中名为 `Caddyfile` 的文件中:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# 服务器的域名
|
||||
localhost {
|
||||
# 将 webroot 设置为 public/ 目录
|
||||
root public/
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资源
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
# 服务器的域名
|
||||
localhost {
|
||||
# 将 webroot 设置为 public/ 目录
|
||||
root public/
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资源
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 从 Laravel 项目的根目录启动 FrankenPHP:`frankenphp run`
|
||||
|
||||
@@ -61,17 +61,17 @@ php artisan octane:frankenphp
|
||||
|
||||
`octane:frankenphp` 命令可以采用以下选项:
|
||||
|
||||
* `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`)
|
||||
* `--port`: 服务器应可用的端口(默认值: `8000`)
|
||||
* `--admin-port`: 管理服务器应可用的端口(默认值: `2019`)
|
||||
* `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`)
|
||||
* `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`)
|
||||
* `--caddyfile`:FrankenPHP `Caddyfile` 文件的路径(默认: [Laravel Octane 中的存根 `Caddyfile`](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
* `--https`: 开启 HTTPS、HTTP/2 和 HTTP/3,自动生成和延长证书
|
||||
* `--http-redirect`: 启用 HTTP 到 HTTPS 重定向(仅在使用 `--https` 时启用)
|
||||
* `--watch`: 修改应用程序时自动重新加载服务器
|
||||
* `--poll`: 在监视时使用文件系统轮询,以便通过网络监视文件
|
||||
* `--log-level`: 在指定日志级别或高于指定日志级别的日志消息
|
||||
- `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`)
|
||||
- `--port`: 服务器应可用的端口(默认值: `8000`)
|
||||
- `--admin-port`: 管理服务器应可用的端口(默认值: `2019`)
|
||||
- `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`)
|
||||
- `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`)
|
||||
- `--caddyfile`:FrankenPHP `Caddyfile` 文件的路径(默认: [Laravel Octane 中的存根 `Caddyfile`](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
- `--https`: 开启 HTTPS、HTTP/2 和 HTTP/3,自动生成和延长证书
|
||||
- `--http-redirect`: 启用 HTTP 到 HTTPS 重定向(仅在使用 `--https` 时启用)
|
||||
- `--watch`: 修改应用程序时自动重新加载服务器
|
||||
- `--poll`: 在监视时使用文件系统轮询,以便通过网络监视文件
|
||||
- `--log-level`: 在指定日志级别或高于指定日志级别的日志消息
|
||||
|
||||
> [!TIP]
|
||||
> 要获取结构化的 JSON 日志(在使用日志分析解决方案时非常有用),请明确传递 `--log-level` 选项。
|
||||
|
@@ -117,17 +117,17 @@ cd frankenphp
|
||||
以下环境变量可以传递给 `docker build` 和 `build-static.sh`
|
||||
脚本来自定义静态构建:
|
||||
|
||||
* `FRANKENPHP_VERSION`: 要使用的 FrankenPHP 版本
|
||||
* `PHP_VERSION`: 要使用的 PHP 版本
|
||||
* `PHP_EXTENSIONS`: 要构建的 PHP 扩展([支持的扩展列表](https://static-php.dev/zh/guide/extensions.html))
|
||||
* `PHP_EXTENSION_LIBS`: 要构建的额外库,为扩展添加额外的功能
|
||||
* `XCADDY_ARGS`:传递给 [xcaddy](https://github.com/caddyserver/xcaddy) 的参数,例如用于添加额外的 Caddy 模块
|
||||
* `EMBED`: 要嵌入二进制文件的 PHP 应用程序的路径
|
||||
* `CLEAN`: 设置后,libphp 及其所有依赖项都是重新构建的(不使用缓存)
|
||||
* `NO_COMPRESS`: 不要使用UPX压缩生成的二进制文件
|
||||
* `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
|
||||
* `MIMALLOC`: (实验性,仅限Linux) 用[mimalloc](https://github.com/microsoft/mimalloc)替换musl的mallocng,以提高性能。我们仅建议在musl目标构建中使用此选项,对于glibc,建议禁用此选项,并在运行二进制文件时使用[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)。
|
||||
* `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
|
||||
- `FRANKENPHP_VERSION`: 要使用的 FrankenPHP 版本
|
||||
- `PHP_VERSION`: 要使用的 PHP 版本
|
||||
- `PHP_EXTENSIONS`: 要构建的 PHP 扩展([支持的扩展列表](https://static-php.dev/zh/guide/extensions.html))
|
||||
- `PHP_EXTENSION_LIBS`: 要构建的额外库,为扩展添加额外的功能
|
||||
- `XCADDY_ARGS`:传递给 [xcaddy](https://github.com/caddyserver/xcaddy) 的参数,例如用于添加额外的 Caddy 模块
|
||||
- `EMBED`: 要嵌入二进制文件的 PHP 应用程序的路径
|
||||
- `CLEAN`: 设置后,libphp 及其所有依赖项都是重新构建的(不使用缓存)
|
||||
- `NO_COMPRESS`: 不要使用UPX压缩生成的二进制文件
|
||||
- `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
|
||||
- `MIMALLOC`: (实验性,仅限Linux) 用[mimalloc](https://github.com/microsoft/mimalloc)替换musl的mallocng,以提高性能。我们仅建议在musl目标构建中使用此选项,对于glibc,建议禁用此选项,并在运行二进制文件时使用[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)。
|
||||
- `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
|
||||
|
||||
## 扩展
|
||||
|
||||
|
@@ -80,7 +80,7 @@ Some FrankenPHP features depend on optional system dependencies that must be ins
|
||||
Alternatively, these features can be disabled by passing build tags to the Go compiler.
|
||||
|
||||
| Feature | Dependency | Build tag to disable it |
|
||||
|--------------------------------|-----------------------------------------------------------------------|-------------------------|
|
||||
| ------------------------------ | --------------------------------------------------------------------- | ----------------------- |
|
||||
| Brotli compression | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Restart workers on file change | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
|
@@ -72,17 +72,18 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
There are two important things to note here:
|
||||
|
||||
* A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type;
|
||||
* The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go.
|
||||
- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type;
|
||||
- The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go.
|
||||
|
||||
While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive to type juggling in the next section.
|
||||
|
||||
### Type Juggling
|
||||
|
||||
While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know:
|
||||
While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP.
|
||||
This table summarizes what you need to know:
|
||||
|
||||
| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
|
||||
|--------------------|-------------------------------|-------------------|-----------------------------------|------------------------------------|-----------------------|
|
||||
| ------------------ | ----------------------------- | ----------------- | --------------------------------- | ---------------------------------- | --------------------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
@@ -93,6 +94,7 @@ While some variable types have the same memory representation between C/PHP and
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
@@ -170,20 +172,20 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
**Key features of array conversion:**
|
||||
|
||||
* **Ordered key-value pairs** - Option to keep the order of the associative array
|
||||
* **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice
|
||||
* **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap
|
||||
* **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`)
|
||||
* **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array.
|
||||
- **Ordered key-value pairs** - Option to keep the order of the associative array
|
||||
- **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice
|
||||
- **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap
|
||||
- **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`)
|
||||
- **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array.
|
||||
|
||||
##### Available methods: Packed and Associative
|
||||
|
||||
* `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP array with key-value pairs
|
||||
* `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP array with key-value pairs
|
||||
* `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed array with indexed values only
|
||||
* `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convert a PHP array to an ordered Go `AssociativeArray` (map with order)
|
||||
* `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convert a PHP array to an unordered Go map
|
||||
* `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convert a PHP array to a Go slice
|
||||
- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP array with key-value pairs
|
||||
- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP array with key-value pairs
|
||||
- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed array with indexed values only
|
||||
- `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convert a PHP array to an ordered Go `AssociativeArray` (map with order)
|
||||
- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convert a PHP array to an unordered Go map
|
||||
- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convert a PHP array to a Go slice
|
||||
|
||||
### Declaring a Native PHP Class
|
||||
|
||||
@@ -201,11 +203,11 @@ type UserStruct struct {
|
||||
|
||||
**Opaque classes** are classes where the internal structure (properties) is hidden from PHP code. This means:
|
||||
|
||||
* **No direct property access**: You cannot read or write properties directly from PHP (`$user->name` won't work)
|
||||
* **Method-only interface** - All interactions must go through methods you define
|
||||
* **Better encapsulation** - Internal data structure is completely controlled by Go code
|
||||
* **Type safety** - No risk of PHP code corrupting internal state with wrong types
|
||||
* **Cleaner API** - Forces to design a proper public interface
|
||||
- **No direct property access**: You cannot read or write properties directly from PHP (`$user->name` won't work)
|
||||
- **Method-only interface** - All interactions must go through methods you define
|
||||
- **Better encapsulation** - Internal data structure is completely controlled by Go code
|
||||
- **Type safety** - No risk of PHP code corrupting internal state with wrong types
|
||||
- **Cleaner API** - Forces to design a proper public interface
|
||||
|
||||
This approach provides better encapsulation and prevents PHP code from accidentally corrupting the internal state of your Go objects. All interactions with the object must go through the methods you explicitly define.
|
||||
|
||||
@@ -252,12 +254,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// Check if age was provided (not null)
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// Check if active was provided (not null)
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -267,10 +269,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**Key points about nullable parameters:**
|
||||
|
||||
* **Nullable primitive types** (`?int`, `?float`, `?bool`) become pointers (`*int64`, `*float64`, `*bool`) in Go
|
||||
* **Nullable strings** (`?string`) remain as `*C.zend_string` but can be `nil`
|
||||
* **Check for `nil`** before dereferencing pointer values
|
||||
* **PHP `null` becomes Go `nil`** - when PHP passes `null`, your Go function receives a `nil` pointer
|
||||
- **Nullable primitive types** (`?int`, `?float`, `?bool`) become pointers (`*int64`, `*float64`, `*bool`) in Go
|
||||
- **Nullable strings** (`?string`) remain as `*C.zend_string` but can be `nil`
|
||||
- **Check for `nil`** before dereferencing pointer values
|
||||
- **PHP `null` becomes Go `nil`** - when PHP passes `null`, your Go function receives a `nil` pointer
|
||||
|
||||
> [!WARNING]
|
||||
> Currently, class methods have the following limitations. **Objects are not supported** as parameter types or return types. **Arrays are fully supported** for both parameters and return types. Supported types: `string`, `int`, `float`, `bool`, `array`, and `void` (for return type). **Nullable parameter types are fully supported** for all scalar types (`?string`, `?int`, `?float`, `?bool`).
|
||||
@@ -389,7 +391,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// reverse the string
|
||||
}
|
||||
|
||||
@@ -408,14 +410,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -470,17 +472,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
|
||||
|
||||
#### Important Notes
|
||||
|
||||
* Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error.
|
||||
* The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants.
|
||||
* Namespace names follow PHP namespace conventions using backslashes (`\`) as separators.
|
||||
* If no namespace is declared, symbols are exported to the global namespace as usual.
|
||||
- Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error.
|
||||
- The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants.
|
||||
- Namespace names follow PHP namespace conventions using backslashes (`\`) as separators.
|
||||
- If no namespace is declared, symbols are exported to the global namespace as usual.
|
||||
|
||||
### Generating the Extension
|
||||
|
||||
This is where the magic happens, and your extension can now be generated. You can run the generator with the following command:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -600,9 +602,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
Next, create a file named `extension.c` that will perform the following steps:
|
||||
|
||||
* Include PHP headers;
|
||||
* Declare our new native PHP function `go_print()`;
|
||||
* Declare the extension metadata.
|
||||
- Include PHP headers;
|
||||
- Declare our new native PHP function `go_print()`;
|
||||
- Declare the extension metadata.
|
||||
|
||||
Let's start by including the required headers:
|
||||
|
||||
@@ -734,9 +736,9 @@ import "strings"
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
@@ -82,7 +82,7 @@ Certaines fonctionnalités de FrankenPHP nécessitent des dépendances optionnel
|
||||
Ces fonctionnalités peuvent également être désactivées en passant des tags de compilation au compilateur Go.
|
||||
|
||||
| Fonctionnalité | Dépendance | Tag de compilation pour la désactiver |
|
||||
|---------------------------------------------------------|-----------------------------------------------------------------------|---------------------------------------|
|
||||
| ------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------- |
|
||||
| Compression Brotli | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Redémarrage des workers en cas de changement de fichier | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
|
@@ -72,8 +72,8 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
Il y a deux choses importantes à noter ici :
|
||||
|
||||
* Une directive `//export_php:function` définit la signature de la fonction en PHP. C'est ainsi que le générateur sait comment générer la fonction PHP avec les bons paramètres et le bon type de retour ;
|
||||
* La fonction doit retourner un `unsafe.Pointer`. FrankenPHP fournit une API pour vous aider avec le jonglage de types entre C et Go.
|
||||
- Une directive `//export_php:function` définit la signature de la fonction en PHP. C'est ainsi que le générateur sait comment générer la fonction PHP avec les bons paramètres et le bon type de retour ;
|
||||
- La fonction doit retourner un `unsafe.Pointer`. FrankenPHP fournit une API pour vous aider avec le jonglage de types entre C et Go.
|
||||
|
||||
Alors que le premier point parle de lui-même, le second peut être plus difficile à appréhender. Plongeons plus profondément dans la jonglage de types dans la section suivante.
|
||||
|
||||
@@ -82,16 +82,17 @@ Alors que le premier point parle de lui-même, le second peut être plus diffici
|
||||
Bien que certains types de variables aient la même représentation mémoire entre C/PHP et Go, certains types nécessitent plus de logique pour être directement utilisés. C'est peut-être la partie la plus difficile quand il s'agit d'écrire des extensions car cela nécessite de comprendre les fonctionnements internes du moteur Zend et comment les variables sont stockées dans le moteur de PHP. Ce tableau résume ce que vous devez savoir :
|
||||
|
||||
| Type PHP | Type Go | Conversion directe | Assistant C vers Go | Assistant Go vers C | Support des Méthodes de Classe |
|
||||
|--------------------|---------------------|--------------------|-------------------------|-------------------------|--------------------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `object` | `struct` | ❌ | _Pas encore implémenté_ | _Pas encore implémenté_ | ❌ |
|
||||
| ------------------ | ------------------- | ------------------ | ----------------------- | ----------------------- | ------------------------------ |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _Pas encore implémenté_ | _Pas encore implémenté_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> Ce tableau n'est pas encore exhaustif et sera complété au fur et à mesure que l'API de types FrankenPHP deviendra plus complète.
|
||||
@@ -111,16 +112,16 @@ FrankenPHP fournit un support natif pour les tableaux PHP à travers le type `fr
|
||||
func process_data(arr *C.zval) unsafe.Pointer {
|
||||
// Convertir le tableau PHP vers Go
|
||||
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
|
||||
|
||||
|
||||
result := &frankenphp.Array{}
|
||||
|
||||
|
||||
result.SetInt(0, "first")
|
||||
result.SetInt(1, "second")
|
||||
result.Append("third") // Assigne automatiquement la prochaine clé entière
|
||||
|
||||
|
||||
result.SetString("name", "John")
|
||||
result.SetString("age", int64(30))
|
||||
|
||||
|
||||
for i := uint32(0); i < goArray.Len(); i++ {
|
||||
key, value := goArray.At(i)
|
||||
if key.Type == frankenphp.PHPStringKey {
|
||||
@@ -129,7 +130,7 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
result.SetInt(key.Int+100, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reconvertir vers un tableau PHP
|
||||
return frankenphp.PHPArray(result)
|
||||
}
|
||||
@@ -137,20 +138,20 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
**Fonctionnalités clés de `frankenphp.Array` :**
|
||||
|
||||
* **Paires clé-valeur ordonnées** - Maintient l'ordre d'insertion comme les tableaux PHP
|
||||
* **Types de clés mixtes** - Supporte les clés entières et chaînes dans le même tableau
|
||||
* **Sécurité de type** - Le type `PHPKey` assure une gestion appropriée des clés
|
||||
* **Détection automatique de liste** - Lors de la conversion vers PHP, détecte automatiquement si le tableau doit être une liste compacte ou un hashmap
|
||||
* **Les objets ne sont pas supportés** - Actuellement, seuls les types scalaires et les tableaux sont supportés. Passer un objet en tant qu'élément du tableau résultera d'une valeur `null` dans le tableau PHP.
|
||||
- **Paires clé-valeur ordonnées** - Maintient l'ordre d'insertion comme les tableaux PHP
|
||||
- **Types de clés mixtes** - Supporte les clés entières et chaînes dans le même tableau
|
||||
- **Sécurité de type** - Le type `PHPKey` assure une gestion appropriée des clés
|
||||
- **Détection automatique de liste** - Lors de la conversion vers PHP, détecte automatiquement si le tableau doit être une liste compacte ou un hashmap
|
||||
- **Les objets ne sont pas supportés** - Actuellement, seuls les types scalaires et les tableaux sont supportés. Passer un objet en tant qu'élément du tableau résultera d'une valeur `null` dans le tableau PHP.
|
||||
|
||||
**Méthodes disponibles :**
|
||||
|
||||
* `SetInt(key int64, value interface{})` - Définir une valeur avec une clé entière
|
||||
* `SetString(key string, value interface{})` - Définir une valeur avec une clé chaîne
|
||||
* `Append(value interface{})` - Ajouter une valeur avec la prochaine clé entière disponible
|
||||
* `Len() uint32` - Obtenir le nombre d'éléments
|
||||
* `At(index uint32) (PHPKey, interface{})` - Obtenir la paire clé-valeur à l'index
|
||||
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convertir vers un tableau PHP
|
||||
- `SetInt(key int64, value interface{})` - Définir une valeur avec une clé entière
|
||||
- `SetString(key string, value interface{})` - Définir une valeur avec une clé chaîne
|
||||
- `Append(value interface{})` - Ajouter une valeur avec la prochaine clé entière disponible
|
||||
- `Len() uint32` - Obtenir le nombre d'éléments
|
||||
- `At(index uint32) (PHPKey, interface{})` - Obtenir la paire clé-valeur à l'index
|
||||
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convertir vers un tableau PHP
|
||||
|
||||
### Déclarer une Classe PHP Native
|
||||
|
||||
@@ -168,11 +169,11 @@ type UserStruct struct {
|
||||
|
||||
Les **classes opaques** sont des classes avec lesquelles la structure interne (comprendre : les propriétés) est cachée du code PHP. Cela signifie :
|
||||
|
||||
* **Pas d'accès direct aux propriétés** : Vous ne pouvez pas lire ou écrire des propriétés directement depuis PHP (`$user->name` ne fonctionnera pas)
|
||||
* **Interface uniquement par méthodes** - Toutes les interactions doivent passer par les méthodes que vous définissez
|
||||
* **Meilleure encapsulation** - La structure de données interne est complètement contrôlée par le code Go
|
||||
* **Sécurité de type** - Aucun risque que le code PHP corrompe l'état interne avec de mauvais types
|
||||
* **API plus propre** - Force à concevoir une interface publique appropriée
|
||||
- **Pas d'accès direct aux propriétés** : Vous ne pouvez pas lire ou écrire des propriétés directement depuis PHP (`$user->name` ne fonctionnera pas)
|
||||
- **Interface uniquement par méthodes** - Toutes les interactions doivent passer par les méthodes que vous définissez
|
||||
- **Meilleure encapsulation** - La structure de données interne est complètement contrôlée par le code Go
|
||||
- **Sécurité de type** - Aucun risque que le code PHP corrompe l'état interne avec de mauvais types
|
||||
- **API plus propre** - Force à concevoir une interface publique appropriée
|
||||
|
||||
Cette approche fournit une meilleure encapsulation et empêche le code PHP de corrompre accidentellement l'état interne de vos objets Go. Toutes les interactions avec l'objet doivent passer par les méthodes que vous définissez explicitement.
|
||||
|
||||
@@ -219,12 +220,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// $age est null?
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// $active est null?
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -234,10 +235,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**Points clés sur les paramètres nullables :**
|
||||
|
||||
* **Types primitifs nullables** (`?int`, `?float`, `?bool`) deviennent des pointeurs (`*int64`, `*float64`, `*bool`) en Go
|
||||
* **Chaînes nullables** (`?string`) restent comme `*C.zend_string` mais peuvent être `nil`
|
||||
* **Vérifiez `nil`** avant de déréférencer les valeurs de pointeur
|
||||
* **PHP `null` devient Go `nil`** - quand PHP passe `null`, votre fonction Go reçoit un pointeur `nil`
|
||||
- **Types primitifs nullables** (`?int`, `?float`, `?bool`) deviennent des pointeurs (`*int64`, `*float64`, `*bool`) en Go
|
||||
- **Chaînes nullables** (`?string`) restent comme `*C.zend_string` mais peuvent être `nil`
|
||||
- **Vérifiez `nil`** avant de déréférencer les valeurs de pointeur
|
||||
- **PHP `null` devient Go `nil`** - quand PHP passe `null`, votre fonction Go reçoit un pointeur `nil`
|
||||
|
||||
> [!WARNING]
|
||||
> Actuellement, les méthodes de classe ont les limitations suivantes. **Les objets ne sont pas supportés** comme types de paramètres ou types de retour. **Les tableaux sont entièrement supportés** pour les paramètres et types de retour. Types supportés : `string`, `int`, `float`, `bool`, `array`, et `void` (pour le type de retour). **Les types de paramètres nullables sont entièrement supportés** pour tous les types scalaires (`?string`, `?int`, `?float`, `?bool`).
|
||||
@@ -356,7 +357,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// inverser la chaîne
|
||||
}
|
||||
|
||||
@@ -375,14 +376,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -437,17 +438,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
|
||||
|
||||
#### Notes Importantes
|
||||
|
||||
* Seule **une** directive d'espace de noms est autorisée par fichier. Si plusieurs directives d'espace de noms sont trouvées, le générateur retournera une erreur.
|
||||
* L'espace de noms s'applique à **tous** les symboles exportés dans le fichier : fonctions, classes, méthodes et constantes.
|
||||
* Les noms d'espaces de noms suivent les conventions des espaces de noms PHP en utilisant les barres obliques inverses (`\`) comme séparateurs.
|
||||
* Si aucun espace de noms n'est déclaré, les symboles sont exportés vers l'espace de noms global comme d'habitude.
|
||||
- Seule **une** directive d'espace de noms est autorisée par fichier. Si plusieurs directives d'espace de noms sont trouvées, le générateur retournera une erreur.
|
||||
- L'espace de noms s'applique à **tous** les symboles exportés dans le fichier : fonctions, classes, méthodes et constantes.
|
||||
- Les noms d'espaces de noms suivent les conventions des espaces de noms PHP en utilisant les barres obliques inverses (`\`) comme séparateurs.
|
||||
- Si aucun espace de noms n'est déclaré, les symboles sont exportés vers l'espace de noms global comme d'habitude.
|
||||
|
||||
### Générer l'Extension
|
||||
|
||||
C'est là que la magie opère, et votre extension peut maintenant être générée. Vous pouvez exécuter le générateur avec la commande suivante :
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -567,9 +568,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
Ensuite, créez un fichier nommé `extension.c` qui effectuera les étapes suivantes :
|
||||
|
||||
* Inclure les en-têtes PHP ;
|
||||
* Déclarer notre nouvelle fonction PHP native `go_print()` ;
|
||||
* Déclarer les métadonnées de l'extension.
|
||||
- Inclure les en-têtes PHP ;
|
||||
- Déclarer notre nouvelle fonction PHP native `go_print()` ;
|
||||
- Déclarer les métadonnées de l'extension.
|
||||
|
||||
Commençons par inclure les en-têtes requis :
|
||||
|
||||
@@ -701,9 +702,9 @@ import "strings"
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
@@ -79,10 +79,10 @@ sudo make install
|
||||
FrankenPHPの一部の機能は、システムにインストールされているオプションの依存パッケージに依存しています。
|
||||
または、Goコンパイラにビルドタグを渡すことで、これらの機能を無効にできます。
|
||||
|
||||
| 機能 | 依存関係 | 無効にするためのビルドタグ |
|
||||
|--------------------------------|-----------------------------------------------------------------------|-------------------------|
|
||||
| Brotli圧縮 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| ファイル変更時のワーカー再起動 | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
| 機能 | 依存関係 | 無効にするためのビルドタグ |
|
||||
| ------------------------------ | --------------------------------------------------------------------- | -------------------------- |
|
||||
| Brotli圧縮 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| ファイル変更時のワーカー再起動 | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Goアプリのコンパイル
|
||||
|
||||
|
@@ -72,8 +72,8 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
ここで重要なポイントが2つあります:
|
||||
|
||||
* ディレクティブコメント`//export_php:function`はPHPでの関数シグネチャを定義します。これにより、ジェネレーターは適切なパラメータと戻り値の型でPHP関数を生成する方法を知ることができます。
|
||||
* 関数は`unsafe.Pointer`を返さなければなりません。FrankenPHPはCとGo間の型変換を支援するAPIを提供しています。
|
||||
- ディレクティブコメント`//export_php:function`はPHPでの関数シグネチャを定義します。これにより、ジェネレーターは適切なパラメータと戻り値の型でPHP関数を生成する方法を知ることができます。
|
||||
- 関数は`unsafe.Pointer`を返さなければなりません。FrankenPHPはCとGo間の型変換を支援するAPIを提供しています。
|
||||
|
||||
前者は理解しやすいですが、後者は少し複雑かもしれません。次のセクションで型変換について詳しく説明します。
|
||||
|
||||
@@ -81,17 +81,18 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
C/PHPとGoの間でメモリ表現が同じ変数型もありますが、直接使用するにはより多くのロジックが必要な型もあります。これは拡張モジュールを書く際の最も挑戦的な部分かもしれません。Zendエンジンの内部仕組みや、変数がPHP内でどのように格納されているかを理解する必要があるためです。以下の表は、知っておくべき重要な情報をまとめています:
|
||||
|
||||
| PHP型 | Go型 | 直接変換 | CからGoヘルパー | GoからCヘルパー | クラスメソッドサポート |
|
||||
|--------------------|------------------|-------------------|-----------------------|------------------------|-----------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `slice`/`map` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
| `object` | `struct` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
| PHP型 | Go型 | 直接変換 | CからGoヘルパー | GoからCヘルパー | クラスメソッドサポート |
|
||||
| ------------------ | ---------------- | -------- | --------------------- | ---------------------- | ---------------------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `slice`/`map` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> この表はまだ完全ではなく、FrankenPHPの型APIがより完全になるにつれて完成されます。
|
||||
@@ -116,11 +117,11 @@ type UserStruct struct {
|
||||
|
||||
**不透明クラス(opaque classes)**は、内部構造(プロパティ)がPHPコードから隠されているクラスです。これは以下を意味します:
|
||||
|
||||
* **プロパティへの直接アクセス不可** :PHPから直接プロパティを読み書きできません(`$user->name`は機能しません)
|
||||
* **メソッド経由のみで操作** - すべてのやりとりはGoで定義したメソッドを通じて行う必要があります
|
||||
* **より良いカプセル化** - 内部データ構造は完全にGoコードによって制御されます
|
||||
* **型安全性** - PHP側から誤った型で内部状態が破壊されるリスクがありません
|
||||
* **よりクリーンなAPI** - 適切な公開インターフェースを設計することを強制します
|
||||
- **プロパティへの直接アクセス不可** :PHPから直接プロパティを読み書きできません(`$user->name`は機能しません)
|
||||
- **メソッド経由のみで操作** - すべてのやりとりはGoで定義したメソッドを通じて行う必要があります
|
||||
- **より良いカプセル化** - 内部データ構造は完全にGoコードによって制御されます
|
||||
- **型安全性** - PHP側から誤った型で内部状態が破壊されるリスクがありません
|
||||
- **よりクリーンなAPI** - 適切な公開インターフェースを設計することを強制します
|
||||
|
||||
このアプローチは優れたカプセル化を実現し、PHPコードがGoオブジェクトの内部状態を意図せずに破壊してしまうことを防ぎます。オブジェクトとのすべてのやりとりは、明示的に定義したメソッドを通じて行う必要があります。
|
||||
|
||||
@@ -167,12 +168,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// ageが渡された(nullではない)かチェック
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// activeが渡された(nullではない)かチェック
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -182,10 +183,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**Nullableパラメータの重要なポイント:**
|
||||
|
||||
* **プリミティブ型のnullable** (`?int`, `?float`, `?bool`) はGoではそれぞれポインタ (`*int64`, `*float64`, `*bool`) になります
|
||||
* **nullable文字列** (`?string`) は `*C.zend_string` のままですが、`nil` になることがあります
|
||||
* ポインタ値を逆参照する前に **`nil`をチェック** してください
|
||||
* **PHPの`null`はGoの`nil`になります** - PHPが`null`を渡すと、Go関数は`nil`ポインタを受け取ります
|
||||
- **プリミティブ型のnullable** (`?int`, `?float`, `?bool`) はGoではそれぞれポインタ (`*int64`, `*float64`, `*bool`) になります
|
||||
- **nullable文字列** (`?string`) は `*C.zend_string` のままですが、`nil` になることがあります
|
||||
- ポインタ値を逆参照する前に **`nil`をチェック** してください
|
||||
- **PHPの`null`はGoの`nil`になります** - PHPが`null`を渡すと、Go関数は`nil`ポインタを受け取ります
|
||||
|
||||
> [!WARNING]
|
||||
> 現在、クラスメソッドには次の制限があります。**配列とオブジェクトはパラメータ型や戻り値の型としてサポートされていません**。サポートされるのは`string`、`int`、`float`、`bool`、`void`(戻り値の型)といったスカラー型のみです。**nullableなスカラー型はすべてサポートされています** (`?string`、`?int`、`?float`、`?bool`)。
|
||||
@@ -304,7 +305,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// 文字列を逆転
|
||||
}
|
||||
|
||||
@@ -323,14 +324,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -340,11 +341,10 @@ func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsaf
|
||||
ここでいよいよ、拡張モジュールを生成できるようになります。以下のコマンドでジェネレーターを実行できます:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `GEN_STUB_SCRIPT`環境変数に、先ほどダウンロードしたPHPソースの`gen_stub.php`ファイルのパスを設定するのを忘れないでください。これは手動実装セクションで言及されているのと同じ`gen_stub.php`スクリプトです。
|
||||
> [!NOTE] > `GEN_STUB_SCRIPT`環境変数に、先ほどダウンロードしたPHPソースの`gen_stub.php`ファイルのパスを設定するのを忘れないでください。これは手動実装セクションで言及されているのと同じ`gen_stub.php`スクリプトです。
|
||||
|
||||
すべてがうまくいけば、`build`という名前の新しいディレクトリが作成されているはずです。このディレクトリには、生成されたPHP関数スタブを含む`my_extension.go`ファイルなど、拡張用の生成されたファイルが含まれています。
|
||||
|
||||
@@ -460,9 +460,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
次に、以下のステップを実行する`extension.c`という名前のファイルを作成します:
|
||||
|
||||
* PHPヘッダーをインクルードする
|
||||
* 新しいネイティブPHP関数`go_print()`を宣言する
|
||||
* 拡張モジュールのメタデータを宣言する
|
||||
- PHPヘッダーをインクルードする
|
||||
- 新しいネイティブPHP関数`go_print()`を宣言する
|
||||
- 拡張モジュールのメタデータを宣言する
|
||||
|
||||
まずは必要なヘッダーのインクルードから始めましょう:
|
||||
|
||||
@@ -594,9 +594,9 @@ import "strings"
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
@@ -4,8 +4,8 @@
|
||||
|
||||
以下の拡張モジュールはFrankenPHPと互換性がないことが確認されています:
|
||||
|
||||
| 名前 | 理由 | 代替手段 |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| 名前 | 理由 | 代替手段 |
|
||||
| ----------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | スレッドセーフでない | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | スレッドセーフでない | - |
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
以下の拡張モジュールはFrankenPHPとの組み合わせで既知のバグや予期しない動作が確認されています:
|
||||
|
||||
| 名前 | 問題 |
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 名前 | 問題 |
|
||||
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | FrankenPHPの静的ビルド(musl libcでビルド)を使用した場合、高負荷時にOpenSSL拡張がクラッシュすることがあります。回避策として動的リンクのビルド(Dockerイメージで使用されているもの)を使用してください。このバグは[PHP側で追跡中](https://github.com/php/php-src/issues/13648)です。 |
|
||||
|
||||
## get_browser
|
||||
|
@@ -92,7 +92,7 @@ Alternativamente, esses recursos podem ser desabilitados passando as tags de
|
||||
compilação para o compilador Go.
|
||||
|
||||
| Recurso | Dependência | Tag de compilação para desabilitá-lo |
|
||||
|---------------------------------------|-----------------------------------------------------------------------|--------------------------------------|
|
||||
| ------------------------------------- | --------------------------------------------------------------------- | ------------------------------------ |
|
||||
| Compressão Brotli | [Brotli](https://github.com/google/brotli) | `nobrotli` |
|
||||
| Reiniciar workers ao alterar arquivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | `nowatcher` |
|
||||
|
||||
@@ -126,8 +126,8 @@ xcaddy build \
|
||||
> Se você estiver usando a `libc` `musl` (o padrão no Alpine Linux) e Symfony,
|
||||
> pode ser necessário aumentar o tamanho da pilha padrão.
|
||||
> Caso contrário, você poderá receber erros como `PHP Fatal error: Maximum call
|
||||
> stack size of 83360 bytes reached during compilation.
|
||||
> Try splitting expression`.
|
||||
stack size of 83360 bytes reached during compilation.
|
||||
Try splitting expression`.
|
||||
>
|
||||
> Para fazer isso, altere a variável de ambiente `XCADDY_GO_BUILD_FLAGS` para
|
||||
> algo como
|
||||
|
@@ -132,19 +132,20 @@ variáveis são armazenadas internamente no PHP.
|
||||
Esta tabela resume o que você precisa saber:
|
||||
|
||||
| Tipo PHP | Tipo Go | Conversão direta | Auxiliar de C para Go | Auxiliar de Go para C | Suporte a métodos de classe |
|
||||
|--------------------|-------------------------------|------------------|-----------------------------------|------------------------------------|-----------------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `object` | `struct` | ❌ | _Ainda não implementado_ | _Ainda não implementado_ | ❌ |
|
||||
| ------------------ | ----------------------------- | ---------------- | --------------------------------- | ---------------------------------- | --------------------------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _Ainda não implementado_ | _Ainda não implementado_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> Esta tabela ainda não é exaustiva e será completada à medida que a API de
|
||||
|
@@ -6,7 +6,7 @@ As seguintes extensões são conhecidas por não serem compatíveis com o
|
||||
FrankenPHP:
|
||||
|
||||
| Nome | Motivo | Alternativas |
|
||||
|-------------------------------------------------------------------------------------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| ----------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/pt_BR/imap.installation.php) | Não é thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | Não é thread-safe | - |
|
||||
|
||||
@@ -16,7 +16,7 @@ As seguintes extensões apresentam falhas conhecidas e comportamentos inesperado
|
||||
quando usadas com o FrankenPHP:
|
||||
|
||||
| Nome | Problema |
|
||||
|------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/pt_BR/book.openssl.php) | Ao usar uma versão estática do FrankenPHP (compilada com a `libc` `musl`), a extensão OpenSSL pode quebrar sob cargas pesadas. Uma solução alternativa é usar uma versão vinculada dinamicamente (como a usada em imagens Docker). Esta falha está [sendo monitorada pelo PHP](https://github.com/php/php-src/issues/13648) |
|
||||
|
||||
## `get_browser`
|
||||
|
52
testdata/load-test.js
vendored
52
testdata/load-test.js
vendored
@@ -1,11 +1,11 @@
|
||||
import http from 'k6/http'
|
||||
import { check } from 'k6'
|
||||
import http from "k6/http";
|
||||
import { check } from "k6";
|
||||
|
||||
export const options = {
|
||||
// A number specifying the number of VUs to run concurrently.
|
||||
vus: 100,
|
||||
// A string specifying the total duration of the test run.
|
||||
duration: '30s'
|
||||
duration: "30s",
|
||||
|
||||
// The following section contains configuration options for execution of this
|
||||
// test script in Grafana Cloud.
|
||||
@@ -48,9 +48,9 @@ export const options = {
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
const payload = 'foo\n'.repeat(1000)
|
||||
const payload = "foo\n".repeat(1000);
|
||||
|
||||
// The function that defines VU logic.
|
||||
//
|
||||
@@ -61,29 +61,29 @@ export default function () {
|
||||
const params = {
|
||||
headers: {
|
||||
Accept:
|
||||
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
// 'Accept-Encoding': 'br',
|
||||
'Accept-Language': 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
"Accept-Language": "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
Cookie:
|
||||
'user_session=myrandomuuid; __Host-user_session_same_site=myotherrandomuuid; dotcom_user=dunglas; logged_in=yes; _foo=barbarbarbarbarbar; _device_id=anotherrandomuuid; color_mode=foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar; preferred_color_mode=light; tz=Europe%2FParis; has_recent_activity=1',
|
||||
DNT: '1',
|
||||
Host: 'example.com',
|
||||
Pragma: 'no-cache',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
'Sec-Fetch-Mode': 'navigate',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
'Sec-GPC': '1',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0'
|
||||
}
|
||||
}
|
||||
"user_session=myrandomuuid; __Host-user_session_same_site=myotherrandomuuid; dotcom_user=dunglas; logged_in=yes; _foo=barbarbarbarbarbar; _device_id=anotherrandomuuid; color_mode=foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar; preferred_color_mode=light; tz=Europe%2FParis; has_recent_activity=1",
|
||||
DNT: "1",
|
||||
Host: "example.com",
|
||||
Pragma: "no-cache",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": "cross-site",
|
||||
"Sec-GPC": "1",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
},
|
||||
};
|
||||
|
||||
const res = http.post('http://localhost/echo.php', payload, params)
|
||||
const res = http.post("http://localhost/echo.php", payload, params);
|
||||
check(res, {
|
||||
'is status 200': (r) => r.status === 200,
|
||||
'is echoed': (r) => r.body === payload
|
||||
})
|
||||
"is status 200": (r) => r.status === 200,
|
||||
"is echoed": (r) => r.body === payload,
|
||||
});
|
||||
}
|
||||
|
24
testdata/performance/api.js
vendored
24
testdata/performance/api.js
vendored
@@ -1,4 +1,4 @@
|
||||
import http from 'k6/http'
|
||||
import http from "k6/http";
|
||||
|
||||
/**
|
||||
* Many applications communicate with external APIs or microservices.
|
||||
@@ -7,23 +7,25 @@ import http from 'k6/http'
|
||||
*/
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '20s', target: 150 },
|
||||
{ duration: '20s', target: 1000 },
|
||||
{ duration: '10s', target: 0 }
|
||||
{ duration: "20s", target: 150 },
|
||||
{ duration: "20s", target: 1000 },
|
||||
{ duration: "10s", target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01']
|
||||
}
|
||||
}
|
||||
http_req_failed: ["rate<0.01"],
|
||||
},
|
||||
};
|
||||
|
||||
/* global __ENV */
|
||||
export default function () {
|
||||
// 10-150ms latency
|
||||
const latency = Math.floor(Math.random() * 141) + 10
|
||||
const latency = Math.floor(Math.random() * 141) + 10;
|
||||
// 1-30000 work units
|
||||
const work = Math.ceil(Math.random() * 30000)
|
||||
const work = Math.ceil(Math.random() * 30000);
|
||||
// 1-40 output units
|
||||
const output = Math.ceil(Math.random() * 40)
|
||||
const output = Math.ceil(Math.random() * 40);
|
||||
|
||||
http.get(http.url`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=${latency}&work=${work}&output=${output}`)
|
||||
http.get(
|
||||
http.url`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=${latency}&work=${work}&output=${output}`,
|
||||
);
|
||||
}
|
||||
|
24
testdata/performance/computation.js
vendored
24
testdata/performance/computation.js
vendored
@@ -1,27 +1,29 @@
|
||||
import http from 'k6/http'
|
||||
import http from "k6/http";
|
||||
|
||||
/**
|
||||
* Simulate an application that does very little IO, but a lot of computation
|
||||
*/
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '20s', target: 80 },
|
||||
{ duration: '20s', target: 150 },
|
||||
{ duration: '5s', target: 0 }
|
||||
{ duration: "20s", target: 80 },
|
||||
{ duration: "20s", target: 150 },
|
||||
{ duration: "5s", target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01']
|
||||
}
|
||||
}
|
||||
http_req_failed: ["rate<0.01"],
|
||||
},
|
||||
};
|
||||
|
||||
/* global __ENV */
|
||||
export default function () {
|
||||
// do 1-1,000,000 work units
|
||||
const work = Math.ceil(Math.random() * 1_000_000)
|
||||
const work = Math.ceil(Math.random() * 1_000_000);
|
||||
// output 1-500 units
|
||||
const output = Math.ceil(Math.random() * 500)
|
||||
const output = Math.ceil(Math.random() * 500);
|
||||
// simulate 0-2ms latency
|
||||
const latency = Math.floor(Math.random() * 3)
|
||||
const latency = Math.floor(Math.random() * 3);
|
||||
|
||||
http.get(http.url`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=${latency}&work=${work}&output=${output}`)
|
||||
http.get(
|
||||
http.url`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=${latency}&work=${work}&output=${output}`,
|
||||
);
|
||||
}
|
||||
|
26
testdata/performance/database.js
vendored
26
testdata/performance/database.js
vendored
@@ -1,4 +1,4 @@
|
||||
import http from 'k6/http'
|
||||
import http from "k6/http";
|
||||
|
||||
/**
|
||||
* Modern databases tend to have latencies in the single-digit milliseconds.
|
||||
@@ -6,25 +6,27 @@ import http from 'k6/http'
|
||||
*/
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '20s', target: 100 },
|
||||
{ duration: '30s', target: 200 },
|
||||
{ duration: '10s', target: 0 }
|
||||
{ duration: "20s", target: 100 },
|
||||
{ duration: "30s", target: 200 },
|
||||
{ duration: "10s", target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01']
|
||||
}
|
||||
}
|
||||
http_req_failed: ["rate<0.01"],
|
||||
},
|
||||
};
|
||||
|
||||
/* global __ENV */
|
||||
export default function () {
|
||||
// 1-10ms latency
|
||||
const latency = Math.floor(Math.random() * 10) + 1
|
||||
const latency = Math.floor(Math.random() * 10) + 1;
|
||||
// 1-2 iterations per request
|
||||
const iterations = Math.floor(Math.random() * 2) + 1
|
||||
const iterations = Math.floor(Math.random() * 2) + 1;
|
||||
// 1-30000 work units per iteration
|
||||
const work = Math.ceil(Math.random() * 30000)
|
||||
const work = Math.ceil(Math.random() * 30000);
|
||||
// 1-40 output units
|
||||
const output = Math.ceil(Math.random() * 40)
|
||||
const output = Math.ceil(Math.random() * 40);
|
||||
|
||||
http.get(http.url`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=${latency}&work=${work}&output=${output}&iterations=${iterations}`)
|
||||
http.get(
|
||||
http.url`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=${latency}&work=${work}&output=${output}&iterations=${iterations}`,
|
||||
);
|
||||
}
|
||||
|
22
testdata/performance/hanging-requests.js
vendored
22
testdata/performance/hanging-requests.js
vendored
@@ -1,4 +1,4 @@
|
||||
import http from 'k6/http'
|
||||
import http from "k6/http";
|
||||
|
||||
/**
|
||||
* It is not uncommon for external services to hang for a long time.
|
||||
@@ -6,23 +6,25 @@ import http from 'k6/http'
|
||||
*/
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '20s', target: 100 },
|
||||
{ duration: '20s', target: 500 },
|
||||
{ duration: '20s', target: 0 }
|
||||
{ duration: "20s", target: 100 },
|
||||
{ duration: "20s", target: 500 },
|
||||
{ duration: "20s", target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01']
|
||||
}
|
||||
}
|
||||
http_req_failed: ["rate<0.01"],
|
||||
},
|
||||
};
|
||||
|
||||
/* global __ENV */
|
||||
export default function () {
|
||||
// 2% chance for a request that hangs for 15s
|
||||
if (Math.random() < 0.02) {
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=15000&work=10000&output=100`)
|
||||
return
|
||||
http.get(
|
||||
`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=15000&work=10000&output=100`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// a regular request
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=5&work=10000&output=100`)
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=5&work=10000&output=100`);
|
||||
}
|
||||
|
16
testdata/performance/hello-world.js
vendored
16
testdata/performance/hello-world.js
vendored
@@ -1,20 +1,20 @@
|
||||
import http from 'k6/http'
|
||||
import http from "k6/http";
|
||||
|
||||
/**
|
||||
* 'Hello world' tests the raw server performance.
|
||||
*/
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '5s', target: 100 },
|
||||
{ duration: '20s', target: 400 },
|
||||
{ duration: '5s', target: 0 }
|
||||
{ duration: "5s", target: 100 },
|
||||
{ duration: "20s", target: 400 },
|
||||
{ duration: "5s", target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01']
|
||||
}
|
||||
}
|
||||
http_req_failed: ["rate<0.01"],
|
||||
},
|
||||
};
|
||||
|
||||
/* global __ENV */
|
||||
export default function () {
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php`)
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php`);
|
||||
}
|
||||
|
24
testdata/performance/timeouts.js
vendored
24
testdata/performance/timeouts.js
vendored
@@ -1,4 +1,4 @@
|
||||
import http from 'k6/http'
|
||||
import http from "k6/http";
|
||||
|
||||
/**
|
||||
* Databases or external resources can sometimes become unavailable for short periods of time.
|
||||
@@ -7,26 +7,26 @@ import http from 'k6/http'
|
||||
*/
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '20s', target: 100 },
|
||||
{ duration: '20s', target: 500 },
|
||||
{ duration: '20s', target: 0 }
|
||||
{ duration: "20s", target: 100 },
|
||||
{ duration: "20s", target: 500 },
|
||||
{ duration: "20s", target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_failed: ['rate<0.01']
|
||||
}
|
||||
}
|
||||
http_req_failed: ["rate<0.01"],
|
||||
},
|
||||
};
|
||||
|
||||
/* global __ENV */
|
||||
export default function () {
|
||||
const tenSecondInterval = Math.floor(new Date().getSeconds() / 10)
|
||||
const shouldHang = tenSecondInterval % 2 === 0
|
||||
const tenSecondInterval = Math.floor(new Date().getSeconds() / 10);
|
||||
const shouldHang = tenSecondInterval % 2 === 0;
|
||||
|
||||
// every 10 seconds requests lead to a max_execution-timeout
|
||||
if (shouldHang) {
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=50000`)
|
||||
return
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=50000`);
|
||||
return;
|
||||
}
|
||||
|
||||
// every other 10 seconds the resource is back
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=5&work=30000&output=100`)
|
||||
http.get(`${__ENV.CADDY_HOSTNAME}/sleep.php?sleep=5&work=30000&output=100`);
|
||||
}
|
||||
|
53
types.go
53
types.go
@@ -49,7 +49,7 @@ func GoAssociativeArray(arr unsafe.Pointer) AssociativeArray {
|
||||
return AssociativeArray{entries, order}
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map
|
||||
// EXPERIMENTAL: GoMap converts a zval having a zend_array value to an unordered Go map
|
||||
func GoMap(arr unsafe.Pointer) map[string]any {
|
||||
entries, _ := goArray(arr, false)
|
||||
return entries
|
||||
@@ -61,14 +61,14 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
}
|
||||
|
||||
zval := (*C.zval)(arr)
|
||||
hashTable := (*C.HashTable)(castZval(zval, C.IS_ARRAY))
|
||||
hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY))
|
||||
|
||||
if hashTable == nil {
|
||||
panic("received a *zval that wasn't a HashTable on array conversion")
|
||||
}
|
||||
|
||||
nNumUsed := hashTable.nNumUsed
|
||||
entries := make(map[string]any)
|
||||
entries := make(map[string]any, nNumUsed)
|
||||
var order []string
|
||||
if ordered {
|
||||
order = make([]string, 0, nNumUsed)
|
||||
@@ -82,7 +82,7 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
v := C.get_ht_packed_data(hashTable, i)
|
||||
if v != nil && C.zval_get_type(v) != C.IS_UNDEF {
|
||||
strIndex := strconv.Itoa(int(i))
|
||||
entries[strIndex] = convertZvalToGo(v)
|
||||
entries[strIndex] = GoValue(v)
|
||||
if ordered {
|
||||
order = append(order, strIndex)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
continue
|
||||
}
|
||||
|
||||
v := convertZvalToGo(&bucket.val)
|
||||
v := GoValue(&bucket.val)
|
||||
|
||||
if bucket.key != nil {
|
||||
keyStr := GoString(unsafe.Pointer(bucket.key))
|
||||
@@ -121,14 +121,14 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
return entries, order
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice
|
||||
// EXPERIMENTAL: GoPackedArray converts a zval with a zend_array value to a Go slice
|
||||
func GoPackedArray(arr unsafe.Pointer) []any {
|
||||
if arr == nil {
|
||||
panic("GoPackedArray received a nil pointer")
|
||||
}
|
||||
|
||||
zval := (*C.zval)(arr)
|
||||
hashTable := (*C.HashTable)(castZval(zval, C.IS_ARRAY))
|
||||
hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY))
|
||||
|
||||
if hashTable == nil {
|
||||
panic("GoPackedArray received *zval that wasn't a HashTable")
|
||||
@@ -141,7 +141,7 @@ func GoPackedArray(arr unsafe.Pointer) []any {
|
||||
for i := C.uint32_t(0); i < nNumUsed; i++ {
|
||||
v := C.get_ht_packed_data(hashTable, i)
|
||||
if v != nil && C.zval_get_type(v) != C.IS_UNDEF {
|
||||
result = append(result, convertZvalToGo(v))
|
||||
result = append(result, GoValue(v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,19 +152,19 @@ func GoPackedArray(arr unsafe.Pointer) []any {
|
||||
for i := C.uint32_t(0); i < nNumUsed; i++ {
|
||||
bucket := C.get_ht_bucket_data(hashTable, i)
|
||||
if bucket != nil && C.zval_get_type(&bucket.val) != C.IS_UNDEF {
|
||||
result = append(result, convertZvalToGo(&bucket.val))
|
||||
result = append(result, GoValue(&bucket.val))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array.
|
||||
// EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array
|
||||
func PHPMap(arr map[string]any) unsafe.Pointer {
|
||||
return phpArray(arr, nil)
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zend_array.
|
||||
// EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zval with a zend_array value
|
||||
func PHPAssociativeArray(arr AssociativeArray) unsafe.Pointer {
|
||||
return phpArray(arr.Map, arr.Order)
|
||||
}
|
||||
@@ -176,13 +176,13 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer {
|
||||
zendArray = createNewArray((uint32)(len(order)))
|
||||
for _, key := range order {
|
||||
val := entries[key]
|
||||
zval := convertGoToZval(val)
|
||||
zval := PHPValue(val)
|
||||
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval)
|
||||
}
|
||||
} else {
|
||||
zendArray = createNewArray((uint32)(len(entries)))
|
||||
for key, val := range entries {
|
||||
zval := convertGoToZval(val)
|
||||
zval := PHPValue(val)
|
||||
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval)
|
||||
}
|
||||
}
|
||||
@@ -193,11 +193,11 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer {
|
||||
return unsafe.Pointer(&zval)
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zend_array.
|
||||
// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zval with a zend_array value.
|
||||
func PHPPackedArray(slice []any) unsafe.Pointer {
|
||||
zendArray := createNewArray((uint32)(len(slice)))
|
||||
for _, val := range slice {
|
||||
zval := convertGoToZval(val)
|
||||
zval := PHPValue(val)
|
||||
C.zend_hash_next_index_insert(zendArray, zval)
|
||||
}
|
||||
|
||||
@@ -207,9 +207,10 @@ func PHPPackedArray(slice []any) unsafe.Pointer {
|
||||
return unsafe.Pointer(&zval)
|
||||
}
|
||||
|
||||
// convertZvalToGo converts a PHP zval to a Go any
|
||||
func convertZvalToGo(zval *C.zval) any {
|
||||
// EXPERIMENTAL: GoValue converts a PHP zval to a Go value
|
||||
func GoValue(zval *C.zval) any {
|
||||
t := C.zval_get_type(zval)
|
||||
|
||||
switch t {
|
||||
case C.IS_NULL:
|
||||
return nil
|
||||
@@ -218,26 +219,28 @@ func convertZvalToGo(zval *C.zval) any {
|
||||
case C.IS_TRUE:
|
||||
return true
|
||||
case C.IS_LONG:
|
||||
longPtr := (*C.zend_long)(castZval(zval, C.IS_LONG))
|
||||
longPtr := (*C.zend_long)(extractZvalValue(zval, C.IS_LONG))
|
||||
if longPtr != nil {
|
||||
return int64(*longPtr)
|
||||
}
|
||||
|
||||
return int64(0)
|
||||
case C.IS_DOUBLE:
|
||||
doublePtr := (*C.double)(castZval(zval, C.IS_DOUBLE))
|
||||
doublePtr := (*C.double)(extractZvalValue(zval, C.IS_DOUBLE))
|
||||
if doublePtr != nil {
|
||||
return float64(*doublePtr)
|
||||
}
|
||||
|
||||
return float64(0)
|
||||
case C.IS_STRING:
|
||||
str := (*C.zend_string)(castZval(zval, C.IS_STRING))
|
||||
str := (*C.zend_string)(extractZvalValue(zval, C.IS_STRING))
|
||||
if str == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return GoString(unsafe.Pointer(str))
|
||||
case C.IS_ARRAY:
|
||||
hashTable := (*C.HashTable)(castZval(zval, C.IS_ARRAY))
|
||||
hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY))
|
||||
if hashTable != nil && htIsPacked(hashTable) {
|
||||
return GoPackedArray(unsafe.Pointer(zval))
|
||||
}
|
||||
@@ -248,8 +251,8 @@ func convertZvalToGo(zval *C.zval) any {
|
||||
}
|
||||
}
|
||||
|
||||
// convertGoToZval converts a Go any to a PHP zval
|
||||
func convertGoToZval(value any) *C.zval {
|
||||
// PHPValue converts a Go any to a PHP zval
|
||||
func PHPValue(value any) *C.zval {
|
||||
var zval C.zval
|
||||
|
||||
switch v := value.(type) {
|
||||
@@ -292,8 +295,8 @@ func htIsPacked(ht *C.HashTable) bool {
|
||||
return (flags & C.HASH_FLAG_PACKED) != 0
|
||||
}
|
||||
|
||||
// castZval casts a zval to the expected type and returns a pointer to the value
|
||||
func castZval(zval *C.zval, expectedType C.uint8_t) unsafe.Pointer {
|
||||
// extractZvalValue returns a pointer to the zval value cast to the expected type
|
||||
func extractZvalValue(zval *C.zval, expectedType C.uint8_t) unsafe.Pointer {
|
||||
if zval == nil || C.zval_get_type(zval) != expectedType {
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user