mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
feat(extgen): support for mixed type (#1913)
* feat(extgent): support for mixed type
* refactor: use unsafe.Pointer
* Revert "refactor: use unsafe.Pointer"
This reverts commit 8a0b9c1beb.
* fix docs
* fix docs
* cleanup template
* fix template
* fix tests
This commit is contained in:
@@ -146,11 +146,11 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
**可用方法:**
|
||||
|
||||
- `SetInt(key int64, value interface{})` - 使用整数键设置值
|
||||
- `SetString(key string, value interface{})` - 使用字符串键设置值
|
||||
- `Append(value interface{})` - 使用下一个可用整数键添加值
|
||||
- `SetInt(key int64, value any)` - 使用整数键设置值
|
||||
- `SetString(key string, value any)` - 使用字符串键设置值
|
||||
- `Append(value any)` - 使用下一个可用整数键添加值
|
||||
- `Len() uint32` - 获取元素数量
|
||||
- `At(index uint32) (PHPKey, interface{})` - 获取索引处的键值对
|
||||
- `At(index uint32) (PHPKey, any)` - 获取索引处的键值对
|
||||
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
|
||||
|
||||
### 声明原生 PHP 类
|
||||
|
||||
@@ -33,7 +33,7 @@ As covered in the manual implementation section below as well, you need to [get
|
||||
The first step to writing a PHP extension in Go is to create a new Go module. You can use the following command for this:
|
||||
|
||||
```console
|
||||
go mod init github.com/my-account/my-module
|
||||
go mod init example.com/example
|
||||
```
|
||||
|
||||
The second step is to [get the PHP sources](https://www.php.net/downloads.php) for the next steps. Once you have them, decompress them into the directory of your choice, not inside your Go module:
|
||||
@@ -47,10 +47,14 @@ tar xf php-*
|
||||
Everything is now setup to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"C"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"strings"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
|
||||
@@ -98,6 +102,7 @@ This table summarizes what you need to know:
|
||||
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This table is not exhaustive yet and will be completed as the FrankenPHP types API gets more complete.
|
||||
>
|
||||
> For class methods specifically, primitive types and arrays are currently supported. Objects cannot be used as method parameters or return types yet.
|
||||
@@ -115,6 +120,16 @@ If order or association are not needed, it's also possible to directly convert t
|
||||
**Creating and manipulating arrays in Go:**
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
// export_php:function process_data_ordered(array $input): array
|
||||
func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
// Convert PHP associative array to Go while keeping the order
|
||||
@@ -128,7 +143,7 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
// return an ordered array
|
||||
// if 'Order' is not empty, only the key-value pairs in 'Order' will be respected
|
||||
return frankenphp.PHPAssociativeArray(AssociativeArray{
|
||||
return frankenphp.PHPAssociativeArray(frankenphp.AssociativeArray{
|
||||
Map: map[string]any{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
@@ -192,6 +207,8 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
The generator supports declaring **opaque classes** as Go structs, which can be used to create PHP objects. You can use the `//export_php:class` directive comment to define a PHP class. For example:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
@@ -216,6 +233,16 @@ This approach provides better encapsulation and prevents PHP code from accidenta
|
||||
Since properties are not directly accessible, you **must define methods** to interact with your opaque classes. Use the `//export_php:method` directive to define behavior:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
@@ -248,6 +275,16 @@ func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
|
||||
The generator supports nullable parameters using the `?` prefix in PHP signatures. When a parameter is nullable, it becomes a pointer in your Go function, allowing you to check if the value was `null` in PHP:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
|
||||
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
|
||||
// Check if name was provided (not null)
|
||||
@@ -275,6 +312,7 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
- **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`).
|
||||
|
||||
After generating the extension, you will be allowed to use the class and its methods in PHP. Note that you **cannot access properties directly**:
|
||||
@@ -311,6 +349,8 @@ The generator supports exporting Go constants to PHP using two directives: `//ex
|
||||
Use the `//export_php:const` directive to create global PHP constants:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:const
|
||||
const MAX_CONNECTIONS = 100
|
||||
|
||||
@@ -329,6 +369,8 @@ const STATUS_ERROR = iota
|
||||
Use the `//export_php:classconstant ClassName` directive to create constants that belong to a specific PHP class:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:classconstant User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
@@ -368,10 +410,15 @@ The directive supports various value types including strings, integers, booleans
|
||||
You can use constants just like you are used to in the Go code. For example, let's take the `repeat_this()` function we declared earlier and change the last argument to an integer:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"C"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"strings"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:const
|
||||
@@ -388,37 +435,37 @@ const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
// reverse the string
|
||||
}
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
// reverse the string
|
||||
}
|
||||
|
||||
if mode == STR_NORMAL {
|
||||
// no-op, just to showcase the constant
|
||||
}
|
||||
if mode == STR_NORMAL {
|
||||
// no-op, just to showcase the constant
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(result, false)
|
||||
return frankenphp.PHPString(result, false)
|
||||
}
|
||||
|
||||
//export_php:class StringProcessor
|
||||
type StringProcessorStruct struct {
|
||||
// internal fields
|
||||
// internal fields
|
||||
}
|
||||
|
||||
//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))
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -432,9 +479,13 @@ Use the `//export_php:namespace` directive at the top of your Go file to place a
|
||||
|
||||
```go
|
||||
//export_php:namespace My\Extension
|
||||
package main
|
||||
package example
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function hello(): string
|
||||
func hello() string {
|
||||
@@ -537,25 +588,26 @@ We'll see how to write a simple PHP extension in Go that defines a new native fu
|
||||
In your module, you need to define a new native function that will be called from PHP. To do this, create a file with the name you want, for example, `extension.go`, and add the following code:
|
||||
|
||||
```go
|
||||
package ext_go
|
||||
package example
|
||||
|
||||
//#include "extension.h"
|
||||
// #include "extension.h"
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"log/slog"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
|
||||
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
|
||||
}
|
||||
|
||||
//export go_print_something
|
||||
func go_print_something() {
|
||||
go func() {
|
||||
caddy.Log().Info("Hello from a goroutine!")
|
||||
}()
|
||||
go func() {
|
||||
slog.Info("Hello from a goroutine!")
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -731,7 +783,16 @@ There's only one thing left to do: implement the `go_upper` function in Go.
|
||||
Our Go function will take a `*C.zend_string` as a parameter, convert it to a Go string using FrankenPHP's helper function, process it, and return the result as a new `*C.zend_string`. The helper functions handle all the memory management and conversion complexity for us.
|
||||
|
||||
```go
|
||||
import "strings"
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"strings"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
@@ -743,9 +804,12 @@ func go_upper(s *C.zend_string) *C.zend_string {
|
||||
}
|
||||
```
|
||||
|
||||
This approach is much cleaner and safer than manual memory management. FrankenPHP's helper functions handle the conversion between PHP's `zend_string` format and Go strings automatically. The `false` parameter in `PHPString()` indicates that we want to create a new non-persistent string (freed at the end of the request).
|
||||
This approach is much cleaner and safer than manual memory management.
|
||||
FrankenPHP's helper functions handle the conversion between PHP's `zend_string` format and Go strings automatically.
|
||||
The `false` parameter in `PHPString()` indicates that we want to create a new non-persistent string (freed at the end of the request).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> In this example, we don't perform any error handling, but you should always check that pointers are not `nil` and that the data is valid before using it in your Go functions.
|
||||
|
||||
### Integrating the Extension into FrankenPHP
|
||||
|
||||
@@ -146,11 +146,11 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
**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
|
||||
- `SetInt(key int64, value any)` - Définir une valeur avec une clé entière
|
||||
- `SetString(key string, value any)` - Définir une valeur avec une clé chaîne
|
||||
- `Append(value any)` - 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
|
||||
- `At(index uint32) (PHPKey, any)` - 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
|
||||
|
||||
Reference in New Issue
Block a user