Merge pull request #18 from deuill/feature/dependency-cleanup

Clean up method receiver constructors
This commit is contained in:
Alex Palaistras 2016-02-19 21:59:00 +00:00
commit 3eaec657a3
7 changed files with 126 additions and 143 deletions

View File

@ -9,6 +9,7 @@ package engine
//
// #include <stdlib.h>
// #include <main/php.h>
// #include "receiver.h"
// #include "context.h"
import "C"
@ -30,26 +31,9 @@ type Context struct {
// Header represents the HTTP headers set by current PHP context.
Header http.Header
context *C.struct__engine_context
values []*Value
}
// NewContext creates a new execution context for the active engine and returns
// an error if the execution context failed to initialize at any point.
func NewContext() (*Context, error) {
ctx := &Context{
Header: make(http.Header),
values: make([]*Value, 0),
}
ptr, err := C.context_new(unsafe.Pointer(ctx))
if err != nil {
return nil, fmt.Errorf("Failed to initialize context for PHP engine")
}
ctx.context = ptr
return ctx, nil
context *C.struct__engine_context
values []*Value
receivers map[string]*Receiver
}
// Bind allows for binding Go values into the current execution context under
@ -71,6 +55,34 @@ func (c *Context) Bind(name string, val interface{}) error {
return nil
}
// Define registers a PHP class for the name passed, using function fn as
// constructor for individual object instances as needed by the PHP context.
//
// The class name registered is assumed to be unique for the active engine.
//
// The constructor function accepts a slice of arguments, as passed by the PHP
// context, and should return a method receiver instance, or nil on error (in
// which case, an exception is thrown on the PHP object constructor).
func (c *Context) Define(name string, fn func(args []interface{}) interface{}) error {
if _, exists := c.receivers[name]; exists {
return fmt.Errorf("Failed to define duplicate receiver '%s'", name)
}
rcvr := &Receiver{
name: name,
create: fn,
objects: make([]*ReceiverObject, 0),
}
n := C.CString(name)
defer C.free(unsafe.Pointer(n))
C.receiver_define(n, unsafe.Pointer(rcvr))
c.receivers[name] = rcvr
return nil
}
// Exec executes a PHP script pointed to by filename in the current execution
// context, and returns an error, if any. Output produced by the script is
// written to the context's pre-defined io.Writer instance.
@ -117,6 +129,12 @@ func (c *Context) Destroy() {
return
}
for _, r := range c.receivers {
r.Destroy()
}
c.receivers = nil
for _, v := range c.values {
v.Destroy()
}

View File

