diff --git a/docs/extensions.md b/docs/extensions.md index dadaa6d12..f12e4c0f3 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -132,7 +132,7 @@ import ( ) // 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.zend_array) unsafe.Pointer { // Convert PHP associative array to Go while keeping the order associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr)) if err != nil { @@ -157,7 +157,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.zend_array) unsafe.Pointer { // Convert PHP associative array to a Go map without keeping the order // ignoring the order will be more performant goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr)) @@ -178,7 +178,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.zend_array) unsafe.Pointer { // Convert PHP packed array to Go goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr), false) if err != nil { diff --git a/internal/extgen/templates/extension.c.tpl b/internal/extgen/templates/extension.c.tpl index 0dd4608e1..b641145b3 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}} diff --git a/types.c b/types.c index 9c4887b23..ce3835fb3 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 4e4bbbbed..2e79d6da1 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,20 @@ 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 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__ #include "types.h" */ import "C" @@ -13,7 +27,7 @@ import ( ) type toZval interface { - toZval() *C.zval + toZval(*C.zval) } // EXPERIMENTAL: GoString copies a zend_string to a Go string. @@ -50,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 @@ -61,7 +75,7 @@ func GoAssociativeArray[T any](arr unsafe.Pointer) (AssociativeArray[T], error) return AssociativeArray[T]{entries, order}, err } -// EXPERIMENTAL: GoMap converts a zval having a zend_array value to an unordered Go map +// EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map func GoMap[T any](arr unsafe.Pointer) (map[string]T, error) { entries, _, err := goArray[T](arr, false) @@ -73,27 +87,25 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e return nil, nil, errors.New("received a nil pointer on array conversion") } - zval := (*C.zval)(arr) - v, err := extractZvalValue(zval, C.IS_ARRAY) - if err != nil { - return nil, nil, fmt.Errorf("received a *zval that wasn't a HashTable on array conversion: %w", err) - } + array := (*C.zend_array)(arr) - hashTable := (*C.HashTable)(v) + if array == nil { + return nil, nil, fmt.Errorf("received a *zval that wasn't a HashTable on array conversion") + } - nNumUsed := hashTable.nNumUsed + nNumUsed := array.nNumUsed entries := make(map[string]T, 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)) e, err := goValue[T](v) @@ -114,7 +126,7 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e var zeroVal T 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 } @@ -150,26 +162,24 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e return entries, order, nil } -// EXPERIMENTAL: GoPackedArray converts a zval with a zend_array value to a Go slice +// EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) { if arr == nil { return nil, errors.New("GoPackedArray received a nil value") } - zval := (*C.zval)(arr) - v, err := extractZvalValue(zval, C.IS_ARRAY) - if err != nil { - return nil, fmt.Errorf("GoPackedArray received *zval that wasn't a HashTable: %w", err) - } + array := (*C.zend_array)(arr) - hashTable := (*C.HashTable)(v) + if array == nil { + return nil, fmt.Errorf("GoPackedArray received *zval that wasn't a HashTable") + } - nNumUsed := hashTable.nNumUsed + nNumUsed := array.nNumUsed result := make([]T, 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 { v, err := goValue[T](v) if err != nil { @@ -185,7 +195,7 @@ func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) { // 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 { v, err := goValue[T](&bucket.val) if err != nil { @@ -199,18 +209,18 @@ func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) { return result, nil } -// 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[T any](arr map[string]T) unsafe.Pointer { return phpArray[T](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 zend_array func PHPAssociativeArray[T any](arr AssociativeArray[T]) unsafe.Pointer { return phpArray[T](arr.Map, arr.Order) } func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer { - var zendArray *C.HashTable + var zendArray *C.zend_array if len(order) != 0 { zendArray = createNewArray((uint32)(len(order))) @@ -227,10 +237,7 @@ func phpArray[T any](entries map[string]T, 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. @@ -241,10 +248,7 @@ func PHPPackedArray[T any](slice []T) 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 @@ -316,11 +320,11 @@ func goValue[T any](zval *C.zval) (res T, err error) { return resZero, err } - hashTable := (*C.HashTable)(v) - if hashTable != nil && htIsPacked(hashTable) { + array := (*C.zend_array)(v) + if array != nil && htIsPacked(array) { typ := reflect.TypeOf(res) if typ == nil || typ.Kind() == reflect.Interface && typ.NumMethod() == 0 { - r, e := GoPackedArray[any](unsafe.Pointer(zval)) + r, e := GoPackedArray[any](unsafe.Pointer(array)) if e != nil { return resZero, e } @@ -333,7 +337,7 @@ func goValue[T any](zval *C.zval) (res T, err error) { return resZero, fmt.Errorf("cannot convert packed array to non-any Go type %s", typ.String()) } - a, err := GoAssociativeArray[T](unsafe.Pointer(zval)) + a, err := GoAssociativeArray[T](unsafe.Pointer(array)) if err != nil { return resZero, err } @@ -367,7 +371,8 @@ func phpValue(value any) *C.zval { var zval C.zval if toZvalObj, ok := value.(toZval); ok { - return toZvalObj.toZval() + toZvalObj.toZval(&zval) + return &zval } switch v := value.(type) { @@ -382,12 +387,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[any]: + C.__zval_arr__(&zval, (*C.zend_array)(PHPAssociativeArray[any](v))) case map[string]any: - return (*C.zval)(PHPAssociativeArray[any](AssociativeArray[any]{Map: v})) + C.__zval_arr__(&zval, (*C.zend_array)(PHPMap[any](v))) case []any: - return (*C.zval)(PHPPackedArray(v)) + C.__zval_arr__(&zval, (*C.zend_array)(PHPPackedArray[any](v))) default: panic(fmt.Sprintf("unsupported Go type %T", v)) } @@ -396,13 +407,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 @@ -435,3 +446,13 @@ func extractZvalValue(zval *C.zval, expectedType C.uint8_t) (unsafe.Pointer, err return nil, fmt.Errorf("unsupported zval type %d", expectedType) } + +func zendStringRelease(p unsafe.Pointer) { + zs := (*C.zend_string)(p) + C.zend_string_release(zs) +} + +func zendHashDestroy(p unsafe.Pointer) { + ht := (*C.zend_array)(p) + C.zend_hash_destroy(ht) +} diff --git a/types.h b/types.h index c82f479d4..72442cf30 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); diff --git a/types_test.go b/types_test.go index 122fe930d..67fc43982 100644 --- a/types_test.go +++ b/types_test.go @@ -29,9 +29,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") }) } @@ -42,7 +43,9 @@ func TestPHPMap(t *testing.T) { "foo2": "bar2", } - convertedMap, err := GoMap[string](PHPMap(originalMap)) + phpArray := PHPMap(originalMap) + defer zendHashDestroy(phpArray) + convertedMap, err := GoMap[string](phpArray) require.NoError(t, err) assert.Equal(t, originalMap, convertedMap, "associative array should be equal after conversion") @@ -59,7 +62,9 @@ func TestOrderedPHPAssociativeArray(t *testing.T) { Order: []string{"foo2", "foo1"}, } - convertedArray, err := GoAssociativeArray[string](PHPAssociativeArray(originalArray)) + phpArray := PHPAssociativeArray(originalArray) + defer zendHashDestroy(phpArray) + convertedArray, err := GoAssociativeArray[string](phpArray) require.NoError(t, err) assert.Equal(t, originalArray, convertedArray, "associative array should be equal after conversion") @@ -70,7 +75,9 @@ func TestPHPPackedArray(t *testing.T) { testOnDummyPHPThread(t, func() { originalSlice := []string{"bar1", "bar2"} - convertedSlice, err := GoPackedArray[string](PHPPackedArray(originalSlice)) + phpArray := PHPPackedArray(originalSlice) + defer zendHashDestroy(phpArray) + convertedSlice, err := GoPackedArray[string](phpArray) require.NoError(t, err) assert.Equal(t, originalSlice, convertedSlice, "slice should be equal after conversion") @@ -85,7 +92,9 @@ func TestPHPPackedArrayToGoMap(t *testing.T) { "1": "bar2", } - convertedMap, err := GoMap[string](PHPPackedArray(originalSlice)) + phpArray := PHPPackedArray(originalSlice) + defer zendHashDestroy(phpArray) + convertedMap, err := GoMap[string](phpArray) require.NoError(t, err) assert.Equal(t, expectedMap, convertedMap, "convert a packed to an associative array") @@ -103,7 +112,9 @@ func TestPHPAssociativeArrayToPacked(t *testing.T) { } expectedSlice := []string{"bar1", "bar2"} - convertedSlice, err := GoPackedArray[string](PHPAssociativeArray(originalArray)) + phpArray := PHPAssociativeArray(originalArray) + defer zendHashDestroy(phpArray) + convertedSlice, err := GoPackedArray[string](phpArray) require.NoError(t, err) assert.Equal(t, expectedSlice, convertedSlice, "convert an associative array to a slice") @@ -126,7 +137,9 @@ func TestNestedMixedArray(t *testing.T) { }, } - convertedArray, err := GoMap[any](PHPMap(originalArray)) + phpArray := PHPMap(originalArray) + defer zendHashDestroy(phpArray) + convertedArray, err := GoMap[any](phpArray) require.NoError(t, err) assert.Equal(t, originalArray, convertedArray, "nested mixed array should be equal after conversion")