Skip to content

Commit

Permalink
Merge pull request #18 from deuill/feature/dependency-cleanup
Browse files Browse the repository at this point in the history
Clean up method receiver constructors
  • Loading branch information
deuill committed Feb 19, 2016
2 parents a9eec41 + c1905f3 commit 3eaec65
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 141 deletions.
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

0 comments on commit 3eaec65

Please sign in to comment.