11package frankenphp
22
33/*
4+ #cgo nocallback __zend_new_array__
5+ #cgo nocallback __zval_null__
6+ #cgo nocallback __zval_bool__
7+ #cgo nocallback __zval_long__
8+ #cgo nocallback __zval_double__
9+ #cgo nocallback __zval_string__
10+ #cgo nocallback __zval_arr__
11+ #cgo noescape __zend_new_array__
12+ #cgo noescape __zval_null__
13+ #cgo noescape __zval_bool__
14+ #cgo noescape __zval_long__
15+ #cgo noescape __zval_double__
16+ #cgo noescape __zval_string__
17+ #cgo noescape __zval_arr__
418#include "types.h"
519*/
620import "C"
@@ -13,7 +27,7 @@ import (
1327)
1428
1529type toZval interface {
16- toZval () * C.zval
30+ toZval (* C.zval )
1731}
1832
1933// EXPERIMENTAL: GoString copies a zend_string to a Go string.
@@ -50,8 +64,8 @@ type AssociativeArray[T any] struct {
5064 Order []string
5165}
5266
53- func (a AssociativeArray [T ]) toZval () * C.zval {
54- return (* C .zval )(PHPAssociativeArray [T ](a ))
67+ func (a AssociativeArray [T ]) toZval (zval * C.zval ) {
68+ C . __zval_arr__ ( zval , (* C .zend_array )(PHPAssociativeArray [T ](a ) ))
5569}
5670
5771// EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray
@@ -61,7 +75,7 @@ func GoAssociativeArray[T any](arr unsafe.Pointer) (AssociativeArray[T], error)
6175 return AssociativeArray [T ]{entries , order }, err
6276}
6377
64- // EXPERIMENTAL: GoMap converts a zval having a zend_array value to an unordered Go map
78+ // EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map
6579func GoMap [T any ](arr unsafe.Pointer ) (map [string ]T , error ) {
6680 entries , _ , err := goArray [T ](arr , false )
6781
@@ -73,27 +87,25 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e
7387 return nil , nil , errors .New ("received a nil pointer on array conversion" )
7488 }
7589
76- zval := (* C .zval )(arr )
77- v , err := extractZvalValue (zval , C .IS_ARRAY )
78- if err != nil {
79- return nil , nil , fmt .Errorf ("received a *zval that wasn't a HashTable on array conversion: %w" , err )
80- }
90+ array := (* C .zend_array )(arr )
8191
82- hashTable := (* C .HashTable )(v )
92+ if array == nil {
93+ return nil , nil , fmt .Errorf ("received a *zval that wasn't a HashTable on array conversion" )
94+ }
8395
84- nNumUsed := hashTable .nNumUsed
96+ nNumUsed := array .nNumUsed
8597 entries := make (map [string ]T , nNumUsed )
8698 var order []string
8799 if ordered {
88100 order = make ([]string , 0 , nNumUsed )
89101 }
90102
91- if htIsPacked (hashTable ) {
92- // if the HashTable is packed, convert all integer keys to strings
103+ if htIsPacked (array ) {
104+ // if the array is packed, convert all integer keys to strings
93105 // this is probably a bug by the dev using this function
94106 // still, we'll (inefficiently) convert to an associative array
95107 for i := C .uint32_t (0 ); i < nNumUsed ; i ++ {
96- v := C .get_ht_packed_data (hashTable , i )
108+ v := C .get_ht_packed_data (array , i )
97109 if v != nil && C .zval_get_type (v ) != C .IS_UNDEF {
98110 strIndex := strconv .Itoa (int (i ))
99111 e , err := goValue [T ](v )
@@ -114,7 +126,7 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e
114126 var zeroVal T
115127
116128 for i := C .uint32_t (0 ); i < nNumUsed ; i ++ {
117- bucket := C .get_ht_bucket_data (hashTable , i )
129+ bucket := C .get_ht_bucket_data (array , i )
118130 if bucket == nil || C .zval_get_type (& bucket .val ) == C .IS_UNDEF {
119131 continue
120132 }
@@ -150,26 +162,24 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e
150162 return entries , order , nil
151163}
152164
153- // EXPERIMENTAL: GoPackedArray converts a zval with a zend_array value to a Go slice
165+ // EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice
154166func GoPackedArray [T any ](arr unsafe.Pointer ) ([]T , error ) {
155167 if arr == nil {
156168 return nil , errors .New ("GoPackedArray received a nil value" )
157169 }
158170
159- zval := (* C .zval )(arr )
160- v , err := extractZvalValue (zval , C .IS_ARRAY )
161- if err != nil {
162- return nil , fmt .Errorf ("GoPackedArray received *zval that wasn't a HashTable: %w" , err )
163- }
171+ array := (* C .zend_array )(arr )
164172
165- hashTable := (* C .HashTable )(v )
173+ if array == nil {
174+ return nil , fmt .Errorf ("GoPackedArray received *zval that wasn't a HashTable" )
175+ }
166176
167- nNumUsed := hashTable .nNumUsed
177+ nNumUsed := array .nNumUsed
168178 result := make ([]T , 0 , nNumUsed )
169179
170- if htIsPacked (hashTable ) {
180+ if htIsPacked (array ) {
171181 for i := C .uint32_t (0 ); i < nNumUsed ; i ++ {
172- v := C .get_ht_packed_data (hashTable , i )
182+ v := C .get_ht_packed_data (array , i )
173183 if v != nil && C .zval_get_type (v ) != C .IS_UNDEF {
174184 v , err := goValue [T ](v )
175185 if err != nil {
@@ -185,7 +195,7 @@ func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) {
185195
186196 // fallback if ht isn't packed - equivalent to array_values()
187197 for i := C .uint32_t (0 ); i < nNumUsed ; i ++ {
188- bucket := C .get_ht_bucket_data (hashTable , i )
198+ bucket := C .get_ht_bucket_data (array , i )
189199 if bucket != nil && C .zval_get_type (& bucket .val ) != C .IS_UNDEF {
190200 v , err := goValue [T ](& bucket .val )
191201 if err != nil {
@@ -199,18 +209,18 @@ func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) {
199209 return result , nil
200210}
201211
202- // EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array
212+ // EXPERIMENTAL: PHPMap converts an unordered Go map to a zend_array
203213func PHPMap [T any ](arr map [string ]T ) unsafe.Pointer {
204214 return phpArray [T ](arr , nil )
205215}
206216
207- // EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zval with a zend_array value
217+ // EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a zend_array
208218func PHPAssociativeArray [T any ](arr AssociativeArray [T ]) unsafe.Pointer {
209219 return phpArray [T ](arr .Map , arr .Order )
210220}
211221
212222func phpArray [T any ](entries map [string ]T , order []string ) unsafe.Pointer {
213- var zendArray * C.HashTable
223+ var zendArray * C.zend_array
214224
215225 if len (order ) != 0 {
216226 zendArray = createNewArray ((uint32 )(len (order )))
@@ -227,10 +237,7 @@ func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer {
227237 }
228238 }
229239
230- var zval C.zval
231- C .__zval_arr__ (& zval , zendArray )
232-
233- return unsafe .Pointer (& zval )
240+ return unsafe .Pointer (zendArray )
234241}
235242
236243// 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 {
241248 C .zend_hash_next_index_insert (zendArray , zval )
242249 }
243250
244- var zval C.zval
245- C .__zval_arr__ (& zval , zendArray )
246-
247- return unsafe .Pointer (& zval )
251+ return unsafe .Pointer (zendArray )
248252}
249253
250254// 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) {
316320 return resZero , err
317321 }
318322
319- hashTable := (* C .HashTable )(v )
320- if hashTable != nil && htIsPacked (hashTable ) {
323+ array := (* C .zend_array )(v )
324+ if array != nil && htIsPacked (array ) {
321325 typ := reflect .TypeOf (res )
322326 if typ == nil || typ .Kind () == reflect .Interface && typ .NumMethod () == 0 {
323- r , e := GoPackedArray [any ](unsafe .Pointer (zval ))
327+ r , e := GoPackedArray [any ](unsafe .Pointer (array ))
324328 if e != nil {
325329 return resZero , e
326330 }
@@ -333,7 +337,7 @@ func goValue[T any](zval *C.zval) (res T, err error) {
333337 return resZero , fmt .Errorf ("cannot convert packed array to non-any Go type %s" , typ .String ())
334338 }
335339
336- a , err := GoAssociativeArray [T ](unsafe .Pointer (zval ))
340+ a , err := GoAssociativeArray [T ](unsafe .Pointer (array ))
337341 if err != nil {
338342 return resZero , err
339343 }
@@ -367,7 +371,8 @@ func phpValue(value any) *C.zval {
367371 var zval C.zval
368372
369373 if toZvalObj , ok := value .(toZval ); ok {
370- return toZvalObj .toZval ()
374+ toZvalObj .toZval (& zval )
375+ return & zval
371376 }
372377
373378 switch v := value .(type ) {
@@ -382,12 +387,18 @@ func phpValue(value any) *C.zval {
382387 case float64 :
383388 C .__zval_double__ (& zval , C .double (v ))
384389 case string :
390+ if v == "" {
391+ C .__zval_empty_string__ (& zval )
392+ break
393+ }
385394 str := (* C .zend_string )(PHPString (v , false ))
386395 C .__zval_string__ (& zval , str )
396+ case AssociativeArray [any ]:
397+ C .__zval_arr__ (& zval , (* C .zend_array )(PHPAssociativeArray [any ](v )))
387398 case map [string ]any :
388- return (* C .zval )( PHPAssociativeArray [any ](AssociativeArray [ any ]{ Map : v } ))
399+ C . __zval_arr__ ( & zval , (* C .zend_array )( PHPMap [any ](v ) ))
389400 case []any :
390- return (* C .zval )(PHPPackedArray ( v ))
401+ C . __zval_arr__ ( & zval , (* C .zend_array )(PHPPackedArray [ any ]( v ) ))
391402 default :
392403 panic (fmt .Sprintf ("unsupported Go type %T" , v ))
393404 }
@@ -396,13 +407,13 @@ func phpValue(value any) *C.zval {
396407}
397408
398409// createNewArray creates a new zend_array with the specified size.
399- func createNewArray (size uint32 ) * C.HashTable {
410+ func createNewArray (size uint32 ) * C.zend_array {
400411 arr := C .__zend_new_array__ (C .uint32_t (size ))
401- return (* C .HashTable )(unsafe .Pointer (arr ))
412+ return (* C .zend_array )(unsafe .Pointer (arr ))
402413}
403414
404- // htIsPacked checks if a HashTable is a list (packed) or hashmap (not packed).
405- func htIsPacked (ht * C.HashTable ) bool {
415+ // htIsPacked checks if a zend_array is a list (packed) or hashmap (not packed).
416+ func htIsPacked (ht * C.zend_array ) bool {
406417 flags := * (* C .uint32_t )(unsafe .Pointer (& ht .u [0 ]))
407418
408419 return (flags & C .HASH_FLAG_PACKED ) != 0
@@ -435,3 +446,13 @@ func extractZvalValue(zval *C.zval, expectedType C.uint8_t) (unsafe.Pointer, err
435446
436447 return nil , fmt .Errorf ("unsupported zval type %d" , expectedType )
437448}
449+
450+ func zendStringRelease (p unsafe.Pointer ) {
451+ zs := (* C .zend_string )(p )
452+ C .zend_string_release (zs )
453+ }
454+
455+ func zendHashDestroy (p unsafe.Pointer ) {
456+ ht := (* C .zend_array )(p )
457+ C .zend_hash_destroy (ht )
458+ }
0 commit comments