mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
feat: allow creating strongly typed slices and maps from PHP values with type utilities (#1933)
* feat: use generics in type functions for better type support * various improvements * better docs * update docs
This commit is contained in:
@@ -53,6 +53,7 @@ package example
|
||||
import "C"
|
||||
import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
@@ -133,7 +134,10 @@ import (
|
||||
// export_php:function process_data_ordered(array $input): array
|
||||
func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
// Convert PHP associative array to Go while keeping the order
|
||||
associativeArray := frankenphp.GoAssociativeArray(unsafe.Pointer(arr))
|
||||
associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// loop over the entries in order
|
||||
for _, key := range associativeArray.Order {
|
||||
@@ -143,8 +147,8 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
// return an ordered array
|
||||
// if 'Order' is not empty, only the key-value pairs in 'Order' will be respected
|
||||
return frankenphp.PHPAssociativeArray(frankenphp.AssociativeArray{
|
||||
Map: map[string]any{
|
||||
return frankenphp.PHPAssociativeArray[string](frankenphp.AssociativeArray[string]{
|
||||
Map: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
@@ -156,7 +160,10 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
func process_data_unordered_map(arr *C.zval) 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))
|
||||
goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// loop over the entries in no specific order
|
||||
for key, value := range goMap {
|
||||
@@ -164,7 +171,7 @@ func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// return an unordered array
|
||||
return frankenphp.PHPMap(map[string]any{
|
||||
return frankenphp.PHPMap(map[string]string {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
})
|
||||
@@ -173,7 +180,10 @@ 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 {
|
||||
// Convert PHP packed array to Go
|
||||
goSlice := frankenphp.GoPackedArray(unsafe.Pointer(arr), false)
|
||||
goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr), false)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// loop over the slice in order
|
||||
for index, value := range goSlice {
|
||||
@@ -181,7 +191,7 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// return a packed array
|
||||
return frankenphp.PHPackedArray([]any{"value1", "value2", "value3"})
|
||||
return frankenphp.PHPPackedArray([]string{"value1", "value2", "value3"})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package frankenphp
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -245,7 +246,12 @@ func go_frankenphp_finish_worker_request(threadIndex C.uintptr_t, retval *C.zval
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.getRequestContext()
|
||||
if retval != nil {
|
||||
fc.handlerReturn = GoValue(unsafe.Pointer(retval))
|
||||
r, err := GoValue[any](unsafe.Pointer(retval))
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("cannot convert return value: %s", err))
|
||||
}
|
||||
|
||||
fc.handlerReturn = r
|
||||
}
|
||||
|
||||
fc.closeContext()
|
||||
|
||||
260
types.go
260
types.go
@@ -5,11 +5,17 @@ package frankenphp
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type toZval interface {
|
||||
toZval() *C.zval
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: GoString copies a zend_string to a Go string.
|
||||
func GoString(s unsafe.Pointer) string {
|
||||
if s == nil {
|
||||
@@ -39,37 +45,44 @@ func PHPString(s string, persistent bool) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// AssociativeArray represents a PHP array with ordered key-value pairs
|
||||
type AssociativeArray struct {
|
||||
Map map[string]any
|
||||
type AssociativeArray[T any] struct {
|
||||
Map map[string]T
|
||||
Order []string
|
||||
}
|
||||
|
||||
func (a AssociativeArray[T]) toZval() *C.zval {
|
||||
return (*C.zval)(PHPAssociativeArray[T](a))
|
||||
}
|
||||
|
||||
// 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}
|
||||
func GoAssociativeArray[T any](arr unsafe.Pointer) (AssociativeArray[T], error) {
|
||||
entries, order, err := goArray[T](arr, true)
|
||||
|
||||
return AssociativeArray[T]{entries, order}, err
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: GoMap converts a zval having a zend_array value to an unordered Go map
|
||||
func GoMap(arr unsafe.Pointer) map[string]any {
|
||||
entries, _ := goArray(arr, false)
|
||||
return entries
|
||||
func GoMap[T any](arr unsafe.Pointer) (map[string]T, error) {
|
||||
entries, _, err := goArray[T](arr, false)
|
||||
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, error) {
|
||||
if arr == nil {
|
||||
panic("received a nil pointer on array conversion")
|
||||
return nil, nil, errors.New("received a nil pointer on array conversion")
|
||||
}
|
||||
|
||||
zval := (*C.zval)(arr)
|
||||
hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY))
|
||||
|
||||
if hashTable == nil {
|
||||
panic("received a *zval that wasn't a HashTable on array conversion")
|
||||
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)
|
||||
}
|
||||
|
||||
hashTable := (*C.HashTable)(v)
|
||||
|
||||
nNumUsed := hashTable.nNumUsed
|
||||
entries := make(map[string]any, nNumUsed)
|
||||
entries := make(map[string]T, nNumUsed)
|
||||
var order []string
|
||||
if ordered {
|
||||
order = make([]string, 0, nNumUsed)
|
||||
@@ -83,27 +96,42 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
v := C.get_ht_packed_data(hashTable, i)
|
||||
if v != nil && C.zval_get_type(v) != C.IS_UNDEF {
|
||||
strIndex := strconv.Itoa(int(i))
|
||||
entries[strIndex] = goValue(v)
|
||||
e, err := goValue[T](v)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
entries[strIndex] = e
|
||||
if ordered {
|
||||
order = append(order, strIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries, order
|
||||
return entries, order, nil
|
||||
}
|
||||
|
||||
var zeroVal T
|
||||
|
||||
for i := C.uint32_t(0); i < nNumUsed; i++ {
|
||||
bucket := C.get_ht_bucket_data(hashTable, i)
|
||||
if bucket == nil || C.zval_get_type(&bucket.val) == C.IS_UNDEF {
|
||||
continue
|
||||
}
|
||||
|
||||
v := goValue(&bucket.val)
|
||||
v, err := goValue[any](&bucket.val)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if bucket.key != nil {
|
||||
keyStr := GoString(unsafe.Pointer(bucket.key))
|
||||
entries[keyStr] = v
|
||||
if v == nil {
|
||||
entries[keyStr] = zeroVal
|
||||
} else {
|
||||
entries[keyStr] = v.(T)
|
||||
}
|
||||
|
||||
if ordered {
|
||||
order = append(order, keyStr)
|
||||
}
|
||||
@@ -113,64 +141,75 @@ func goArray(arr unsafe.Pointer, ordered bool) (map[string]any, []string) {
|
||||
|
||||
// as fallback convert the bucket index to a string key
|
||||
strIndex := strconv.Itoa(int(bucket.h))
|
||||
entries[strIndex] = v
|
||||
entries[strIndex] = v.(T)
|
||||
if ordered {
|
||||
order = append(order, strIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return entries, order
|
||||
return entries, order, nil
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: GoPackedArray converts a zval with a zend_array value to a Go slice
|
||||
func GoPackedArray(arr unsafe.Pointer) []any {
|
||||
func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) {
|
||||
if arr == nil {
|
||||
panic("GoPackedArray received a nil pointer")
|
||||
return nil, errors.New("GoPackedArray received a nil value")
|
||||
}
|
||||
|
||||
zval := (*C.zval)(arr)
|
||||
hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY))
|
||||
|
||||
if hashTable == nil {
|
||||
panic("GoPackedArray received *zval that wasn't a HashTable")
|
||||
v, err := extractZvalValue(zval, C.IS_ARRAY)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GoPackedArray received *zval that wasn't a HashTable: %w", err)
|
||||
}
|
||||
|
||||
hashTable := (*C.HashTable)(v)
|
||||
|
||||
nNumUsed := hashTable.nNumUsed
|
||||
result := make([]any, 0, nNumUsed)
|
||||
result := make([]T, 0, nNumUsed)
|
||||
|
||||
if htIsPacked(hashTable) {
|
||||
for i := C.uint32_t(0); i < nNumUsed; i++ {
|
||||
v := C.get_ht_packed_data(hashTable, i)
|
||||
if v != nil && C.zval_get_type(v) != C.IS_UNDEF {
|
||||
result = append(result, goValue(v))
|
||||
v, err := goValue[T](v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
if bucket != nil && C.zval_get_type(&bucket.val) != C.IS_UNDEF {
|
||||
result = append(result, goValue(&bucket.val))
|
||||
v, err := goValue[T](&bucket.val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array
|
||||
func PHPMap(arr map[string]any) unsafe.Pointer {
|
||||
return phpArray(arr, nil)
|
||||
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
|
||||
func PHPAssociativeArray(arr AssociativeArray) unsafe.Pointer {
|
||||
return phpArray(arr.Map, arr.Order)
|
||||
func PHPAssociativeArray[T any](arr AssociativeArray[T]) unsafe.Pointer {
|
||||
return phpArray[T](arr.Map, arr.Order)
|
||||
}
|
||||
|
||||
func phpArray(entries map[string]any, order []string) unsafe.Pointer {
|
||||
func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer {
|
||||
var zendArray *C.HashTable
|
||||
|
||||
if len(order) != 0 {
|
||||
@@ -195,7 +234,7 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zval with a zend_array value.
|
||||
func PHPPackedArray(slice []any) unsafe.Pointer {
|
||||
func PHPPackedArray[T any](slice []T) unsafe.Pointer {
|
||||
zendArray := createNewArray((uint32)(len(slice)))
|
||||
for _, val := range slice {
|
||||
zval := phpValue(val)
|
||||
@@ -209,54 +248,117 @@ func PHPPackedArray(slice []any) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: GoValue converts a PHP zval to a Go value
|
||||
func GoValue(zval unsafe.Pointer) any {
|
||||
return goValue((*C.zval)(zval))
|
||||
//
|
||||
// Zval having the null, bool, long, double, string and array types are currently supported.
|
||||
// Arrays can curently only be converted to any[] and AssociativeArray[any].
|
||||
// Any other type will cause an error.
|
||||
// More types may be supported in the future.
|
||||
func GoValue[T any](zval unsafe.Pointer) (T, error) {
|
||||
return goValue[T]((*C.zval)(zval))
|
||||
}
|
||||
|
||||
func goValue(zval *C.zval) any {
|
||||
func goValue[T any](zval *C.zval) (res T, err error) {
|
||||
var (
|
||||
resAny any
|
||||
resZero T
|
||||
)
|
||||
t := C.zval_get_type(zval)
|
||||
|
||||
switch t {
|
||||
case C.IS_NULL:
|
||||
return nil
|
||||
resAny = any(nil)
|
||||
case C.IS_FALSE:
|
||||
return false
|
||||
resAny = any(false)
|
||||
case C.IS_TRUE:
|
||||
return true
|
||||
resAny = any(true)
|
||||
case C.IS_LONG:
|
||||
longPtr := (*C.zend_long)(extractZvalValue(zval, C.IS_LONG))
|
||||
if longPtr != nil {
|
||||
return int64(*longPtr)
|
||||
v, err := extractZvalValue(zval, C.IS_LONG)
|
||||
if err != nil {
|
||||
return resZero, err
|
||||
}
|
||||
|
||||
return int64(0)
|
||||
if v != nil {
|
||||
resAny = any(int64(*(*C.zend_long)(v)))
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
resAny = any(int64(0))
|
||||
case C.IS_DOUBLE:
|
||||
doublePtr := (*C.double)(extractZvalValue(zval, C.IS_DOUBLE))
|
||||
if doublePtr != nil {
|
||||
return float64(*doublePtr)
|
||||
v, err := extractZvalValue(zval, C.IS_DOUBLE)
|
||||
if err != nil {
|
||||
return resZero, err
|
||||
}
|
||||
|
||||
return float64(0)
|
||||
if v != nil {
|
||||
resAny = any(float64(*(*C.double)(v)))
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
resAny = any(float64(0))
|
||||
case C.IS_STRING:
|
||||
str := (*C.zend_string)(extractZvalValue(zval, C.IS_STRING))
|
||||
if str == nil {
|
||||
return ""
|
||||
v, err := extractZvalValue(zval, C.IS_STRING)
|
||||
if err != nil {
|
||||
return resZero, err
|
||||
}
|
||||
|
||||
return GoString(unsafe.Pointer(str))
|
||||
if v == nil {
|
||||
resAny = any("")
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
resAny = any(GoString(v))
|
||||
case C.IS_ARRAY:
|
||||
hashTable := (*C.HashTable)(extractZvalValue(zval, C.IS_ARRAY))
|
||||
if hashTable != nil && htIsPacked(hashTable) {
|
||||
return GoPackedArray(unsafe.Pointer(zval))
|
||||
v, err := extractZvalValue(zval, C.IS_ARRAY)
|
||||
if err != nil {
|
||||
return resZero, err
|
||||
}
|
||||
|
||||
return GoAssociativeArray(unsafe.Pointer(zval))
|
||||
hashTable := (*C.HashTable)(v)
|
||||
if hashTable != nil && htIsPacked(hashTable) {
|
||||
typ := reflect.TypeOf(res)
|
||||
if typ == nil || typ.Kind() == reflect.Interface && typ.NumMethod() == 0 {
|
||||
r, e := GoPackedArray[any](unsafe.Pointer(zval))
|
||||
if e != nil {
|
||||
return resZero, e
|
||||
}
|
||||
|
||||
resAny = any(r)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return resZero, fmt.Errorf("cannot convert packed array to non-any Go type %s", typ.String())
|
||||
}
|
||||
|
||||
a, err := GoAssociativeArray[T](unsafe.Pointer(zval))
|
||||
if err != nil {
|
||||
return resZero, err
|
||||
}
|
||||
|
||||
resAny = any(a)
|
||||
default:
|
||||
return nil
|
||||
return resZero, fmt.Errorf("unsupported zval type %d", t)
|
||||
}
|
||||
|
||||
if resAny == nil {
|
||||
return resZero, nil
|
||||
}
|
||||
|
||||
if castRes, ok := resAny.(T); ok {
|
||||
return castRes, nil
|
||||
}
|
||||
|
||||
return resZero, fmt.Errorf("cannot cast value of type %T to type %T", resAny, res)
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: PHPValue converts a Go any to a PHP zval
|
||||
//
|
||||
// nil, bool, int, int64, float64, string, []any, and map[string]any are currently supported.
|
||||
// Any other type will cause a panic.
|
||||
// More types may be supported in the future.
|
||||
func PHPValue(value any) unsafe.Pointer {
|
||||
return unsafe.Pointer(phpValue(value))
|
||||
}
|
||||
@@ -264,6 +366,10 @@ func PHPValue(value any) unsafe.Pointer {
|
||||
func phpValue(value any) *C.zval {
|
||||
var zval C.zval
|
||||
|
||||
if toZvalObj, ok := value.(toZval); ok {
|
||||
return toZvalObj.toZval()
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
C.__zval_null__(&zval)
|
||||
@@ -278,10 +384,8 @@ func phpValue(value any) *C.zval {
|
||||
case string:
|
||||
str := (*C.zend_string)(PHPString(v, false))
|
||||
C.__zval_string__(&zval, str)
|
||||
case AssociativeArray:
|
||||
return (*C.zval)(PHPAssociativeArray(v))
|
||||
case map[string]any:
|
||||
return (*C.zval)(PHPAssociativeArray(AssociativeArray{Map: v}))
|
||||
return (*C.zval)(PHPAssociativeArray[any](AssociativeArray[any]{Map: v}))
|
||||
case []any:
|
||||
return (*C.zval)(PHPPackedArray(v))
|
||||
default:
|
||||
@@ -305,23 +409,29 @@ func htIsPacked(ht *C.HashTable) bool {
|
||||
}
|
||||
|
||||
// extractZvalValue returns a pointer to the zval value cast to the expected type
|
||||
func extractZvalValue(zval *C.zval, expectedType C.uint8_t) unsafe.Pointer {
|
||||
if zval == nil || C.zval_get_type(zval) != expectedType {
|
||||
return nil
|
||||
func extractZvalValue(zval *C.zval, expectedType C.uint8_t) (unsafe.Pointer, error) {
|
||||
if zval == nil {
|
||||
if expectedType == C.IS_NULL {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("zval type mismatch: expected %d, got nil", expectedType)
|
||||
}
|
||||
|
||||
if zType := C.zval_get_type(zval); zType != expectedType {
|
||||
return nil, fmt.Errorf("zval type mismatch: expected %d, got %d", expectedType, zType)
|
||||
}
|
||||
|
||||
v := unsafe.Pointer(&zval.value[0])
|
||||
|
||||
switch expectedType {
|
||||
case C.IS_LONG:
|
||||
return v
|
||||
case C.IS_DOUBLE:
|
||||
return v
|
||||
case C.IS_LONG, C.IS_DOUBLE:
|
||||
return v, nil
|
||||
case C.IS_STRING:
|
||||
return unsafe.Pointer(*(**C.zend_string)(v))
|
||||
return unsafe.Pointer(*(**C.zend_string)(v)), nil
|
||||
case C.IS_ARRAY:
|
||||
return unsafe.Pointer(*(**C.zend_array)(v))
|
||||
default:
|
||||
return nil
|
||||
return unsafe.Pointer(*(**C.zend_array)(v)), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported zval type %d", expectedType)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// execute the function on a PHP thread directly
|
||||
@@ -36,12 +37,13 @@ func TestGoString(t *testing.T) {
|
||||
|
||||
func TestPHPMap(t *testing.T) {
|
||||
testOnDummyPHPThread(t, func() {
|
||||
originalMap := map[string]any{
|
||||
originalMap := map[string]string{
|
||||
"foo1": "bar1",
|
||||
"foo2": "bar2",
|
||||
}
|
||||
|
||||
convertedMap := GoMap(PHPMap(originalMap))
|
||||
convertedMap, err := GoMap[string](PHPMap(originalMap))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, originalMap, convertedMap, "associative array should be equal after conversion")
|
||||
})
|
||||
@@ -49,15 +51,16 @@ func TestPHPMap(t *testing.T) {
|
||||
|
||||
func TestOrderedPHPAssociativeArray(t *testing.T) {
|
||||
testOnDummyPHPThread(t, func() {
|
||||
originalArray := AssociativeArray{
|
||||
Map: map[string]any{
|
||||
originalArray := AssociativeArray[string]{
|
||||
Map: map[string]string{
|
||||
"foo1": "bar1",
|
||||
"foo2": "bar2",
|
||||
},
|
||||
Order: []string{"foo2", "foo1"},
|
||||
}
|
||||
|
||||
convertedArray := GoAssociativeArray(PHPAssociativeArray(originalArray))
|
||||
convertedArray, err := GoAssociativeArray[string](PHPAssociativeArray(originalArray))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, originalArray, convertedArray, "associative array should be equal after conversion")
|
||||
})
|
||||
@@ -65,9 +68,10 @@ func TestOrderedPHPAssociativeArray(t *testing.T) {
|
||||
|
||||
func TestPHPPackedArray(t *testing.T) {
|
||||
testOnDummyPHPThread(t, func() {
|
||||
originalSlice := []any{"bar1", "bar2"}
|
||||
originalSlice := []string{"bar1", "bar2"}
|
||||
|
||||
convertedSlice := GoPackedArray(PHPPackedArray(originalSlice))
|
||||
convertedSlice, err := GoPackedArray[string](PHPPackedArray(originalSlice))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, originalSlice, convertedSlice, "slice should be equal after conversion")
|
||||
})
|
||||
@@ -75,13 +79,14 @@ func TestPHPPackedArray(t *testing.T) {
|
||||
|
||||
func TestPHPPackedArrayToGoMap(t *testing.T) {
|
||||
testOnDummyPHPThread(t, func() {
|
||||
originalSlice := []any{"bar1", "bar2"}
|
||||
expectedMap := map[string]any{
|
||||
originalSlice := []string{"bar1", "bar2"}
|
||||
expectedMap := map[string]string{
|
||||
"0": "bar1",
|
||||
"1": "bar2",
|
||||
}
|
||||
|
||||
convertedMap := GoMap(PHPPackedArray(originalSlice))
|
||||
convertedMap, err := GoMap[string](PHPPackedArray(originalSlice))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedMap, convertedMap, "convert a packed to an associative array")
|
||||
})
|
||||
@@ -89,16 +94,17 @@ func TestPHPPackedArrayToGoMap(t *testing.T) {
|
||||
|
||||
func TestPHPAssociativeArrayToPacked(t *testing.T) {
|
||||
testOnDummyPHPThread(t, func() {
|
||||
originalArray := AssociativeArray{
|
||||
Map: map[string]any{
|
||||
originalArray := AssociativeArray[string]{
|
||||
Map: map[string]string{
|
||||
"foo1": "bar1",
|
||||
"foo2": "bar2",
|
||||
},
|
||||
Order: []string{"foo1", "foo2"},
|
||||
}
|
||||
expectedSlice := []any{"bar1", "bar2"}
|
||||
expectedSlice := []string{"bar1", "bar2"}
|
||||
|
||||
convertedSlice := GoPackedArray(PHPAssociativeArray(originalArray))
|
||||
convertedSlice, err := GoPackedArray[string](PHPAssociativeArray(originalArray))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedSlice, convertedSlice, "convert an associative array to a slice")
|
||||
})
|
||||
@@ -109,18 +115,19 @@ func TestNestedMixedArray(t *testing.T) {
|
||||
originalArray := map[string]any{
|
||||
"string": "value",
|
||||
"int": int64(123),
|
||||
"float": float64(1.2),
|
||||
"float": 1.2,
|
||||
"true": true,
|
||||
"false": false,
|
||||
"nil": nil,
|
||||
"packedArray": []any{"bar1", "bar2"},
|
||||
"associativeArray": AssociativeArray{
|
||||
"associativeArray": AssociativeArray[any]{
|
||||
Map: map[string]any{"foo1": "bar1", "foo2": "bar2"},
|
||||
Order: []string{"foo2", "foo1"},
|
||||
},
|
||||
}
|
||||
|
||||
convertedArray := GoMap(PHPMap(originalArray))
|
||||
convertedArray, err := GoMap[any](PHPMap(originalArray))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, originalArray, convertedArray, "nested mixed array should be equal after conversion")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user