-
Notifications
You must be signed in to change notification settings - Fork 105
/
value.go
283 lines (237 loc) · 7.01 KB
/
value.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// Copyright 2017 Alexander Palaistras. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
package php
// #cgo CFLAGS: -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM
// #cgo CFLAGS: -I/usr/include/php/Zend -Iinclude
//
// #include <stdlib.h>
// #include <stdbool.h>
// #include <main/php.h>
// #include "value.h"
import "C"
import (
"fmt"
"reflect"
"strconv"
"unsafe"
)
// ValueKind represents the specific kind of type represented in Value.
type ValueKind int
// PHP types representable in Go.
const (
Null ValueKind = iota
Long
Double
Bool
String
Array
Map
Object
)
// Value represents a PHP value.
type Value struct {
value *C.struct__engine_value
}
// NewValue creates a PHP value representation of a Go value val. Available
// bindings for Go to PHP types are:
//
// int -> integer
// float64 -> double
// bool -> boolean
// string -> string
// slice -> indexed array
// map[int|string] -> associative array
// struct -> object
//
// It is only possible to bind maps with integer or string keys. Only exported
// struct fields are passed to the PHP context. Bindings for functions and method
// receivers to PHP functions and classes are only available in the engine scope,
// and must be predeclared before context execution.
func NewValue(val interface{}) (*Value, error) {
ptr, err := C.value_new()
if err != nil {
return nil, fmt.Errorf("Unable to instantiate PHP value")
}
v := reflect.ValueOf(val)
// Determine interface value type and create PHP value from the concrete type.
switch v.Kind() {
// Bind integer to PHP int type.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
C.value_set_long(ptr, C.long(v.Int()))
// Bind floating point number to PHP double type.
case reflect.Float32, reflect.Float64:
C.value_set_double(ptr, C.double(v.Float()))
// Bind boolean to PHP bool type.
case reflect.Bool:
C.value_set_bool(ptr, C.bool(v.Bool()))
// Bind string to PHP string type.
case reflect.String:
str := C.CString(v.String())
defer C.free(unsafe.Pointer(str))
C.value_set_string(ptr, str)
// Bind slice to PHP indexed array type.
case reflect.Slice:
C.value_set_array(ptr, C.uint(v.Len()))
for i := 0; i < v.Len(); i++ {
vs, err := NewValue(v.Index(i).Interface())
if err != nil {
C._value_destroy(ptr)
return nil, err
}
C.value_array_next_set(ptr, vs.value)
}
// Bind map (with integer or string keys) to PHP associative array type.
case reflect.Map:
kt := v.Type().Key().Kind()
if kt == reflect.Int || kt == reflect.String {
C.value_set_array(ptr, C.uint(v.Len()))
for _, key := range v.MapKeys() {
kv, err := NewValue(v.MapIndex(key).Interface())
if err != nil {
C._value_destroy(ptr)
return nil, err
}
if kt == reflect.Int {
C.value_array_index_set(ptr, C.ulong(key.Int()), kv.value)
} else {
str := C.CString(key.String())
defer C.free(unsafe.Pointer(str))
C.value_array_key_set(ptr, str, kv.value)
}
}
} else {
return nil, fmt.Errorf("Unable to create value of unknown type '%T'", val)
}
// Bind struct to PHP object (stdClass) type.
case reflect.Struct:
C.value_set_object(ptr)
vt := v.Type()
for i := 0; i < v.NumField(); i++ {
// Skip unexported fields.
if vt.Field(i).PkgPath != "" {
continue
}
fv, err := NewValue(v.Field(i).Interface())
if err != nil {
C._value_destroy(ptr)
return nil, err
}
str := C.CString(vt.Field(i).Name)
defer C.free(unsafe.Pointer(str))
C.value_object_property_set(ptr, str, fv.value)
}
case reflect.Invalid:
C.value_set_null(ptr)
default:
C._value_destroy(ptr)
return nil, fmt.Errorf("Unable to create value of unknown type '%T'", val)
}
return &Value{value: ptr}, nil
}
// NewValueFromPtr creates a Value type from an existing PHP value pointer.
func NewValueFromPtr(val unsafe.Pointer) (*Value, error) {
if val == nil {
return nil, fmt.Errorf("Cannot create value from 'nil' pointer")
}
ptr, err := C.value_new()
if err != nil {
return nil, fmt.Errorf("Unable to create new PHP value")
}
if _, err := C.value_set_zval(ptr, (*C.zval)(val)); err != nil {
return nil, fmt.Errorf("Unable to set PHP value from pointer")
}
return &Value{value: ptr}, nil
}
// Kind returns the Value's concrete kind of type.
func (v *Value) Kind() ValueKind {
return (ValueKind)(C.value_kind(v.value))
}
// Interface returns the internal PHP value as it lies, with no conversion step.
func (v *Value) Interface() interface{} {
switch v.Kind() {
case Long:
return v.Int()
case Double:
return v.Float()
case Bool:
return v.Bool()
case String:
return v.String()
case Array:
return v.Slice()
case Map, Object:
return v.Map()
}
return nil
}
// Int returns the internal PHP value as an integer, converting if necessary.
func (v *Value) Int() int64 {
return (int64)(C.value_get_long(v.value))
}
// Float returns the internal PHP value as a floating point number, converting
// if necessary.
func (v *Value) Float() float64 {
return (float64)(C.value_get_double(v.value))
}
// Bool returns the internal PHP value as a boolean, converting if necessary.
func (v *Value) Bool() bool {
return (bool)(C.value_get_bool(v.value))
}
// String returns the internal PHP value as a string, converting if necessary.
func (v *Value) String() string {
str := C.value_get_string(v.value)
defer C.free(unsafe.Pointer(str))
return C.GoString(str)
}
// Slice returns the internal PHP value as a slice of interface types. Non-array
// values are implicitly converted to single-element slices.
func (v *Value) Slice() []interface{} {
size := (int)(C.value_array_size(v.value))
val := make([]interface{}, size)
C.value_array_reset(v.value)
for i := 0; i < size; i++ {
t := &Value{value: C.value_array_next_get(v.value)}
val[i] = t.Interface()
t.Destroy()
}
return val
}
// Map returns the internal PHP value as a map of interface types, indexed by
// string keys. Non-array values are implicitly converted to single-element maps
// with a key of '0'.
func (v *Value) Map() map[string]interface{} {
val := make(map[string]interface{})
keys := &Value{value: C.value_array_keys(v.value)}
for _, k := range keys.Slice() {
switch key := k.(type) {
case int64:
t := &Value{value: C.value_array_index_get(v.value, C.ulong(key))}
sk := strconv.Itoa((int)(key))
val[sk] = t.Interface()
t.Destroy()
case string:
str := C.CString(key)
t := &Value{value: C.value_array_key_get(v.value, str)}
C.free(unsafe.Pointer(str))
val[key] = t.Interface()
t.Destroy()
}
}
keys.Destroy()
return val
}
// Ptr returns a pointer to the internal PHP value, and is mostly used for
// passing to C functions.
func (v *Value) Ptr() unsafe.Pointer {
return unsafe.Pointer(v.value)
}
// Destroy removes all active references to the internal PHP value and frees
// any resources used.
func (v *Value) Destroy() {
if v.value == nil {
return
}
C._value_destroy(v.value)
v.value = nil
}