From 3744bf0c1d26d16e6b8686423e0abac1581c5f99 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Mon, 22 Sep 2025 21:37:57 +0200 Subject: [PATCH 01/11] Returns a zend_array to PHP. --- docs/extensions.md | 12 ++++++------ types.go | 39 ++++++++++++++++++++++++++------------- types_test.go | 35 +++++++++++++++++++++-------------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index dff1844e82..4cfb3cd14e 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -126,7 +126,7 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer { // do something with key and value } - // return an ordered array + // return an ordered zend_array // if 'Order' is not empty, only the key-value pairs in 'Order' will be respected return frankenphp.PHPAssociativeArray(AssociativeArray{ Map: map[string]any{ @@ -148,7 +148,7 @@ func process_data_unordered_map(arr *C.zval) unsafe.Pointer { // do something with key and value } - // return an unordered array + // return an unordered zend_array return frankenphp.PHPMap(map[string]any{ "key1": "value1", "key2": "value2", @@ -165,7 +165,7 @@ func process_data_packed(arr *C.zval) unsafe.Pointer { // do something with index and value } - // return a packed array + // return a packed zend_array return frankenphp.PHPackedArray([]any{"value1", "value2", "value3"}) } ``` @@ -180,9 +180,9 @@ func process_data_packed(arr *C.zval) unsafe.Pointer { ##### 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.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP zend_array with key-value pairs +- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP zend_array with key-value pairs +- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed zend_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 diff --git a/types.go b/types.go index e7d290009d..33e5790b85 100644 --- a/types.go +++ b/types.go @@ -164,7 +164,7 @@ func PHPMap(arr map[string]any) unsafe.Pointer { return phpArray(arr, nil) } -// EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zval with a zend_array value +// EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zend_array func PHPAssociativeArray(arr AssociativeArray) unsafe.Pointer { return phpArray(arr.Map, arr.Order) } @@ -187,13 +187,10 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer { } } - var zval C.zval - C.__zval_arr__(&zval, zendArray) - - return unsafe.Pointer(&zval) + return unsafe.Pointer(zendArray) } -// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zval with a zend_array value. +// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zend_array. func PHPPackedArray(slice []any) unsafe.Pointer { zendArray := createNewArray((uint32)(len(slice))) for _, val := range slice { @@ -201,10 +198,7 @@ func PHPPackedArray(slice []any) unsafe.Pointer { C.zend_hash_next_index_insert(zendArray, zval) } - var zval C.zval - C.__zval_arr__(&zval, zendArray) - - return unsafe.Pointer(&zval) + return unsafe.Pointer(zendArray) } // EXPERIMENTAL: GoValue converts a PHP zval to a Go value @@ -278,11 +272,11 @@ func phpValue(value any) *C.zval { str := (*C.zend_string)(PHPString(v, false)) C.__zval_string__(&zval, str) case AssociativeArray: - return (*C.zval)(PHPAssociativeArray(v)) + C.__zval_arr__(&zval, (*C.HashTable)(PHPAssociativeArray(v))) case map[string]any: - return (*C.zval)(PHPAssociativeArray(AssociativeArray{Map: v})) + C.__zval_arr__(&zval, (*C.HashTable)(PHPMap(v))) case []any: - return (*C.zval)(PHPPackedArray(v)) + C.__zval_arr__(&zval, (*C.HashTable)(PHPPackedArray(v))) default: C.__zval_null__(&zval) } @@ -324,3 +318,22 @@ func extractZvalValue(zval *C.zval, expectedType C.uint8_t) unsafe.Pointer { return nil } } + +func zvalPtrDtor(p unsafe.Pointer) { + zv := (*C.zval)(p) + C.zval_ptr_dtor(zv) +} + +func zendStringRelease(p unsafe.Pointer) { + zs := (*C.zend_string)(p) + C.zend_string_release(zs) +} + +func arrayAsZval(arr unsafe.Pointer) unsafe.Pointer { + var zv *C.zval + zv = (*C.zval)(C.__emalloc__(C.size_t(unsafe.Sizeof(C.zval{})))) + ht := (*C.HashTable)(arr) + C.__zval_arr__(zv, ht) + + return unsafe.Pointer(zv) +} diff --git a/types_test.go b/types_test.go index 9499301f6a..cc6c7a9b86 100644 --- a/types_test.go +++ b/types_test.go @@ -28,9 +28,10 @@ func TestGoString(t *testing.T) { testOnDummyPHPThread(t, func() { originalString := "Hello, World!" - convertedString := GoString(PHPString(originalString, false)) + phpString := PHPString(originalString, false) + defer zendStringRelease(phpString) - assert.Equal(t, originalString, convertedString, "string -> zend_string -> string should yield an equal string") + assert.Equal(t, originalString, GoString(phpString), "string -> zend_string -> string should yield an equal string") }) } @@ -41,9 +42,10 @@ func TestPHPMap(t *testing.T) { "foo2": "bar2", } - convertedMap := GoMap(PHPMap(originalMap)) + phpArray := arrayAsZval(PHPMap(originalMap)) + defer zvalPtrDtor(phpArray) - assert.Equal(t, originalMap, convertedMap, "associative array should be equal after conversion") + assert.Equal(t, originalMap, GoMap(phpArray), "associative array should be equal after conversion") }) } @@ -57,9 +59,10 @@ func TestOrderedPHPAssociativeArray(t *testing.T) { Order: []string{"foo2", "foo1"}, } - convertedArray := GoAssociativeArray(PHPAssociativeArray(originalArray)) + phpArray := arrayAsZval(PHPAssociativeArray(originalArray)) + defer zvalPtrDtor(phpArray) - assert.Equal(t, originalArray, convertedArray, "associative array should be equal after conversion") + assert.Equal(t, originalArray, GoAssociativeArray(phpArray), "associative array should be equal after conversion") }) } @@ -67,9 +70,10 @@ func TestPHPPackedArray(t *testing.T) { testOnDummyPHPThread(t, func() { originalSlice := []any{"bar1", "bar2"} - convertedSlice := GoPackedArray(PHPPackedArray(originalSlice)) + phpArray := arrayAsZval(PHPPackedArray(originalSlice)) + defer zvalPtrDtor(phpArray) - assert.Equal(t, originalSlice, convertedSlice, "slice should be equal after conversion") + assert.Equal(t, originalSlice, GoPackedArray(phpArray), "slice should be equal after conversion") }) } @@ -81,9 +85,10 @@ func TestPHPPackedArrayToGoMap(t *testing.T) { "1": "bar2", } - convertedMap := GoMap(PHPPackedArray(originalSlice)) + phpArray := arrayAsZval(PHPPackedArray(originalSlice)) + defer zvalPtrDtor(phpArray) - assert.Equal(t, expectedMap, convertedMap, "convert a packed to an associative array") + assert.Equal(t, expectedMap, GoMap(phpArray), "convert a packed to an associative array") }) } @@ -98,9 +103,10 @@ func TestPHPAssociativeArrayToPacked(t *testing.T) { } expectedSlice := []any{"bar1", "bar2"} - convertedSlice := GoPackedArray(PHPAssociativeArray(originalArray)) + phpArray := arrayAsZval(PHPAssociativeArray(originalArray)) + defer zvalPtrDtor(phpArray) - assert.Equal(t, expectedSlice, convertedSlice, "convert an associative array to a slice") + assert.Equal(t, expectedSlice, GoPackedArray(phpArray), "convert an associative array to a slice") }) } @@ -120,8 +126,9 @@ func TestNestedMixedArray(t *testing.T) { }, } - convertedArray := GoMap(PHPMap(originalArray)) + phpArray := arrayAsZval(PHPMap(originalArray)) + defer zvalPtrDtor(phpArray) - assert.Equal(t, originalArray, convertedArray, "nested mixed array should be equal after conversion") + assert.Equal(t, originalArray, GoMap(phpArray), "nested mixed array should be equal after conversion") }) } From c87e4c935188013322badeb96fbb2b752c04ca02 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Mon, 22 Sep 2025 21:43:37 +0200 Subject: [PATCH 02/11] Adds cgo optimizations. --- types.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/types.go b/types.go index 33e5790b85..3e6bbf14d6 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,22 @@ package frankenphp /* +#cgo nocallback __zend_new_array__ +#cgo nocallback __zval_null__ +#cgo nocallback __zval_bool__ +#cgo nocallback __zval_long__ +#cgo nocallback __zval_double__ +#cgo nocallback __zval_string__ +#cgo nocallback __zval_arr__ +#cgo nocallback __emalloc__ +#cgo noescape __zend_new_array__ +#cgo noescape __zval_null__ +#cgo noescape __zval_bool__ +#cgo noescape __zval_long__ +#cgo noescape __zval_double__ +#cgo noescape __zval_string__ +#cgo noescape __zval_arr__ +#cgo noescape __emalloc__ #include "types.h" */ import "C" From fb9acec3fa4c5136c5fc83547ddb874c4e08fdbb Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Mon, 22 Sep 2025 23:15:36 +0200 Subject: [PATCH 03/11] linting. --- types.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types.go b/types.go index 3e6bbf14d6..9973714423 100644 --- a/types.go +++ b/types.go @@ -346,8 +346,7 @@ func zendStringRelease(p unsafe.Pointer) { } func arrayAsZval(arr unsafe.Pointer) unsafe.Pointer { - var zv *C.zval - zv = (*C.zval)(C.__emalloc__(C.size_t(unsafe.Sizeof(C.zval{})))) + zv := (*C.zval)(C.__emalloc__(C.size_t(unsafe.Sizeof(C.zval{})))) ht := (*C.HashTable)(arr) C.__zval_arr__(zv, ht) From cc6eae4aca753c4fb4c3d809dfe3ee21ded46dd9 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Mon, 29 Sep 2025 16:53:06 +0200 Subject: [PATCH 04/11] Makes go functions also return a hashtable. --- types.go | 36 ++++++++++++------------------------ types_test.go | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/types.go b/types.go index 9973714423..120d533551 100644 --- a/types.go +++ b/types.go @@ -8,7 +8,6 @@ package frankenphp #cgo nocallback __zval_double__ #cgo nocallback __zval_string__ #cgo nocallback __zval_arr__ -#cgo nocallback __emalloc__ #cgo noescape __zend_new_array__ #cgo noescape __zval_null__ #cgo noescape __zval_bool__ @@ -16,7 +15,6 @@ package frankenphp #cgo noescape __zval_double__ #cgo noescape __zval_string__ #cgo noescape __zval_arr__ -#cgo noescape __emalloc__ #include "types.h" */ import "C" @@ -59,13 +57,13 @@ type AssociativeArray struct { Order []string } -// EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray +// EXPERIMENTAL: GoAssociativeArray converts a PHP HashTable to a Go AssociativeArray func GoAssociativeArray(arr unsafe.Pointer) AssociativeArray { entries, order := goArray(arr, true) return AssociativeArray{entries, order} } -// EXPERIMENTAL: GoMap converts a zval having a zend_array value to an unordered Go map +// EXPERIMENTAL: GoMap converts a PHP HashTable to an unordered Go map func GoMap(arr unsafe.Pointer) map[string]any { entries, _ := goArray(arr, false) return entries @@ -76,11 +74,10 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) { panic("received a nil pointer on array conversion") } - zval := (*C.zval)(arr) - hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY)) + hashTable := (*C.HashTable)(arr) if hashTable == nil { - panic("received a *zval that wasn't a HashTable on array conversion") + panic("received a pointer that wasn't a HashTable on array conversion") } nNumUsed := hashTable.nNumUsed @@ -137,17 +134,16 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) { return entries, order } -// EXPERIMENTAL: GoPackedArray converts a zval with a zend_array value to a Go slice +// EXPERIMENTAL: GoPackedArray converts a PHP HashTable 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)(extractZvalValue(zval, C.IS_ARRAY)) + hashTable := (*C.HashTable)(arr) if hashTable == nil { - panic("GoPackedArray received *zval that wasn't a HashTable") + panic("GoPackedArray received a pointer that wasn't a HashTable") } nNumUsed := hashTable.nNumUsed @@ -256,10 +252,10 @@ func goValue(zval *C.zval) any { case C.IS_ARRAY: hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY)) if hashTable != nil && htIsPacked(hashTable) { - return GoPackedArray(unsafe.Pointer(zval)) + return GoPackedArray(unsafe.Pointer(hashTable)) } - return GoAssociativeArray(unsafe.Pointer(zval)) + return GoAssociativeArray(unsafe.Pointer(hashTable)) default: return nil } @@ -335,20 +331,12 @@ func extractZvalValue(zval *C.zval, expectedType C.uint8_t) unsafe.Pointer { } } -func zvalPtrDtor(p unsafe.Pointer) { - zv := (*C.zval)(p) - C.zval_ptr_dtor(zv) -} - func zendStringRelease(p unsafe.Pointer) { zs := (*C.zend_string)(p) C.zend_string_release(zs) } -func arrayAsZval(arr unsafe.Pointer) unsafe.Pointer { - zv := (*C.zval)(C.__emalloc__(C.size_t(unsafe.Sizeof(C.zval{})))) - ht := (*C.HashTable)(arr) - C.__zval_arr__(zv, ht) - - return unsafe.Pointer(zv) +func zendHashDestroy(p unsafe.Pointer) { + ht := (*C.HashTable)(p) + C.zend_hash_destroy(ht) } diff --git a/types_test.go b/types_test.go index cc6c7a9b86..9350ddb02d 100644 --- a/types_test.go +++ b/types_test.go @@ -42,8 +42,8 @@ func TestPHPMap(t *testing.T) { "foo2": "bar2", } - phpArray := arrayAsZval(PHPMap(originalMap)) - defer zvalPtrDtor(phpArray) + phpArray := PHPMap(originalMap) + defer zendHashDestroy(phpArray) assert.Equal(t, originalMap, GoMap(phpArray), "associative array should be equal after conversion") }) @@ -59,8 +59,8 @@ func TestOrderedPHPAssociativeArray(t *testing.T) { Order: []string{"foo2", "foo1"}, } - phpArray := arrayAsZval(PHPAssociativeArray(originalArray)) - defer zvalPtrDtor(phpArray) + phpArray := PHPAssociativeArray(originalArray) + defer zendHashDestroy(phpArray) assert.Equal(t, originalArray, GoAssociativeArray(phpArray), "associative array should be equal after conversion") }) @@ -70,8 +70,8 @@ func TestPHPPackedArray(t *testing.T) { testOnDummyPHPThread(t, func() { originalSlice := []any{"bar1", "bar2"} - phpArray := arrayAsZval(PHPPackedArray(originalSlice)) - defer zvalPtrDtor(phpArray) + phpArray := PHPPackedArray(originalSlice) + defer zendHashDestroy(phpArray) assert.Equal(t, originalSlice, GoPackedArray(phpArray), "slice should be equal after conversion") }) @@ -85,8 +85,8 @@ func TestPHPPackedArrayToGoMap(t *testing.T) { "1": "bar2", } - phpArray := arrayAsZval(PHPPackedArray(originalSlice)) - defer zvalPtrDtor(phpArray) + phpArray := PHPPackedArray(originalSlice) + defer zendHashDestroy(phpArray) assert.Equal(t, expectedMap, GoMap(phpArray), "convert a packed to an associative array") }) @@ -103,8 +103,8 @@ func TestPHPAssociativeArrayToPacked(t *testing.T) { } expectedSlice := []any{"bar1", "bar2"} - phpArray := arrayAsZval(PHPAssociativeArray(originalArray)) - defer zvalPtrDtor(phpArray) + phpArray := PHPAssociativeArray(originalArray) + defer zendHashDestroy(phpArray) assert.Equal(t, expectedSlice, GoPackedArray(phpArray), "convert an associative array to a slice") }) @@ -126,8 +126,8 @@ func TestNestedMixedArray(t *testing.T) { }, } - phpArray := arrayAsZval(PHPMap(originalArray)) - defer zvalPtrDtor(phpArray) + phpArray := PHPMap(originalArray) + defer zendHashDestroy(phpArray) assert.Equal(t, originalArray, GoMap(phpArray), "nested mixed array should be equal after conversion") }) From acfbe5160a8f9bc329e3521f9705d22bcdb4fe2e Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Mon, 29 Sep 2025 17:00:37 +0200 Subject: [PATCH 05/11] Adjusts types. --- docs/extensions.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index 4cfb3cd14e..b3ce7cbfe3 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -116,7 +116,7 @@ If order or association are not needed, it's also possible to directly convert t ```go // export_php:function process_data_ordered(array $input): array -func process_data_ordered_map(arr *C.zval) unsafe.Pointer { +func process_data_ordered_map(arr *C.HashTable) unsafe.Pointer { // Convert PHP associative array to Go while keeping the order associativeArray := frankenphp.GoAssociativeArray(unsafe.Pointer(arr)) @@ -126,7 +126,7 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer { // do something with key and value } - // return an ordered zend_array + // return an ordered HashTable // if 'Order' is not empty, only the key-value pairs in 'Order' will be respected return frankenphp.PHPAssociativeArray(AssociativeArray{ Map: map[string]any{ @@ -138,7 +138,7 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer { } // export_php:function process_data_unordered(array $input): array -func process_data_unordered_map(arr *C.zval) unsafe.Pointer { +func process_data_unordered_map(arr *C.HashTable) unsafe.Pointer { // Convert PHP associative array to a Go map without keeping the order // ignoring the order will be more performant goMap := frankenphp.GoMap(unsafe.Pointer(arr)) @@ -148,7 +148,7 @@ func process_data_unordered_map(arr *C.zval) unsafe.Pointer { // do something with key and value } - // return an unordered zend_array + // return an unordered HashTable return frankenphp.PHPMap(map[string]any{ "key1": "value1", "key2": "value2", @@ -156,7 +156,7 @@ func process_data_unordered_map(arr *C.zval) unsafe.Pointer { } // export_php:function process_data_packed(array $input): array -func process_data_packed(arr *C.zval) unsafe.Pointer { +func process_data_packed(arr *C.HashTable) unsafe.Pointer { // Convert PHP packed array to Go goSlice := frankenphp.GoPackedArray(unsafe.Pointer(arr), false) @@ -165,7 +165,7 @@ func process_data_packed(arr *C.zval) unsafe.Pointer { // do something with index and value } - // return a packed zend_array + // return a packed HashTable return frankenphp.PHPackedArray([]any{"value1", "value2", "value3"}) } ``` @@ -180,9 +180,9 @@ func process_data_packed(arr *C.zval) unsafe.Pointer { ##### Available methods: Packed and Associative -- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP zend_array with key-value pairs -- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP zend_array with key-value pairs -- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed zend_array with indexed values only +- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP HashTable with key-value pairs +- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP HashTable with key-value pairs +- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed HashTable 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 From 0bc8de417559258ebab3a005de40d01ae1f3e5b8 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Tue, 7 Oct 2025 19:33:10 +0200 Subject: [PATCH 06/11] Applies git diff by @alexandre-daubois. --- internal/extgen/templates/extension.c.tpl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/extgen/templates/extension.c.tpl b/internal/extgen/templates/extension.c.tpl index 6ae0b3474c..fdd61a1828 100644 --- a/internal/extgen/templates/extension.c.tpl +++ b/internal/extgen/templates/extension.c.tpl @@ -113,19 +113,19 @@ 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}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{else}}{{.Name}}{{end}}{{end}}{{end}}{{end}}); RETURN_STR(result); {{- 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}}{{end}}{{else}}{{if eq .PhpType "array"}}{{.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}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{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}}{{end}}{{else}}{{if eq .PhpType "array"}}{{.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}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{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}}{{end}}{{else}}{{if eq .PhpType "array"}}{{.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}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{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}}{{end}}{{else}}{{.Name}}{{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}} ? Z_ARRVAL_P({{.Name}}) : NULL{{end}}{{else}}{{if eq .PhpType "array"}}Z_ARRVAL_P({{.Name}}){{else}}{{.Name}}{{end}}{{end}}{{end}}{{end}}); if (result != NULL) { HashTable *ht = (HashTable*)result; RETURN_ARR(ht); @@ -134,7 +134,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}}{{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}}{{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}} ? 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}}); {{- end}} } {{end}}{{end}} From bb3291156425048b69e0d038cb1609c0c5a7b8f0 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sat, 11 Oct 2025 22:29:35 +0200 Subject: [PATCH 07/11] Changes naming to zend_array. --- docs/extensions.md | 18 ++++++------- types.c | 2 ++ types.go | 66 ++++++++++++++++++++++++---------------------- types.h | 1 + 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index b3ce7cbfe3..e583bdb336 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -116,7 +116,7 @@ If order or association are not needed, it's also possible to directly convert t ```go // export_php:function process_data_ordered(array $input): array -func process_data_ordered_map(arr *C.HashTable) unsafe.Pointer { +func process_data_ordered_map(arr *C.zend_array) unsafe.Pointer { // Convert PHP associative array to Go while keeping the order associativeArray := frankenphp.GoAssociativeArray(unsafe.Pointer(arr)) @@ -126,7 +126,7 @@ func process_data_ordered_map(arr *C.HashTable) unsafe.Pointer { // do something with key and value } - // return an ordered HashTable + // return an ordered zend_array // if 'Order' is not empty, only the key-value pairs in 'Order' will be respected return frankenphp.PHPAssociativeArray(AssociativeArray{ Map: map[string]any{ @@ -138,7 +138,7 @@ func process_data_ordered_map(arr *C.HashTable) unsafe.Pointer { } // export_php:function process_data_unordered(array $input): array -func process_data_unordered_map(arr *C.HashTable) unsafe.Pointer { +func process_data_unordered_map(arr *C.zend_array) unsafe.Pointer { // Convert PHP associative array to a Go map without keeping the order // ignoring the order will be more performant goMap := frankenphp.GoMap(unsafe.Pointer(arr)) @@ -148,7 +148,7 @@ func process_data_unordered_map(arr *C.HashTable) unsafe.Pointer { // do something with key and value } - // return an unordered HashTable + // return an unordered zend_array return frankenphp.PHPMap(map[string]any{ "key1": "value1", "key2": "value2", @@ -156,7 +156,7 @@ func process_data_unordered_map(arr *C.HashTable) unsafe.Pointer { } // export_php:function process_data_packed(array $input): array -func process_data_packed(arr *C.HashTable) unsafe.Pointer { +func process_data_packed(arr *C.zend_array) unsafe.Pointer { // Convert PHP packed array to Go goSlice := frankenphp.GoPackedArray(unsafe.Pointer(arr), false) @@ -165,7 +165,7 @@ func process_data_packed(arr *C.HashTable) unsafe.Pointer { // do something with index and value } - // return a packed HashTable + // return a packed zend_array return frankenphp.PHPackedArray([]any{"value1", "value2", "value3"}) } ``` @@ -180,9 +180,9 @@ func process_data_packed(arr *C.HashTable) unsafe.Pointer { ##### Available methods: Packed and Associative -- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP HashTable with key-value pairs -- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP HashTable with key-value pairs -- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed HashTable with indexed values only +- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP zend_array with key-value pairs +- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP zend_array with key-value pairs +- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed zend_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 diff --git a/types.c b/types.c index 9c4887b237..ce3835fb38 100644 --- a/types.c +++ b/types.c @@ -31,6 +31,8 @@ void __zval_double__(zval *zv, double val) { ZVAL_DOUBLE(zv, val); } void __zval_string__(zval *zv, zend_string *str) { ZVAL_STR(zv, str); } +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); } diff --git a/types.go b/types.go index 120d533551..6d9899cd87 100644 --- a/types.go +++ b/types.go @@ -57,13 +57,13 @@ type AssociativeArray struct { Order []string } -// EXPERIMENTAL: GoAssociativeArray converts a PHP HashTable to a Go AssociativeArray +// EXPERIMENTAL: GoAssociativeArray converts a PHP zend_array to a Go AssociativeArray func GoAssociativeArray(arr unsafe.Pointer) AssociativeArray { entries, order := goArray(arr, true) return AssociativeArray{entries, order} } -// EXPERIMENTAL: GoMap converts a PHP HashTable to an unordered Go map +// EXPERIMENTAL: GoMap converts a PHP zend_array to an unordered Go map func GoMap(arr unsafe.Pointer) map[string]any { entries, _ := goArray(arr, false) return entries @@ -74,25 +74,25 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) { panic("received a nil pointer on array conversion") } - hashTable := (*C.HashTable)(arr) + array := (*C.zend_array)(arr) - if hashTable == nil { - panic("received a pointer that wasn't a HashTable on array conversion") + if array == nil { + panic("received a pointer that wasn't a zend_array on array conversion") } - nNumUsed := hashTable.nNumUsed + nNumUsed := array.nNumUsed entries := make(map[string]any, nNumUsed) var order []string if ordered { order = make([]string, 0, nNumUsed) } - if htIsPacked(hashTable) { - // if the HashTable is packed, convert all integer keys to strings + if htIsPacked(array) { + // if the array is packed, convert all integer keys to strings // this is probably a bug by the dev using this function // still, we'll (inefficiently) convert to an associative array for i := C.uint32_t(0); i < nNumUsed; i++ { - v := C.get_ht_packed_data(hashTable, i) + v := C.get_ht_packed_data(array, i) if v != nil && C.zval_get_type(v) != C.IS_UNDEF { strIndex := strconv.Itoa(int(i)) entries[strIndex] = goValue(v) @@ -106,7 +106,7 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) { } for i := C.uint32_t(0); i < nNumUsed; i++ { - bucket := C.get_ht_bucket_data(hashTable, i) + bucket := C.get_ht_bucket_data(array, i) if bucket == nil || C.zval_get_type(&bucket.val) == C.IS_UNDEF { continue } @@ -134,24 +134,24 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) { return entries, order } -// EXPERIMENTAL: GoPackedArray converts a PHP HashTable to a Go slice +// EXPERIMENTAL: GoPackedArray converts a PHP zend_array to a Go slice func GoPackedArray(arr unsafe.Pointer) []any { if arr == nil { panic("GoPackedArray received a nil pointer") } - hashTable := (*C.HashTable)(arr) + array := (*C.zend_array)(arr) - if hashTable == nil { - panic("GoPackedArray received a pointer that wasn't a HashTable") + if array == nil { + panic("GoPackedArray received a pointer that wasn't a zrnd_array") } - nNumUsed := hashTable.nNumUsed + nNumUsed := array.nNumUsed result := make([]any, 0, nNumUsed) - if htIsPacked(hashTable) { + if htIsPacked(array) { for i := C.uint32_t(0); i < nNumUsed; i++ { - v := C.get_ht_packed_data(hashTable, i) + v := C.get_ht_packed_data(array, i) if v != nil && C.zval_get_type(v) != C.IS_UNDEF { result = append(result, goValue(v)) } @@ -162,7 +162,7 @@ func GoPackedArray(arr unsafe.Pointer) []any { // fallback if ht isn't packed - equivalent to array_values() for i := C.uint32_t(0); i < nNumUsed; i++ { - bucket := C.get_ht_bucket_data(hashTable, i) + bucket := C.get_ht_bucket_data(array, i) if bucket != nil && C.zval_get_type(&bucket.val) != C.IS_UNDEF { result = append(result, goValue(&bucket.val)) } @@ -182,7 +182,7 @@ func PHPAssociativeArray(arr AssociativeArray) unsafe.Pointer { } func phpArray(entries map[string]any, order []string) unsafe.Pointer { - var zendArray *C.HashTable + var zendArray *C.zend_array if len(order) != 0 { zendArray = createNewArray((uint32)(len(order))) @@ -250,12 +250,12 @@ func goValue(zval *C.zval) any { return GoString(unsafe.Pointer(str)) case C.IS_ARRAY: - hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY)) - if hashTable != nil && htIsPacked(hashTable) { - return GoPackedArray(unsafe.Pointer(hashTable)) + array := (*C.zend_array)(extractZvalValue(zval, C.IS_ARRAY)) + if array != nil && htIsPacked(array) { + return GoPackedArray(unsafe.Pointer(array)) } - return GoAssociativeArray(unsafe.Pointer(hashTable)) + return GoAssociativeArray(unsafe.Pointer(array)) default: return nil } @@ -281,14 +281,18 @@ func phpValue(value any) *C.zval { case float64: C.__zval_double__(&zval, C.double(v)) case string: + if v == "" { + C.__zval_empty_string__(&zval) + break + } str := (*C.zend_string)(PHPString(v, false)) C.__zval_string__(&zval, str) case AssociativeArray: - C.__zval_arr__(&zval, (*C.HashTable)(PHPAssociativeArray(v))) + C.__zval_arr__(&zval, (*C.zend_array)(PHPAssociativeArray(v))) case map[string]any: - C.__zval_arr__(&zval, (*C.HashTable)(PHPMap(v))) + C.__zval_arr__(&zval, (*C.zend_array)(PHPMap(v))) case []any: - C.__zval_arr__(&zval, (*C.HashTable)(PHPPackedArray(v))) + C.__zval_arr__(&zval, (*C.zend_array)(PHPPackedArray(v))) default: C.__zval_null__(&zval) } @@ -297,13 +301,13 @@ func phpValue(value any) *C.zval { } // createNewArray creates a new zend_array with the specified size. -func createNewArray(size uint32) *C.HashTable { +func createNewArray(size uint32) *C.zend_array { arr := C.__zend_new_array__(C.uint32_t(size)) - return (*C.HashTable)(unsafe.Pointer(arr)) + return (*C.zend_array)(unsafe.Pointer(arr)) } -// htIsPacked checks if a HashTable is a list (packed) or hashmap (not packed). -func htIsPacked(ht *C.HashTable) bool { +// htIsPacked checks if a zend_array is a list (packed) or hashmap (not packed). +func htIsPacked(ht *C.zend_array) bool { flags := *(*C.uint32_t)(unsafe.Pointer(&ht.u[0])) return (flags & C.HASH_FLAG_PACKED) != 0 @@ -337,6 +341,6 @@ func zendStringRelease(p unsafe.Pointer) { } func zendHashDestroy(p unsafe.Pointer) { - ht := (*C.HashTable)(p) + ht := (*C.zend_array)(p) C.zend_hash_destroy(ht) } diff --git a/types.h b/types.h index 853d23ca8b..e667a6f7e4 100644 --- a/types.h +++ b/types.h @@ -19,6 +19,7 @@ void __zval_bool__(zval *zv, bool val); void __zval_long__(zval *zv, zend_long val); void __zval_double__(zval *zv, double val); void __zval_string__(zval *zv, zend_string *str); +void __zval_empty_string__(zval *zv); void __zval_arr__(zval *zv, zend_array *arr); zend_array *__zend_new_array__(uint32_t size); From 5f1bd59a5317fb7dbe9aaf59dd4433ddf293f1e9 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Mon, 13 Oct 2025 22:38:25 +0200 Subject: [PATCH 08/11] Suggestions by @dunglas. --- docs/extensions.md | 12 ++++++------ types.go | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index e583bdb336..0914ddc921 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -126,7 +126,7 @@ func process_data_ordered_map(arr *C.zend_array) unsafe.Pointer { // do something with key and value } - // return an ordered zend_array + // return an ordered array // if 'Order' is not empty, only the key-value pairs in 'Order' will be respected return frankenphp.PHPAssociativeArray(AssociativeArray{ Map: map[string]any{ @@ -148,7 +148,7 @@ func process_data_unordered_map(arr *C.zend_array) unsafe.Pointer { // do something with key and value } - // return an unordered zend_array + // return an unordered array return frankenphp.PHPMap(map[string]any{ "key1": "value1", "key2": "value2", @@ -165,7 +165,7 @@ func process_data_packed(arr *C.zend_array) unsafe.Pointer { // do something with index and value } - // return a packed zend_array + // return a packed array return frankenphp.PHPackedArray([]any{"value1", "value2", "value3"}) } ``` @@ -180,9 +180,9 @@ func process_data_packed(arr *C.zend_array) unsafe.Pointer { ##### Available methods: Packed and Associative -- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP zend_array with key-value pairs -- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP zend_array with key-value pairs -- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed zend_array with indexed values only +- `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 diff --git a/types.go b/types.go index 6d9899cd87..5e196c21fe 100644 --- a/types.go +++ b/types.go @@ -57,13 +57,13 @@ type AssociativeArray struct { Order []string } -// EXPERIMENTAL: GoAssociativeArray converts a PHP zend_array to a Go AssociativeArray +// EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray func GoAssociativeArray(arr unsafe.Pointer) AssociativeArray { entries, order := goArray(arr, true) return AssociativeArray{entries, order} } -// EXPERIMENTAL: GoMap converts a PHP zend_array to an unordered Go map +// EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map func GoMap(arr unsafe.Pointer) map[string]any { entries, _ := goArray(arr, false) return entries @@ -134,7 +134,7 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) { return entries, order } -// EXPERIMENTAL: GoPackedArray converts a PHP zend_array to a Go slice +// EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice func GoPackedArray(arr unsafe.Pointer) []any { if arr == nil { panic("GoPackedArray received a nil pointer") @@ -143,7 +143,7 @@ func GoPackedArray(arr unsafe.Pointer) []any { array := (*C.zend_array)(arr) if array == nil { - panic("GoPackedArray received a pointer that wasn't a zrnd_array") + panic("GoPackedArray received a pointer that wasn't a zend_array") } nNumUsed := array.nNumUsed @@ -171,12 +171,12 @@ func GoPackedArray(arr unsafe.Pointer) []any { return result } -// EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array +// EXPERIMENTAL: PHPMap converts an unordered Go map to a 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 zend_array func PHPAssociativeArray(arr AssociativeArray) unsafe.Pointer { return phpArray(arr.Map, arr.Order) } @@ -202,7 +202,7 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer { return unsafe.Pointer(zendArray) } -// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zend_array. +// EXPERIMENTAL: PHPPackedArray converts a Go slice to a zend_array. func PHPPackedArray(slice []any) unsafe.Pointer { zendArray := createNewArray((uint32)(len(slice))) for _, val := range slice { From a5125f5aa8460b68e54cfcbea2ce7cb7a6e5fe16 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Fri, 24 Oct 2025 11:09:22 +0200 Subject: [PATCH 09/11] Fixes toZval case. --- types.go | 15 +++++---------- types_test.go | 8 ++++---- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/types.go b/types.go index 18413e030f..2e79d6da16 100644 --- a/types.go +++ b/types.go @@ -27,7 +27,7 @@ import ( ) type toZval interface { - toZval() *C.zval + toZval(*C.zval) } // EXPERIMENTAL: GoString copies a zend_string to a Go string. @@ -64,8 +64,8 @@ type AssociativeArray[T any] struct { Order []string } -func (a AssociativeArray[T]) toZval() *C.zval { - return (*C.zval)(PHPAssociativeArray[T](a)) +func (a AssociativeArray[T]) toZval(zval *C.zval) { + C.__zval_arr__(zval, (*C.zend_array)(PHPAssociativeArray[T](a))) } // EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray @@ -232,7 +232,6 @@ func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer { } else { zendArray = createNewArray((uint32)(len(entries))) for key, val := range entries { - fmt.Println("adding key", key, "val", val) zval := phpValue(val) C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval) } @@ -372,12 +371,10 @@ func phpValue(value any) *C.zval { var zval C.zval if toZvalObj, ok := value.(toZval); ok { - fmt.Println("wtf") - return toZvalObj.toZval() + toZvalObj.toZval(&zval) + return &zval } - fmt.Println("type", reflect.TypeOf(value)) - switch v := value.(type) { case nil: C.__zval_null__(&zval) @@ -397,10 +394,8 @@ func phpValue(value any) *C.zval { str := (*C.zend_string)(PHPString(v, false)) C.__zval_string__(&zval, str) case AssociativeArray[any]: - fmt.Println("associative arr") C.__zval_arr__(&zval, (*C.zend_array)(PHPAssociativeArray[any](v))) case map[string]any: - fmt.Println("map arr") C.__zval_arr__(&zval, (*C.zend_array)(PHPMap[any](v))) case []any: C.__zval_arr__(&zval, (*C.zend_array)(PHPPackedArray[any](v))) diff --git a/types_test.go b/types_test.go index 25a101ab2e..67fc43982f 100644 --- a/types_test.go +++ b/types_test.go @@ -76,7 +76,7 @@ func TestPHPPackedArray(t *testing.T) { originalSlice := []string{"bar1", "bar2"} phpArray := PHPPackedArray(originalSlice) - defer zendHashDestroy(phpArray) + defer zendHashDestroy(phpArray) convertedSlice, err := GoPackedArray[string](phpArray) require.NoError(t, err) @@ -93,7 +93,7 @@ func TestPHPPackedArrayToGoMap(t *testing.T) { } phpArray := PHPPackedArray(originalSlice) - defer zendHashDestroy(phpArray) + defer zendHashDestroy(phpArray) convertedMap, err := GoMap[string](phpArray) require.NoError(t, err) @@ -115,7 +115,7 @@ func TestPHPAssociativeArrayToPacked(t *testing.T) { phpArray := PHPAssociativeArray(originalArray) defer zendHashDestroy(phpArray) convertedSlice, err := GoPackedArray[string](phpArray) - require.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedSlice, convertedSlice, "convert an associative array to a slice") }) @@ -138,7 +138,7 @@ func TestNestedMixedArray(t *testing.T) { } phpArray := PHPMap(originalArray) - defer zendHashDestroy(phpArray) + defer zendHashDestroy(phpArray) convertedArray, err := GoMap[any](phpArray) require.NoError(t, err) From 16c1168465b2bb80ec87bc9f61906d37860d635a Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 16 Nov 2025 14:28:56 +0100 Subject: [PATCH 10/11] Adds correct array validation. --- internal/extgen/validator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/extgen/validator.go b/internal/extgen/validator.go index 44fd138023..4d9b8e3fa7 100644 --- a/internal/extgen/validator.go +++ b/internal/extgen/validator.go @@ -203,7 +203,9 @@ func (v *Validator) phpTypeToGoType(t phpType, isNullable bool) string { baseType = "float64" case phpBool: baseType = "bool" - case phpArray, phpMixed: + case phpArray: + baseType = "*C.zend_array" + case phpMixed: baseType = "*C.zval" default: baseType = "any" From 19582888ef8b02f972b2fc13849a684c4a27c6f2 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 16 Nov 2025 14:41:28 +0100 Subject: [PATCH 11/11] Fixes tests. --- internal/extgen/validator_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/extgen/validator_test.go b/internal/extgen/validator_test.go index bfd232a118..df004f967a 100644 --- a/internal/extgen/validator_test.go +++ b/internal/extgen/validator_test.go @@ -669,7 +669,7 @@ func TestValidateGoFunctionSignature(t *testing.T) { Params: []phpParameter{ {Name: "items", PhpType: phpArray}, }, - GoFunction: `func arrayFunc(items *C.zval) unsafe.Pointer { + GoFunction: `func arrayFunc(items *C.zend_array) unsafe.Pointer { return nil }`, }, @@ -684,7 +684,7 @@ func TestValidateGoFunctionSignature(t *testing.T) { {Name: "items", PhpType: phpArray, IsNullable: true}, {Name: "name", PhpType: phpString}, }, - GoFunction: `func nullableArrayFunc(items *C.zval, name *C.zend_string) unsafe.Pointer { + GoFunction: `func nullableArrayFunc(items *C.zend_array, name *C.zend_string) unsafe.Pointer { return nil }`, }, @@ -700,7 +700,7 @@ func TestValidateGoFunctionSignature(t *testing.T) { {Name: "filter", PhpType: phpString}, {Name: "limit", PhpType: phpInt}, }, - GoFunction: `func mixedFunc(data *C.zval, filter *C.zend_string, limit int64) unsafe.Pointer { + GoFunction: `func mixedFunc(data *C.zend_array, filter *C.zend_string, limit int64) unsafe.Pointer { return nil }`, }, @@ -737,8 +737,8 @@ func TestPhpTypeToGoType(t *testing.T) { {"float", true, "*float64"}, {"bool", false, "bool"}, {"bool", true, "*bool"}, - {"array", false, "*C.zval"}, - {"array", true, "*C.zval"}, + {"array", false, "*C.zend_array"}, + {"array", true, "*C.zend_array"}, {"unknown", false, "any"}, }