Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up method receiver constructors #18

Merged
merged 2 commits into from
Feb 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions engine/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package engine
//
// #include <stdlib.h>
// #include <main/php.h>
// #include "receiver.h"
// #include "context.h"
import "C"

Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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()
}
Expand Down
36 changes: 29 additions & 7 deletions engine/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
51 changes: 17 additions & 34 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
if err != nil {
return nil, err
}

e.contexts = append(e.contexts, c)

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)
ctx := &Context{
Header: make(http.Header),
values: make([]*Value, 0),
receivers: make(map[string]*Receiver),
}

rcvr, err := NewReceiver(name, fn)
ptr, err := C.context_new(unsafe.Pointer(ctx))
if err != nil {
return err
return nil, fmt.Errorf("Failed to initialize context for PHP engine")
}

e.receivers[name] = rcvr
ctx.context = ptr
e.contexts = append(e.contexts, ctx)

return nil
return ctx, nil
}

// Destroy shuts down and frees any resources related to the PHP engine bindings.
Expand All @@ -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()
}
Expand Down
25 changes: 2 additions & 23 deletions engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Expand All @@ -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`")
}

Expand Down
38 changes: 8 additions & 30 deletions engine/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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() {
Expand All @@ -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.
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
Loading