mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
feat(extgen): add support for callable in parameters (#1731)
This commit is contained in:
committed by
GitHub
parent
58a63703b4
commit
bb1c3678dc
@@ -88,19 +88,20 @@ While some variable types have the same memory representation between C/PHP and
|
||||
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` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `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` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
|
||||
|--------------------|-------------------------------|-------------------|-----------------------------------|------------------------------------|-----------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `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()` | ❌ |
|
||||
| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ |
|
||||
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@@ -212,6 +213,42 @@ func process_data_packed(arr *C.zend_array) unsafe.Pointer {
|
||||
- `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
|
||||
|
||||
### Working with Callables
|
||||
|
||||
FrankenPHP provides a way to work with PHP callables using the `frankenphp.CallPHPCallable` helper. This allows you to call PHP functions or methods from Go code.
|
||||
|
||||
To showcase this, let's create our own `array_map()` function that takes a callable and an array, applies the callable to each element of the array, and returns a new array with the results:
|
||||
|
||||
```go
|
||||
// export_php:function my_array_map(array $data, callable $callback): array
|
||||
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := make([]any, len(goSlice))
|
||||
|
||||
for index, value := range goSlice {
|
||||
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray(result)
|
||||
}
|
||||
```
|
||||
|
||||
Notice how we use `frankenphp.CallPHPCallable()` to call the PHP callable passed as a parameter. This function takes a pointer to the callable and an array of arguments, and it returns the result of the callable execution. You can use the callable syntax you're used to:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
// $result will be [2, 4, 6]
|
||||
|
||||
$result = my_array_map(['hello', 'world'], 'strtoupper');
|
||||
// $result will be ['HELLO', 'WORLD']
|
||||
```
|
||||
|
||||
### Declaring a Native PHP Class
|
||||
|
||||
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:
|
||||
|
||||
@@ -210,6 +210,42 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convertir un tableau PHP vers une map Go non ordonnée
|
||||
- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convertir un tableau PHP vers un slice Go
|
||||
|
||||
### Travailler avec des Callables
|
||||
|
||||
FrankenPHP propose un moyen de travailler avec les _callables_ PHP grâce au helper `frankenphp.CallPHPCallable()`. Cela permet d'appeler des fonctions ou des méthodes PHP depuis du code Go.
|
||||
|
||||
Pour illustrer cela, créons notre propre fonction `array_map()` qui prend un _callable_ et un tableau, applique le _callable_ à chaque élément du tableau, et retourne un nouveau tableau avec les résultats :
|
||||
|
||||
```go
|
||||
// export_php:function my_array_map(array $data, callable $callback): array
|
||||
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := make([]any, len(goSlice))
|
||||
|
||||
for index, value := range goSlice {
|
||||
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray(result)
|
||||
}
|
||||
```
|
||||
|
||||
Remarquez comment nous utilisons `frankenphp.CallPHPCallable()` pour appeler le _callable_ PHP passé en paramètre. Cette fonction prend un pointeur vers le _callable_ et un tableau d'arguments, et elle retourne le résultat de l'exécution du _callable_. Vous pouvez utiliser la syntaxe habituelle des _callables_ :
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
// $result vaudra [2, 4, 6]
|
||||
|
||||
$result = my_array_map(['hello', 'world'], 'strtoupper');
|
||||
// $result vaudra ['HELLO', 'WORLD']
|
||||
```
|
||||
|
||||
### Déclarer une Classe PHP Native
|
||||
|
||||
Le générateur prend en charge la déclaration de **classes opaques** comme structures Go, qui peuvent être utilisées pour créer des objets PHP. Vous pouvez utiliser la directive `//export_php:class` pour définir une classe PHP. Par exemple :
|
||||
|
||||
@@ -128,14 +128,15 @@ type GoParameter struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
var phpToGoTypeMap = map[phpType]string{
|
||||
phpString: "string",
|
||||
phpInt: "int64",
|
||||
phpFloat: "float64",
|
||||
phpBool: "bool",
|
||||
phpArray: "*frankenphp.Array",
|
||||
phpMixed: "any",
|
||||
phpVoid: "",
|
||||
var phpToGoTypeMap= map[phpType]string{
|
||||
phpString: "string",
|
||||
phpInt: "int64",
|
||||
phpFloat: "float64",
|
||||
phpBool: "bool",
|
||||
phpArray: "*frankenphp.Array",
|
||||
phpMixed: "any",
|
||||
phpVoid: "",
|
||||
phpCallable: "*C.zval",
|
||||
}
|
||||
|
||||
func (gg *GoFileGenerator) phpTypeToGoType(phpT phpType) string {
|
||||
|
||||
@@ -703,6 +703,125 @@ func testGoFileExportedFunctions(t *testing.T, content string, functions []phpFu
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoFileGenerator_MethodWrapperWithCallableParams(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
sourceContent := `package main
|
||||
|
||||
import "C"
|
||||
|
||||
//export_php:class CallableClass
|
||||
type CallableStruct struct{}
|
||||
|
||||
//export_php:method CallableClass::processCallback(callable $callback): string
|
||||
func (cs *CallableStruct) ProcessCallback(callback *C.zval) string {
|
||||
return "processed"
|
||||
}
|
||||
|
||||
//export_php:method CallableClass::processOptionalCallback(?callable $callback): string
|
||||
func (cs *CallableStruct) ProcessOptionalCallback(callback *C.zval) string {
|
||||
return "processed_optional"
|
||||
}`
|
||||
|
||||
sourceFile := filepath.Join(tmpDir, "test.go")
|
||||
require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644))
|
||||
|
||||
methods := []phpClassMethod{
|
||||
{
|
||||
Name: "ProcessCallback",
|
||||
PhpName: "processCallback",
|
||||
ClassName: "CallableClass",
|
||||
Signature: "processCallback(callable $callback): string",
|
||||
ReturnType: phpString,
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, IsNullable: false},
|
||||
},
|
||||
GoFunction: `func (cs *CallableStruct) ProcessCallback(callback *C.zval) string {
|
||||
return "processed"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
Name: "ProcessOptionalCallback",
|
||||
PhpName: "processOptionalCallback",
|
||||
ClassName: "CallableClass",
|
||||
Signature: "processOptionalCallback(?callable $callback): string",
|
||||
ReturnType: phpString,
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, IsNullable: true},
|
||||
},
|
||||
GoFunction: `func (cs *CallableStruct) ProcessOptionalCallback(callback *C.zval) string {
|
||||
return "processed_optional"
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
classes := []phpClass{
|
||||
{
|
||||
Name: "CallableClass",
|
||||
GoStruct: "CallableStruct",
|
||||
Methods: methods,
|
||||
},
|
||||
}
|
||||
|
||||
generator := &Generator{
|
||||
BaseName: "callable_test",
|
||||
SourceFile: sourceFile,
|
||||
Classes: classes,
|
||||
BuildDir: tmpDir,
|
||||
}
|
||||
|
||||
goGen := GoFileGenerator{generator}
|
||||
content, err := goGen.buildContent()
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedCallableWrapperSignature := "func ProcessCallback_wrapper(handle C.uintptr_t, callback *C.zval) unsafe.Pointer"
|
||||
assert.Contains(t, content, expectedCallableWrapperSignature, "Generated content should contain callable wrapper signature: %s", expectedCallableWrapperSignature)
|
||||
|
||||
expectedOptionalCallableWrapperSignature := "func ProcessOptionalCallback_wrapper(handle C.uintptr_t, callback *C.zval) unsafe.Pointer"
|
||||
assert.Contains(t, content, expectedOptionalCallableWrapperSignature, "Generated content should contain optional callable wrapper signature: %s", expectedOptionalCallableWrapperSignature)
|
||||
|
||||
expectedCallableCall := "structObj.ProcessCallback(callback)"
|
||||
assert.Contains(t, content, expectedCallableCall, "Generated content should contain callable method call: %s", expectedCallableCall)
|
||||
|
||||
expectedOptionalCallableCall := "structObj.ProcessOptionalCallback(callback)"
|
||||
assert.Contains(t, content, expectedOptionalCallableCall, "Generated content should contain optional callable method call: %s", expectedOptionalCallableCall)
|
||||
|
||||
assert.Contains(t, content, "//export ProcessCallback_wrapper", "Generated content should contain ProcessCallback export directive")
|
||||
assert.Contains(t, content, "//export ProcessOptionalCallback_wrapper", "Generated content should contain ProcessOptionalCallback export directive")
|
||||
}
|
||||
|
||||
func TestGoFileGenerator_phpTypeToGoType(t *testing.T) {
|
||||
generator := &Generator{}
|
||||
goGen := GoFileGenerator{generator}
|
||||
|
||||
tests := []struct {
|
||||
phpType phpType
|
||||
expected string
|
||||
}{
|
||||
{phpString, "string"},
|
||||
{phpInt, "int64"},
|
||||
{phpFloat, "float64"},
|
||||
{phpBool, "bool"},
|
||||
{phpArray, "*frankenphp.Array"},
|
||||
{phpMixed, "any"},
|
||||
{phpVoid, ""},
|
||||
{phpCallable, "*C.zval"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.phpType), func(t *testing.T) {
|
||||
result := goGen.phpTypeToGoType(tt.phpType)
|
||||
assert.Equal(t, tt.expected, result, "phpTypeToGoType(%s) should return %s", tt.phpType, tt.expected)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("unknown_type", func(t *testing.T) {
|
||||
unknownType := phpType("unknown")
|
||||
result := goGen.phpTypeToGoType(unknownType)
|
||||
assert.Equal(t, "any", result, "phpTypeToGoType should fallback to interface{} for unknown types")
|
||||
})
|
||||
}
|
||||
|
||||
func testGoFileInternalFunctions(t *testing.T, content string) {
|
||||
internalIndicators := []string{
|
||||
"func internalHelper",
|
||||
|
||||
@@ -118,6 +118,30 @@ func (s *IntegrationTestSuite) runExtensionInit(sourceFile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupGeneratedFiles removes generated files from the original source directory
|
||||
func (s *IntegrationTestSuite) cleanupGeneratedFiles(originalSourceFile string) {
|
||||
s.t.Helper()
|
||||
|
||||
sourceDir := filepath.Dir(originalSourceFile)
|
||||
baseName := SanitizePackageName(strings.TrimSuffix(filepath.Base(originalSourceFile), ".go"))
|
||||
|
||||
generatedFiles := []string{
|
||||
baseName + ".stub.php",
|
||||
baseName + "_arginfo.h",
|
||||
baseName + ".h",
|
||||
baseName + ".c",
|
||||
baseName + ".go",
|
||||
"README.md",
|
||||
}
|
||||
|
||||
for _, file := range generatedFiles {
|
||||
fullPath := filepath.Join(sourceDir, file)
|
||||
if _, err := os.Stat(fullPath); err == nil {
|
||||
os.Remove(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compileFrankenPHP compiles FrankenPHP with the generated extension
|
||||
func (s *IntegrationTestSuite) compileFrankenPHP(moduleDir string) (string, error) {
|
||||
s.t.Helper()
|
||||
@@ -250,6 +274,7 @@ func TestBasicFunction(t *testing.T) {
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "basic_function.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
@@ -326,6 +351,7 @@ func TestClassMethodsIntegration(t *testing.T) {
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "class_methods.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
@@ -437,6 +463,7 @@ func TestConstants(t *testing.T) {
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "constants.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
@@ -536,6 +563,7 @@ func TestNamespace(t *testing.T) {
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "namespace.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
@@ -625,6 +653,7 @@ func TestInvalidSignature(t *testing.T) {
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "invalid_signature.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
@@ -640,6 +669,7 @@ func TestTypeMismatch(t *testing.T) {
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "type_mismatch.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
@@ -681,3 +711,83 @@ func dummy() {}
|
||||
assert.Error(t, err, "should fail when gen_stub.php is missing")
|
||||
assert.Contains(t, err.Error(), "gen_stub.php", "error should mention missing script")
|
||||
}
|
||||
|
||||
func TestCallable(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "callable.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.verifyPHPSymbols(
|
||||
[]string{"my_array_map", "my_filter"},
|
||||
[]string{"Processor"},
|
||||
[]string{},
|
||||
)
|
||||
require.NoError(t, err, "all functions and classes should be accessible from PHP")
|
||||
|
||||
err = suite.verifyFunctionBehavior(`<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
if ($result !== [2, 4, 6]) {
|
||||
echo "FAIL: my_array_map with closure expected [2, 4, 6], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_array_map(['hello', 'world'], 'strtoupper');
|
||||
if ($result !== ['HELLO', 'WORLD']) {
|
||||
echo "FAIL: my_array_map with function name expected ['HELLO', 'WORLD'], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_array_map([], function($x) { return $x; });
|
||||
if ($result !== []) {
|
||||
echo "FAIL: my_array_map with empty array expected [], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_filter([1, 2, 3, 4, 5, 6], function($x) { return $x % 2 === 0; });
|
||||
if ($result !== [2, 4, 6]) {
|
||||
echo "FAIL: my_filter expected [2, 4, 6], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_filter([1, 2, 3, 4], null);
|
||||
if ($result !== [1, 2, 3, 4]) {
|
||||
echo "FAIL: my_filter with null callback expected [1, 2, 3, 4], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$processor = new Processor();
|
||||
$result = $processor->transform('hello', function($s) { return strtoupper($s); });
|
||||
if ($result !== 'HELLO') {
|
||||
echo "FAIL: Processor::transform with closure expected 'HELLO', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = $processor->transform('world', 'strtoupper');
|
||||
if ($result !== 'WORLD') {
|
||||
echo "FAIL: Processor::transform with function name expected 'WORLD', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = $processor->transform(' test ', 'trim');
|
||||
if ($result !== 'test') {
|
||||
echo "FAIL: Processor::transform with trim expected 'test', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
`, "OK")
|
||||
require.NoError(t, err, "all callable tests should pass")
|
||||
}
|
||||
|
||||
@@ -9,17 +9,18 @@ import (
|
||||
type phpType string
|
||||
|
||||
const (
|
||||
phpString phpType = "string"
|
||||
phpInt phpType = "int"
|
||||
phpFloat phpType = "float"
|
||||
phpBool phpType = "bool"
|
||||
phpArray phpType = "array"
|
||||
phpObject phpType = "object"
|
||||
phpMixed phpType = "mixed"
|
||||
phpVoid phpType = "void"
|
||||
phpNull phpType = "null"
|
||||
phpTrue phpType = "true"
|
||||
phpFalse phpType = "false"
|
||||
phpString phpType = "string"
|
||||
phpInt phpType = "int"
|
||||
phpFloat phpType = "float"
|
||||
phpBool phpType = "bool"
|
||||
phpArray phpType = "array"
|
||||
phpObject phpType = "object"
|
||||
phpMixed phpType = "mixed"
|
||||
phpVoid phpType = "void"
|
||||
phpNull phpType = "null"
|
||||
phpTrue phpType = "true"
|
||||
phpFalse phpType = "false"
|
||||
phpCallable phpType = "callable"
|
||||
)
|
||||
|
||||
type phpFunction struct {
|
||||
|
||||
@@ -68,8 +68,12 @@ func (pp *ParameterParser) generateSingleParamDeclaration(param phpParameter) []
|
||||
if param.IsNullable {
|
||||
decls = append(decls, fmt.Sprintf("zend_bool %s_is_null = 0;", param.Name))
|
||||
}
|
||||
case phpArray, phpMixed:
|
||||
case phpArray:
|
||||
decls = append(decls, fmt.Sprintf("zend_array *%s = NULL;", param.Name))
|
||||
case phpMixed:
|
||||
decls = append(decls, fmt.Sprintf("zval *%s = NULL;", param.Name))
|
||||
case "callable":
|
||||
decls = append(decls, fmt.Sprintf("zval *%s_callback;", param.Name))
|
||||
}
|
||||
|
||||
return decls
|
||||
@@ -118,9 +122,11 @@ func (pp *ParameterParser) generateParamParsingMacro(param phpParameter) string
|
||||
case phpBool:
|
||||
return fmt.Sprintf("\n Z_PARAM_BOOL_OR_NULL(%s, %s_is_null)", param.Name, param.Name)
|
||||
case phpArray:
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY_OR_NULL(%s)", param.Name)
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY_HT_OR_NULL(%s)", param.Name)
|
||||
case phpMixed:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL_OR_NULL(%s)", param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL_OR_NULL(%s_callback)", param.Name)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -135,9 +141,11 @@ func (pp *ParameterParser) generateParamParsingMacro(param phpParameter) string
|
||||
case phpBool:
|
||||
return fmt.Sprintf("\n Z_PARAM_BOOL(%s)", param.Name)
|
||||
case phpArray:
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY(%s)", param.Name)
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY_HT(%s)", param.Name)
|
||||
case phpMixed:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL(%s)", param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL(%s_callback)", param.Name)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -168,6 +176,8 @@ func (pp *ParameterParser) generateSingleGoCallParam(param phpParameter) string
|
||||
return fmt.Sprintf("%s_is_null ? NULL : &%s", param.Name, param.Name)
|
||||
case phpBool:
|
||||
return fmt.Sprintf("%s_is_null ? NULL : &%s", param.Name, param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("%s_callback", param.Name)
|
||||
default:
|
||||
return param.Name
|
||||
}
|
||||
@@ -180,6 +190,8 @@ func (pp *ParameterParser) generateSingleGoCallParam(param phpParameter) string
|
||||
return fmt.Sprintf("(double) %s", param.Name)
|
||||
case phpBool:
|
||||
return fmt.Sprintf("(int) %s", param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("%s_callback", param.Name)
|
||||
default:
|
||||
return param.Name
|
||||
}
|
||||
|
||||
@@ -145,14 +145,14 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
|
||||
params: []phpParameter{
|
||||
{Name: "items", PhpType: phpArray, HasDefault: false},
|
||||
},
|
||||
expected: " zval *items = NULL;",
|
||||
expected: " zend_array *items = NULL;",
|
||||
},
|
||||
{
|
||||
name: "nullable array parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "items", PhpType: phpArray, HasDefault: false, IsNullable: true},
|
||||
},
|
||||
expected: " zval *items = NULL;",
|
||||
expected: " zend_array *items = NULL;",
|
||||
},
|
||||
{
|
||||
name: "mixed types with array",
|
||||
@@ -161,7 +161,7 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
|
||||
{Name: "items", PhpType: phpArray, HasDefault: false},
|
||||
{Name: "count", PhpType: phpInt, HasDefault: true, DefaultValue: "5"},
|
||||
},
|
||||
expected: " zend_string *name = NULL;\n zval *items = NULL;\n zend_long count = 5;",
|
||||
expected: " zend_string *name = NULL;\n zend_array *items = NULL;\n zend_long count = 5;",
|
||||
},
|
||||
{
|
||||
name: "mixed parameter",
|
||||
@@ -177,6 +177,29 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
|
||||
},
|
||||
expected: " zval *m = NULL;",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, HasDefault: false},
|
||||
},
|
||||
expected: " zval *callback_callback;",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, HasDefault: false, IsNullable: true},
|
||||
},
|
||||
expected: " zval *callback_callback;",
|
||||
},
|
||||
{
|
||||
name: "mixed types with callable",
|
||||
params: []phpParameter{
|
||||
{Name: "data", PhpType: phpArray, HasDefault: false},
|
||||
{Name: "callback", PhpType: phpCallable, HasDefault: false},
|
||||
{Name: "options", PhpType: phpInt, HasDefault: true, DefaultValue: "0"},
|
||||
},
|
||||
expected: " zend_array *data = NULL;\n zval *callback_callback;\n zend_long options = 0;",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -292,6 +315,29 @@ func TestParameterParser_GenerateGoCallParams(t *testing.T) {
|
||||
},
|
||||
expected: "name, items, (long) count",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: "callable"},
|
||||
},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: "callable", IsNullable: true},
|
||||
},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "mixed parameters with callable",
|
||||
params: []phpParameter{
|
||||
{Name: "data", PhpType: "array"},
|
||||
{Name: "callback", PhpType: "callable"},
|
||||
{Name: "limit", PhpType: "int"},
|
||||
},
|
||||
expected: "data, callback_callback, (long) limit",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -353,12 +399,12 @@ func TestParameterParser_GenerateParamParsingMacro(t *testing.T) {
|
||||
{
|
||||
name: "array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray},
|
||||
expected: "\n Z_PARAM_ARRAY(items)",
|
||||
expected: "\n Z_PARAM_ARRAY_HT(items)",
|
||||
},
|
||||
{
|
||||
name: "nullable array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, IsNullable: true},
|
||||
expected: "\n Z_PARAM_ARRAY_OR_NULL(items)",
|
||||
expected: "\n Z_PARAM_ARRAY_HT_OR_NULL(items)",
|
||||
},
|
||||
{
|
||||
name: "mixed parameter",
|
||||
@@ -370,6 +416,16 @@ func TestParameterParser_GenerateParamParsingMacro(t *testing.T) {
|
||||
param: phpParameter{Name: "m", PhpType: phpMixed, IsNullable: true},
|
||||
expected: "\n Z_PARAM_ZVAL_OR_NULL(m)",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: phpCallable},
|
||||
expected: "\n Z_PARAM_ZVAL(callback_callback)",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: phpCallable, IsNullable: true},
|
||||
expected: "\n Z_PARAM_ZVAL_OR_NULL(callback_callback)",
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
param: phpParameter{Name: "unknown", PhpType: phpType("unknown")},
|
||||
@@ -480,6 +536,16 @@ func TestParameterParser_GenerateSingleGoCallParam(t *testing.T) {
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, IsNullable: true},
|
||||
expected: "items",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable"},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable", IsNullable: true},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
param: phpParameter{Name: "unknown", PhpType: phpType("unknown")},
|
||||
@@ -551,12 +617,22 @@ func TestParameterParser_GenerateSingleParamDeclaration(t *testing.T) {
|
||||
{
|
||||
name: "array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, HasDefault: false},
|
||||
expected: []string{"zval *items = NULL;"},
|
||||
expected: []string{"zend_array *items = NULL;"},
|
||||
},
|
||||
{
|
||||
name: "nullable array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, HasDefault: false, IsNullable: true},
|
||||
expected: []string{"zval *items = NULL;"},
|
||||
expected: []string{"zend_array *items = NULL;"},
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable", HasDefault: false},
|
||||
expected: []string{"zval *callback_callback;"},
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable", HasDefault: false, IsNullable: true},
|
||||
expected: []string{"zval *callback_callback;"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -107,8 +107,8 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) {
|
||||
},
|
||||
contains: []string{
|
||||
"PHP_FUNCTION(process_array)",
|
||||
"zval *input = NULL;",
|
||||
"Z_PARAM_ARRAY(input)",
|
||||
"zend_array *input = NULL;",
|
||||
"Z_PARAM_ARRAY_HT(input)",
|
||||
"zend_array *result = process_array(input);",
|
||||
"RETURN_ARR(result)",
|
||||
},
|
||||
@@ -126,10 +126,10 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) {
|
||||
},
|
||||
contains: []string{
|
||||
"PHP_FUNCTION(filter_array)",
|
||||
"zval *data = NULL;",
|
||||
"zend_array *data = NULL;",
|
||||
"zend_string *key = NULL;",
|
||||
"zend_long limit = 10;",
|
||||
"Z_PARAM_ARRAY(data)",
|
||||
"Z_PARAM_ARRAY_HT(data)",
|
||||
"Z_PARAM_STR(key)",
|
||||
"Z_PARAM_LONG(limit)",
|
||||
"ZEND_PARSE_PARAMETERS_START(2, 3)",
|
||||
@@ -201,7 +201,7 @@ func TestPHPFunctionGenerator_GenerateParamDeclarations(t *testing.T) {
|
||||
{Name: "items", PhpType: phpArray},
|
||||
},
|
||||
contains: []string{
|
||||
"zval *items = NULL;",
|
||||
"zend_array *items = NULL;",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -213,7 +213,7 @@ func TestPHPFunctionGenerator_GenerateParamDeclarations(t *testing.T) {
|
||||
},
|
||||
contains: []string{
|
||||
"zend_string *name = NULL;",
|
||||
"zval *data = NULL;",
|
||||
"zend_array *data = NULL;",
|
||||
"zend_long count = 0;",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -95,7 +95,9 @@ PHP_METHOD({{namespacedClassName $.Namespace .ClassName}}, {{.PhpName}}) {
|
||||
zend_bool {{$param.Name}} = {{if $param.HasDefault}}{{if eq $param.DefaultValue "true"}}1{{else}}0{{end}}{{else}}0{{end}};{{if $param.IsNullable}}
|
||||
zend_bool {{$param.Name}}_is_null = 0;{{end}}
|
||||
{{- else if eq $param.PhpType "array"}}
|
||||
zval *{{$param.Name}} = NULL;
|
||||
zend_array *{{$param.Name}} = NULL;
|
||||
{{- else if eq $param.PhpType "callable"}}
|
||||
zval *{{$param.Name}}_callback;
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
@@ -104,7 +106,7 @@ PHP_METHOD({{namespacedClassName $.Namespace .ClassName}}, {{.PhpName}}) {
|
||||
{{$optionalStarted := false}}{{range .Params}}{{if .HasDefault}}{{if not $optionalStarted -}}
|
||||
Z_PARAM_OPTIONAL
|
||||
{{$optionalStarted = true}}{{end}}{{end -}}
|
||||
{{if .IsNullable}}{{if eq .PhpType "string"}}Z_PARAM_STR_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "int"}}Z_PARAM_LONG_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "float"}}Z_PARAM_DOUBLE_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "bool"}}Z_PARAM_BOOL_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "array"}}Z_PARAM_ARRAY_OR_NULL({{.Name}}){{end}}{{else}}{{if eq .PhpType "string"}}Z_PARAM_STR({{.Name}}){{else if eq .PhpType "int"}}Z_PARAM_LONG({{.Name}}){{else if eq .PhpType "float"}}Z_PARAM_DOUBLE({{.Name}}){{else if eq .PhpType "bool"}}Z_PARAM_BOOL({{.Name}}){{else if eq .PhpType "array"}}Z_PARAM_ARRAY({{.Name}}){{end}}{{end}}
|
||||
{{if .IsNullable}}{{if eq .PhpType "string"}}Z_PARAM_STR_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "int"}}Z_PARAM_LONG_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "float"}}Z_PARAM_DOUBLE_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "bool"}}Z_PARAM_BOOL_OR_NULL({{.Name}}, {{.Name}}_is_null){{else if eq .PhpType "array"}}Z_PARAM_ARRAY_HT_OR_NULL({{.Name}}){{else if eq .PhpType "callable"}}Z_PARAM_ZVAL_OR_NULL({{.Name}}_callback){{end}}{{else}}{{if eq .PhpType "string"}}Z_PARAM_STR({{.Name}}){{else if eq .PhpType "int"}}Z_PARAM_LONG({{.Name}}){{else if eq .PhpType "float"}}Z_PARAM_DOUBLE({{.Name}}){{else if eq .PhpType "bool"}}Z_PARAM_BOOL({{.Name}}){{else if eq .PhpType "array"}}Z_PARAM_ARRAY_HT({{.Name}}){{else if eq .PhpType "callable"}}Z_PARAM_ZVAL({{.Name}}_callback){{end}}{{end}}
|
||||
{{end -}}
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
{{else}}
|
||||
@@ -113,22 +115,22 @@ PHP_METHOD({{namespacedClassName $.Namespace .ClassName}}, {{.PhpName}}) {
|
||||
|
||||
{{- if ne .ReturnType "void"}}
|
||||
{{- if eq .ReturnType "string"}}
|
||||
zend_string* result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{end}}{{else}}{{.Name}}{{end}}{{end}}{{end}});
|
||||
zend_string* result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{else}}{{if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{else}}{{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
if (result) {
|
||||
RETURN_STR(result);
|
||||
}
|
||||
RETURN_EMPTY_STRING();
|
||||
{{- else if eq .ReturnType "int"}}
|
||||
zend_long result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{else}}(long){{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
zend_long result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{else}}{{if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{else}}(long){{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
RETURN_LONG(result);
|
||||
{{- else if eq .ReturnType "float"}}
|
||||
double result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{else}}(double){{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
double result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{else}}{{if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{else}}(double){{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
RETURN_DOUBLE(result);
|
||||
{{- else if eq .ReturnType "bool"}}
|
||||
int result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{else}}(int){{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
int result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{else}}{{if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{else}}(int){{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
RETURN_BOOL(result);
|
||||
{{- else if eq .ReturnType "array"}}
|
||||
void* result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{else}}{{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
void* result = {{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{else}}{{if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{else}}{{.Name}}{{end}}{{end}}{{end}}{{end}});
|
||||
if (result != NULL) {
|
||||
HashTable *ht = (HashTable*)result;
|
||||
RETURN_ARR(ht);
|
||||
@@ -137,7 +139,7 @@ PHP_METHOD({{namespacedClassName $.Namespace .ClassName}}, {{.PhpName}}) {
|
||||
}
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
{{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "string"}}{{.Name}}{{else if eq .PhpType "int"}}(long){{.Name}}{{else if eq .PhpType "float"}}(double){{.Name}}{{else if eq .PhpType "bool"}}(int){{.Name}}{{else if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{end}}{{end}}{{end}}{{end}});
|
||||
{{.Name}}_wrapper(intern->go_handle{{if .Params}}{{range .Params}}, {{if .IsNullable}}{{if eq .PhpType "string"}}{{.Name}}_is_null ? NULL : {{.Name}}{{else if eq .PhpType "int"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "float"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "bool"}}{{.Name}}_is_null ? NULL : &{{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{else}}{{if eq .PhpType "string"}}{{.Name}}{{else if eq .PhpType "int"}}(long){{.Name}}{{else if eq .PhpType "float"}}(double){{.Name}}{{else if eq .PhpType "bool"}}(int){{.Name}}{{else if eq .PhpType "array"}}{{.Name}}{{else if eq .PhpType "callable"}}{{.Name}}_callback{{end}}{{end}}{{end}}{{end}});
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}{{end}}
|
||||
|
||||
@@ -76,7 +76,7 @@ func create_{{.GoStruct}}_object() C.uintptr_t {
|
||||
{{- end}}
|
||||
{{- range .Methods}}
|
||||
//export {{.Name}}_wrapper
|
||||
func {{.Name}}_wrapper(handle C.uintptr_t{{range .Params}}{{if eq .PhpType "string"}}, {{.Name}} *C.zend_string{{else if eq .PhpType "array"}}, {{.Name}} *C.zval{{else}}, {{.Name}} {{if .IsNullable}}*{{end}}{{phpTypeToGoType .PhpType}}{{end}}{{end}}){{if not (isVoid .ReturnType)}}{{if isStringOrArray .ReturnType}} unsafe.Pointer{{else}} {{phpTypeToGoType .ReturnType}}{{end}}{{end}} {
|
||||
func {{.Name}}_wrapper(handle C.uintptr_t{{range .Params}}{{if eq .PhpType "string"}}, {{.Name}} *C.zend_string{{else if eq .PhpType "array"}}, {{.Name}} *C.zval{{else if eq .PhpType "callable"}}, {{.Name}} *C.zval{{else}}, {{.Name}} {{if .IsNullable}}*{{end}}{{phpTypeToGoType .PhpType}}{{end}}{{end}}){{if not (isVoid .ReturnType)}}{{if isStringOrArray .ReturnType}} unsafe.Pointer{{else}} {{phpTypeToGoType .ReturnType}}{{end}}{{end}} {
|
||||
obj := getGoObject(handle)
|
||||
if obj == nil {
|
||||
{{- if not (isVoid .ReturnType)}}
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
paramTypes = []phpType{phpString, phpInt, phpFloat, phpBool, phpArray, phpObject, phpMixed}
|
||||
paramTypes = []phpType{phpString, phpInt, phpFloat, phpBool, phpArray, phpObject, phpMixed, phpCallable}
|
||||
returnTypes = []phpType{phpVoid, phpString, phpInt, phpFloat, phpBool, phpArray, phpObject, phpMixed, phpNull, phpTrue, phpFalse}
|
||||
propTypes = []phpType{phpString, phpInt, phpFloat, phpBool, phpArray, phpObject, phpMixed}
|
||||
supportedTypes = []phpType{phpString, phpInt, phpFloat, phpBool, phpArray, phpMixed}
|
||||
supportedTypes = []phpType{phpString, phpInt, phpFloat, phpBool, phpArray, phpMixed, phpCallable}
|
||||
|
||||
functionNameRegex = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||
parameterNameRegex = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||
@@ -160,8 +160,10 @@ func (v *Validator) validateGoFunctionSignatureWithOptions(phpFunc phpFunction,
|
||||
effectiveGoParamCount = goParamCount - 1
|
||||
}
|
||||
|
||||
if len(phpFunc.Params) != effectiveGoParamCount {
|
||||
return fmt.Errorf("parameter count mismatch: PHP function has %d parameters but Go function has %d", len(phpFunc.Params), effectiveGoParamCount)
|
||||
expectedGoParams := len(phpFunc.Params)
|
||||
|
||||
if expectedGoParams != effectiveGoParamCount {
|
||||
return fmt.Errorf("parameter count mismatch: PHP function has %d parameters (expecting %d Go parameters) but Go function has %d", len(phpFunc.Params), expectedGoParams, effectiveGoParamCount)
|
||||
}
|
||||
|
||||
if goFunc.Type.Params != nil && len(phpFunc.Params) > 0 {
|
||||
@@ -207,11 +209,13 @@ func (v *Validator) phpTypeToGoType(t phpType, isNullable bool) string {
|
||||
baseType = "*C.zend_array"
|
||||
case phpMixed:
|
||||
baseType = "*C.zval"
|
||||
case phpCallable:
|
||||
baseType = "*C.zval"
|
||||
default:
|
||||
baseType = "any"
|
||||
}
|
||||
|
||||
if isNullable && t != phpString && t != phpArray {
|
||||
if isNullable && t != phpString && t != phpArray && t != phpCallable {
|
||||
return "*" + baseType
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,53 @@ func TestValidateFunction(t *testing.T) {
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid function with array parameter",
|
||||
function: phpFunction{
|
||||
Name: "arrayFunction",
|
||||
ReturnType: "array",
|
||||
Params: []phpParameter{
|
||||
{Name: "items", PhpType: phpArray},
|
||||
{Name: "filter", PhpType: phpString},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid function with nullable array parameter",
|
||||
function: phpFunction{
|
||||
Name: "nullableArrayFunction",
|
||||
ReturnType: "string",
|
||||
Params: []phpParameter{
|
||||
{Name: "items", PhpType: phpArray, IsNullable: true},
|
||||
{Name: "name", PhpType: phpString},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid function with callable parameter",
|
||||
function: phpFunction{
|
||||
Name: "callableFunction",
|
||||
ReturnType: "array",
|
||||
Params: []phpParameter{
|
||||
{Name: "data", PhpType: phpArray},
|
||||
{Name: "callback", PhpType: phpCallable},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid function with nullable callable parameter",
|
||||
function: phpFunction{
|
||||
Name: "nullableCallableFunction",
|
||||
ReturnType: "string",
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, IsNullable: true},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty function name",
|
||||
function: phpFunction{
|
||||
@@ -304,6 +351,23 @@ func TestValidateParameter(t *testing.T) {
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid callable parameter",
|
||||
param: phpParameter{
|
||||
Name: "callbackParam",
|
||||
PhpType: phpCallable,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid nullable callable parameter",
|
||||
param: phpParameter{
|
||||
Name: "nullableCallbackParam",
|
||||
PhpType: "callable",
|
||||
IsNullable: true,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty parameter name",
|
||||
param: phpParameter{
|
||||
@@ -484,6 +548,28 @@ func TestValidateTypes(t *testing.T) {
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid callable parameter",
|
||||
function: phpFunction{
|
||||
Name: "callableFunction",
|
||||
ReturnType: "array",
|
||||
Params: []phpParameter{
|
||||
{Name: "callbackParam", PhpType: phpCallable},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid nullable callable parameter",
|
||||
function: phpFunction{
|
||||
Name: "nullableCallableFunction",
|
||||
ReturnType: "string",
|
||||
Params: []phpParameter{
|
||||
{Name: "callbackParam", PhpType: phpCallable, IsNullable: true},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid object parameter",
|
||||
function: phpFunction{
|
||||
@@ -600,7 +686,7 @@ func TestValidateGoFunctionSignature(t *testing.T) {
|
||||
}`,
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "parameter count mismatch: PHP function has 2 parameters but Go function has 1",
|
||||
errorMsg: "parameter count mismatch: PHP function has 2 parameters (expecting 2 Go parameters) but Go function has 1",
|
||||
},
|
||||
{
|
||||
name: "parameter type mismatch",
|
||||
@@ -702,6 +788,50 @@ func TestValidateGoFunctionSignature(t *testing.T) {
|
||||
},
|
||||
GoFunction: `func mixedFunc(data *C.zend_array, filter *C.zend_string, limit int64) unsafe.Pointer {
|
||||
return nil
|
||||
}`,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid callable parameter",
|
||||
phpFunc: phpFunction{
|
||||
Name: "callableFunc",
|
||||
ReturnType: "array",
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable},
|
||||
},
|
||||
GoFunction: `func callableFunc(callback *C.zval) unsafe.Pointer {
|
||||
return nil
|
||||
}`,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid nullable callable parameter",
|
||||
phpFunc: phpFunction{
|
||||
Name: "nullableCallableFunc",
|
||||
ReturnType: "string",
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, IsNullable: true},
|
||||
},
|
||||
GoFunction: `func nullableCallableFunc(callback *C.zval) unsafe.Pointer {
|
||||
return nil
|
||||
}`,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "mixed callable and other parameters",
|
||||
phpFunc: phpFunction{
|
||||
Name: "mixedCallableFunc",
|
||||
ReturnType: "array",
|
||||
Params: []phpParameter{
|
||||
{Name: "data", PhpType: phpArray},
|
||||
{Name: "callback", PhpType: phpCallable},
|
||||
{Name: "options", PhpType: "int"},
|
||||
},
|
||||
GoFunction: `func mixedCallableFunc(data *C.zend_array, callback *C.zval, options int64) unsafe.Pointer {
|
||||
return nil
|
||||
}`,
|
||||
},
|
||||
expectError: false,
|
||||
@@ -739,6 +869,8 @@ func TestPhpTypeToGoType(t *testing.T) {
|
||||
{"bool", true, "*bool"},
|
||||
{"array", false, "*C.zend_array"},
|
||||
{"array", true, "*C.zend_array"},
|
||||
{"callable", false, "*C.zval"},
|
||||
{"callable", true, "*C.zval"},
|
||||
{"unknown", false, "any"},
|
||||
}
|
||||
|
||||
|
||||
64
testdata/integration/callable.go
vendored
Normal file
64
testdata/integration/callable.go
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package testintegration
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
// export_php:function my_array_map(array $data, callable $callback): array
|
||||
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goArray, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]any, len(goArray))
|
||||
for i, item := range goArray {
|
||||
callResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []any{item})
|
||||
result[i] = callResult
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray[any](result)
|
||||
}
|
||||
|
||||
// export_php:function my_filter(array $data, ?callable $callback): array
|
||||
func my_filter(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goArray, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if callback == nil {
|
||||
return unsafe.Pointer(arr)
|
||||
}
|
||||
|
||||
result := make([]any, 0)
|
||||
for _, item := range goArray {
|
||||
callResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []any{item})
|
||||
if boolResult, ok := callResult.(bool); ok && boolResult {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray[any](result)
|
||||
}
|
||||
|
||||
// export_php:class Processor
|
||||
type Processor struct{}
|
||||
|
||||
// export_php:method Processor::transform(string $input, callable $transformer): string
|
||||
func (p *Processor) Transform(input *C.zend_string, callback *C.zval) unsafe.Pointer {
|
||||
goInput := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
callResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []any{goInput})
|
||||
|
||||
resultStr, ok := callResult.(string)
|
||||
if !ok {
|
||||
return unsafe.Pointer(input)
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(resultStr, false)
|
||||
}
|
||||
10
types.c
10
types.c
@@ -16,6 +16,8 @@ Bucket *get_ht_bucket_data(HashTable *ht, uint32_t index) {
|
||||
|
||||
void *__emalloc__(size_t size) { return emalloc(size); }
|
||||
|
||||
void __efree__(void *ptr) { efree(ptr); }
|
||||
|
||||
void __zend_hash_init__(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor,
|
||||
bool persistent) {
|
||||
zend_hash_init(ht, nSize, NULL, pDestructor, persistent);
|
||||
@@ -36,3 +38,11 @@ void __zval_empty_string__(zval *zv) { ZVAL_EMPTY_STRING(zv); }
|
||||
void __zval_arr__(zval *zv, zend_array *arr) { ZVAL_ARR(zv, arr); }
|
||||
|
||||
zend_array *__zend_new_array__(uint32_t size) { return zend_new_array(size); }
|
||||
|
||||
int __zend_is_callable__(zval *cb) { return zend_is_callable(cb, 0, NULL); }
|
||||
|
||||
int __call_user_function__(zval *function_name, zval *retval,
|
||||
uint32_t param_count, zval params[]) {
|
||||
return call_user_function(CG(function_table), NULL, function_name, retval,
|
||||
param_count, params);
|
||||
}
|
||||
|
||||
85
types.go
85
types.go
@@ -228,12 +228,14 @@ func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer {
|
||||
val := entries[key]
|
||||
zval := phpValue(val)
|
||||
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval)
|
||||
C.__efree__(unsafe.Pointer(zval))
|
||||
}
|
||||
} else {
|
||||
zendArray = createNewArray((uint32)(len(entries)))
|
||||
for key, val := range entries {
|
||||
zval := phpValue(val)
|
||||
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval)
|
||||
C.__efree__(unsafe.Pointer(zval))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +248,7 @@ func PHPPackedArray[T any](slice []T) unsafe.Pointer {
|
||||
for _, val := range slice {
|
||||
zval := phpValue(val)
|
||||
C.zend_hash_next_index_insert(zendArray, zval)
|
||||
C.__efree__(unsafe.Pointer(zval))
|
||||
}
|
||||
|
||||
return unsafe.Pointer(zendArray)
|
||||
@@ -368,42 +371,43 @@ func PHPValue(value any) unsafe.Pointer {
|
||||
}
|
||||
|
||||
func phpValue(value any) *C.zval {
|
||||
var zval C.zval
|
||||
zval := (*C.zval)(C.__emalloc__(C.size_t(unsafe.Sizeof(C.zval{}))))
|
||||
|
||||
if toZvalObj, ok := value.(toZval); ok {
|
||||
toZvalObj.toZval(&zval)
|
||||
return &zval
|
||||
toZvalObj.toZval(zval)
|
||||
return zval
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
C.__zval_null__(&zval)
|
||||
C.__zval_null__(zval)
|
||||
case bool:
|
||||
C.__zval_bool__(&zval, C._Bool(v))
|
||||
C.__zval_bool__(zval, C._Bool(v))
|
||||
case int:
|
||||
C.__zval_long__(&zval, C.zend_long(v))
|
||||
C.__zval_long__(zval, C.zend_long(v))
|
||||
case int64:
|
||||
C.__zval_long__(&zval, C.zend_long(v))
|
||||
C.__zval_long__(zval, C.zend_long(v))
|
||||
case float64:
|
||||
C.__zval_double__(&zval, C.double(v))
|
||||
C.__zval_double__(zval, C.double(v))
|
||||
case string:
|
||||
if v == "" {
|
||||
C.__zval_empty_string__(&zval)
|
||||
C.__zval_empty_string__(zval)
|
||||
break
|
||||
}
|
||||
str := (*C.zend_string)(PHPString(v, false))
|
||||
C.__zval_string__(&zval, str)
|
||||
C.__zval_string__(zval, str)
|
||||
case AssociativeArray[any]:
|
||||
C.__zval_arr__(&zval, (*C.zend_array)(PHPAssociativeArray[any](v)))
|
||||
C.__zval_arr__(zval, (*C.zend_array)(PHPAssociativeArray[any](v)))
|
||||
case map[string]any:
|
||||
C.__zval_arr__(&zval, (*C.zend_array)(PHPMap[any](v)))
|
||||
C.__zval_arr__(zval, (*C.zend_array)(PHPMap[any](v)))
|
||||
case []any:
|
||||
C.__zval_arr__(&zval, (*C.zend_array)(PHPPackedArray[any](v)))
|
||||
C.__zval_arr__(zval, (*C.zend_array)(PHPPackedArray[any](v)))
|
||||
default:
|
||||
C.__efree__(unsafe.Pointer(zval))
|
||||
panic(fmt.Sprintf("unsupported Go type %T", v))
|
||||
}
|
||||
|
||||
return &zval
|
||||
return zval
|
||||
}
|
||||
|
||||
// createNewArray creates a new zend_array with the specified size.
|
||||
@@ -456,3 +460,56 @@ func zendHashDestroy(p unsafe.Pointer) {
|
||||
ht := (*C.zend_array)(p)
|
||||
C.zend_hash_destroy(ht)
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: CallPHPCallable executes a PHP callable with the given parameters.
|
||||
// Returns the result of the callable as a Go interface{}, or nil if the call failed.
|
||||
func CallPHPCallable(cb unsafe.Pointer, params []interface{}) interface{} {
|
||||
if cb == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
callback := (*C.zval)(cb)
|
||||
if callback == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if C.__zend_is_callable__(callback) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
paramCount := len(params)
|
||||
var paramStorage *C.zval
|
||||
if paramCount > 0 {
|
||||
paramStorage = (*C.zval)(C.__emalloc__(C.size_t(paramCount) * C.size_t(unsafe.Sizeof(C.zval{}))))
|
||||
defer func() {
|
||||
for i := 0; i < paramCount; i++ {
|
||||
targetZval := (*C.zval)(unsafe.Pointer(uintptr(unsafe.Pointer(paramStorage)) + uintptr(i)*unsafe.Sizeof(C.zval{})))
|
||||
C.zval_ptr_dtor(targetZval)
|
||||
}
|
||||
C.__efree__(unsafe.Pointer(paramStorage))
|
||||
}()
|
||||
|
||||
for i, param := range params {
|
||||
targetZval := (*C.zval)(unsafe.Pointer(uintptr(unsafe.Pointer(paramStorage)) + uintptr(i)*unsafe.Sizeof(C.zval{})))
|
||||
sourceZval := phpValue(param)
|
||||
*targetZval = *sourceZval
|
||||
C.__efree__(unsafe.Pointer(sourceZval))
|
||||
}
|
||||
}
|
||||
|
||||
var retval C.zval
|
||||
|
||||
result := C.__call_user_function__(callback, &retval, C.uint32_t(paramCount), paramStorage)
|
||||
if result != C.SUCCESS {
|
||||
return nil
|
||||
}
|
||||
|
||||
goResult, err := goValue[any](&retval)
|
||||
C.zval_ptr_dtor(&retval)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return goResult
|
||||
}
|
||||
|
||||
5
types.h
5
types.h
@@ -11,9 +11,14 @@ zval *get_ht_packed_data(HashTable *, uint32_t index);
|
||||
Bucket *get_ht_bucket_data(HashTable *, uint32_t index);
|
||||
|
||||
void *__emalloc__(size_t size);
|
||||
void __efree__(void *ptr);
|
||||
void __zend_hash_init__(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor,
|
||||
bool persistent);
|
||||
|
||||
int __zend_is_callable__(zval *cb);
|
||||
int __call_user_function__(zval *function_name, zval *retval,
|
||||
uint32_t param_count, zval params[]);
|
||||
|
||||
void __zval_null__(zval *zv);
|
||||
void __zval_bool__(zval *zv, bool val);
|
||||
void __zval_long__(zval *zv, zend_long val);
|
||||
|
||||
Reference in New Issue
Block a user