@ -19,7 +19,7 @@ func TestContextStart(t *testing.T) {
}
func TestContextNew(t *testing.T) {
c, err := NewContext()
c, err := e.NewContext()
if err != nil {
t.Fatalf("NewContext(): %s", err)
}
@ -31,6 +31,28 @@ func TestContextNew(t *testing.T) {
c.Destroy()
}
func TestContextDefine(t *testing.T) {
ctor := func(args []interface{}) interface{} {
return nil
}
c, _ := e.NewContext()
if err := c.Define("TestDefine", ctor); err != nil {
t.Errorf("Context.Define(): %s", err)
}
if len(c.receivers) != 1 {
t.Errorf("Context.Define(): `Context.receivers` length is %d, should be 1", len(c.receivers))
}
if err := c.Define("TestDefine", ctor); err == nil {
t.Errorf("Context.Define(): Incorrectly defined duplicate receiver")
}
c.Destroy()
}
var execTests = []struct {
name string
script string
@ -51,7 +73,7 @@ var execTests = []struct {
func TestContextExec(t *testing.T) {
var w bytes.Buffer
c, _ := NewContext()
c, _ := e.NewContext()
c.Output = &w
for _, tt := range execTests {
@ -99,7 +121,7 @@ var evalTests = []struct {
func TestContextEval(t *testing.T) {
var w bytes.Buffer
c, _ := NewContext()
c, _ := e.NewContext()
c.Output = &w
for _, tt := range evalTests {
@ -151,7 +173,7 @@ var headerTests = []struct {
}
func TestContextHeader(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range headerTests {
if _, err := c.Eval(tt.script); err != nil {
@ -188,7 +210,7 @@ var logTests = []struct {
func TestContextLog(t *testing.T) {
var w bytes.Buffer
c, _ := NewContext()
c, _ := e.NewContext()
c.Log = &w
for _, tt := range logTests {
@ -258,7 +280,7 @@ var bindTests = []struct {
func TestContextBind(t *testing.T) {
var w bytes.Buffer
c, _ := NewContext()
c, _ := e.NewContext()
c.Output = &w
for i, tt := range bindTests {
@ -284,7 +306,7 @@ func TestContextBind(t *testing.T) {
}
func TestContextDestroy(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
c.Destroy()
if c.context != nil || c.values != nil {

View File

@ -18,15 +18,15 @@ import "C"
import (
"fmt"
"io"
"net/http"
"strings"
"unsafe"
)
// Engine represents the core PHP engine bindings.
type Engine struct {
engine *C.struct__php_engine
contexts []*Context
receivers map[string]*Receiver
engine *C.struct__php_engine
contexts []*Context
}
// New initializes a PHP engine instance on which contexts can be executed. It
@ -38,43 +38,32 @@ func New() (*Engine, error) {
}
e := &Engine{
engine: ptr,
contexts: make([]*Context, 0),
receivers: make(map[string]*Receiver),
engine: ptr,
contexts: make([]*Context, 0),
}
return e, nil
}
// NewContext creates a new execution context on which scripts can be executed
// and variables can be binded. It corresponds to PHP's RINIT (request init)
// phase.
// NewContext creates a new execution context for the active engine and returns
// an error if the execution context failed to initialize at any point. This
// corresponds to PHP's RINIT (request init) phase.
func (e *Engine) NewContext() (*Context, error) {
c, err := NewContext()
ctx := &Context{
Header: make(http.Header),
values: make([]*Value, 0),
receivers: make(map[string]*Receiver),
}
ptr, err := C.context_new(unsafe.Pointer(ctx))
if err != nil {
return nil, err
return nil, fmt.Errorf("Failed to initialize context for PHP engine")
}
e.contexts = append(e.contexts, c)
ctx.context = ptr
e.contexts = append(e.contexts, ctx)
return c, nil
}
// Define registers a PHP class under the name passed, using fn as the class
// constructor.
func (e *Engine) Define(name string, fn func(args []interface{}) interface{}) error {
if _, exists := e.receivers[name]; exists {
return fmt.Errorf("Failed to define duplicate receiver '%s'", name)
}
rcvr, err := NewReceiver(name, fn)
if err != nil {
return err
}
e.receivers[name] = rcvr
return nil
return ctx, nil
}
// Destroy shuts down and frees any resources related to the PHP engine bindings.
@ -83,12 +72,6 @@ func (e *Engine) Destroy() {
return
}
for _, r := range e.receivers {
r.Destroy()
}
e.receivers = nil
for _, c := range e.contexts {
c.Destroy()
}

View File

@ -44,7 +44,7 @@ func TestEngineNew(t *testing.T) {
t.Fatalf("New(): %s", err)
}
if e.engine == nil || e.contexts == nil || e.receivers == nil {
if e.engine == nil || e.contexts == nil {
t.Fatalf("New(): Struct fields are `nil` but no error returned")
}
}
@ -60,31 +60,10 @@ func TestEngineNewContext(t *testing.T) {
}
}
func TestEngineDefine(t *testing.T) {
ctor := func(args []interface{}) interface{} {
return nil
}
err := e.Define("TestDefine", ctor)
if err != nil {
t.Errorf("Define(): %s", err)
}
if len(e.receivers) != 1 {
t.Errorf("Define(): `Engine.receivers` length is %d, should be 1", len(e.receivers))
}
err = e.Define("TestDefine", ctor)
if err == nil {
t.Errorf("Define(): Incorrectly defined duplicate receiver")
}
}
func TestEngineDestroy(t *testing.T) {
e.Destroy()
if e.engine != nil || e.contexts != nil || e.receivers != nil {
if e.engine != nil || e.contexts != nil {
t.Errorf("Engine.Destroy(): Did not set internal fields to `nil`")
}

View File

@ -17,7 +17,8 @@ import (
"unsafe"
)
type object struct {
// ReceiverObject represents an object instance of a pre-defined method receiver.
type ReceiverObject struct {
instance interface{}
values map[string]reflect.Value
methods map[string]reflect.Value
@ -27,30 +28,7 @@ type object struct {
type Receiver struct {
name string
create func(args []interface{}) interface{}
objects []*object
}
// NewReceiver registers a PHP class for the name passed, using function fn as
// constructor for individual object instances as needed by the PHP context.
//
// The class name registered is assumed to be unique for the active engine.
//
// The constructor function accepts a slice of arguments, as passed by the PHP
// context, and should return a method receiver instance, or nil on error (in
// which case, an exception is thrown on the PHP object constructor).
func NewReceiver(name string, fn func(args []interface{}) interface{}) (*Receiver, error) {
rcvr := &Receiver{
name: name,
create: fn,
objects: make([]*object, 0),
}
n := C.CString(name)
defer C.free(unsafe.Pointer(n))
C.receiver_define(n, unsafe.Pointer(rcvr))
return rcvr, nil
objects []*ReceiverObject
}
// Destroy removes references to the generated PHP class for this receiver and
@ -80,7 +58,7 @@ func receiverNew(rcvr unsafe.Pointer, args unsafe.Pointer) unsafe.Pointer {
defer va.Destroy()
obj := &object{
obj := &ReceiverObject{
instance: r.create(va.Slice()),
values: make(map[string]reflect.Value),
methods: make(map[string]reflect.Value),
@ -120,7 +98,7 @@ func receiverNew(rcvr unsafe.Pointer, args unsafe.Pointer) unsafe.Pointer {
//export receiverGet
func receiverGet(obj unsafe.Pointer, name *C.char) unsafe.Pointer {
o := (*object)(obj)
o := (*ReceiverObject)(obj)
n := C.GoString(name)
if _, exists := o.values[n]; !exists || !o.values[n].CanInterface() {
@ -137,7 +115,7 @@ func receiverGet(obj unsafe.Pointer, name *C.char) unsafe.Pointer {
//export receiverSet
func receiverSet(obj unsafe.Pointer, name *C.char, val unsafe.Pointer) {
o := (*object)(obj)
o := (*ReceiverObject)(obj)
n := C.GoString(name)
// Do not attempt to set non-existing or unset-able field.
@ -155,7 +133,7 @@ func receiverSet(obj unsafe.Pointer, name *C.char, val unsafe.Pointer) {
//export receiverExists
func receiverExists(obj unsafe.Pointer, name *C.char) C.int {
o := (*object)(obj)
o := (*ReceiverObject)(obj)
n := C.GoString(name)
if _, exists := o.values[n]; !exists {
@ -167,7 +145,7 @@ func receiverExists(obj unsafe.Pointer, name *C.char) C.int {
//export receiverCall
func receiverCall(obj unsafe.Pointer, name *C.char, args unsafe.Pointer) unsafe.Pointer {
o := (*object)(obj)
o := (*ReceiverObject)(obj)
n := C.GoString(name)
if _, exists := o.methods[n]; !exists {

View File

@ -49,7 +49,7 @@ func newTestReceiver(args []interface{}) interface{} {
return &testReceiver{Var: value, hidden: 42}
}
var newReceiverTests = []struct {
var receiverDefineTests = []struct {
script string
expected string
}{
@ -65,7 +65,6 @@ var newReceiverTests = []struct {
}`,
"Failed to instantiate method receiver",
},
{
"$t = new TestReceiver; echo $t->Var;",
"Foo",
@ -78,7 +77,6 @@ var newReceiverTests = []struct {
"$t = new TestReceiver('wow'); echo $t->Var;",
"wow",
},
{
"$t = new TestReceiver; $t->Var = 'Bar'; echo $t->Var;",
"Bar",
@ -87,7 +85,6 @@ var newReceiverTests = []struct {
"$t = new TestReceiver; $t->hello = 'wow'; echo $t->hello;",
"",
},
{
"$t = new TestReceiver; echo $t->Ignore();",
"",
@ -104,7 +101,6 @@ var newReceiverTests = []struct {
"$t = new TestReceiver; echo $t->invalid();",
"",
},
{
"$t = new TestReceiver; echo ($t->Var) ? 1 : 0;",
"1",
@ -123,18 +119,22 @@ var newReceiverTests = []struct {
},
}
func TestNewReceiver(t *testing.T) {
func TestReceiverDefine(t *testing.T) {
var w bytes.Buffer
c, _ := NewContext()
c, _ := e.NewContext()
c.Output = &w
r, err := NewReceiver("TestReceiver", newTestReceiver)
if err != nil {
t.Fatalf("NewReceiver(): Failed to define method receiver: %s", err)
if err := c.Define("TestReceiver", newTestReceiver); err != nil {
t.Fatalf("Engine.Define(): Failed to define method receiver: %s", err)
}
for _, tt := range newReceiverTests {
// Attempting to define a receiver twice should fail.
if err := c.Define("TestReceiver", newTestReceiver); err == nil {
t.Fatalf("Engine.Define(): Defining duplicate receiver should fail")
}
for _, tt := range receiverDefineTests {
_, err := c.Eval(tt.script)
if err != nil {
t.Errorf("Context.Eval('%s'): %s", tt.script, err)
@ -149,17 +149,20 @@ func TestNewReceiver(t *testing.T) {
}
}
r.Destroy()
c.Destroy()
}
func TestReceiverDestroy(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
defer c.Destroy()
r, err := NewReceiver("TestReceiver", newTestReceiver)
if err != nil {
t.Fatalf("NewReceiver(): Failed to define method receiver: %s", err)
if err := c.Define("TestReceiver", newTestReceiver); err != nil {
t.Fatalf("Engine.Define(): Failed to define method receiver: %s", err)
}
r := c.receivers["TestReceiver"]
if r == nil {
t.Fatalf("Receiver.Destroy(): Could not find defined receiver")
}
r.Destroy()

View File

@ -66,7 +66,7 @@ var valueNewTests = []struct {
}
func TestValueNew(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueNewTests {
val, err := NewValue(tt.value)
@ -105,7 +105,7 @@ var valueNewInvalidTests = []interface{}{
}
func TestValueNewInvalid(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, value := range valueNewInvalidTests {
val, err := NewValue(value)
@ -156,7 +156,7 @@ var valueKindTests = []struct {
}
func TestValueKind(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueKindTests {
val, err := NewValue(tt.value)
@ -215,7 +215,7 @@ var valueIntTests = []struct {
}
func TestValueInt(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueIntTests {
val, err := NewValue(tt.value)
@ -274,7 +274,7 @@ var valueFloatTests = []struct {
}
func TestValueFloat(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueFloatTests {
val, err := NewValue(tt.value)
@ -333,7 +333,7 @@ var valueBoolTests = []struct {
}
func TestValueBool(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueBoolTests {
val, err := NewValue(tt.value)
@ -392,7 +392,7 @@ var valueStringTests = []struct {
}
func TestValueString(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueStringTests {
val, err := NewValue(tt.value)
@ -451,7 +451,7 @@ var valueSliceTests = []struct {
}
func TestValueSlice(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueSliceTests {
val, err := NewValue(tt.value)
@ -510,7 +510,7 @@ var valueMapTests = []struct {
}
func TestValueMap(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
for _, tt := range valueMapTests {
val, err := NewValue(tt.value)
@ -532,7 +532,7 @@ func TestValueMap(t *testing.T) {
}
func TestValuePtr(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
defer c.Destroy()
val, err := NewValue(42)
@ -546,7 +546,7 @@ func TestValuePtr(t *testing.T) {
}
func TestValueDestroy(t *testing.T) {
c, _ := NewContext()
c, _ := e.NewContext()
defer c.Destroy()
val, err := NewValue(42)