Skip to content

Commit

Permalink
Merge pull request #16 from deuill/feature/fix-context-eval-scope
Browse files Browse the repository at this point in the history
 Context: Fix variable scoping issues for `Eval`
  • Loading branch information
deuill committed Jan 26, 2016
2 parents 28f2241 + f578e82 commit 088b4b4
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 33 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ func main() {

The above will execute script file `index.php` located in the current folder and will write any output to the `io.Writer` assigned to `Context.Output` (in this case, the standard output).

### Binding and returning variables

The following example demonstrates binding a Go variable to the running PHP context, and returning a PHP variable for use in Go:

```go
package main

import (
"fmt"
php "github.com/deuill/go-php"
)

func main() {
engine, _ := php.New()
context, _ := engine.NewContext()

var str string = "Hello"
context.Bind("var", str)

val, _ := context.Eval("return $var.' World';")
fmt.Printf("%s", val.Interface())
// Prints 'Hello World' back to the user.

engine.Destroy()
}
```

A string value "Hello" is attached using `Context.Bind` under a name `var` (available in PHP as `$var`). A script is executed inline using `Context.Eval`, combinding the attached value with a PHP string and returning it to the user.

Finally, the value is returned as an `interface{}` using `Value.Interface()` (one could also use `Value.String()`, though the both are equivalent in this case).

## License

All code in this repository is covered by the terms of the MIT License, the full text of which can be found in the LICENSE file.
Expand Down
26 changes: 16 additions & 10 deletions engine/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,28 @@ void context_exec(engine_context *context, char *filename) {
}

void *context_eval(engine_context *context, char *script) {
int status;
zval tmp;
zval str;
VALUE_SET_STRING(&str, script);

// Attempt to evaluate inline script.
zend_first_try {
status = zend_eval_string(script, &tmp, "gophp-engine");
} zend_catch {
errno = 1;
return NULL;
} zend_end_try();
// Compile script value.
uint32_t compiler_options = CG(compiler_options);
CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL;
zend_op_array *op = zend_compile_string(&str, "gophp-engine");
CG(compiler_options) = compiler_options;

zval_dtor(&str);

if (status == FAILURE) {
// Return error if script failed to compile.
if (!op) {
errno = 1;
return NULL;
}

// Attempt to execute compiled string.
zval tmp;
CONTEXT_EXECUTE(op, &tmp);

// Allocate result value and copy temporary execution result in.
zval *result = malloc(sizeof(zval));
value_copy(result, &tmp);

Expand Down
14 changes: 6 additions & 8 deletions engine/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ type Context struct {
Header http.Header

context *C.struct__engine_context
values map[string]*Value
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(map[string]*Value),
values: make([]*Value, 0),
}

ptr, err := C.context_new(unsafe.Pointer(ctx))
Expand All @@ -66,7 +66,7 @@ func (c *Context) Bind(name string, val interface{}) error {
defer C.free(unsafe.Pointer(n))

C.context_bind(c.context, n, v.Ptr())
c.values[name] = v
c.values = append(c.values, v)

return nil
}
Expand All @@ -90,11 +90,7 @@ func (c *Context) Exec(filename string) error {
// containing the PHP value returned by the expression, if any. Any output
// produced is written context's pre-defined io.Writer instance.
func (c *Context) Eval(script string) (*Value, error) {
// When PHP compiles code with a non-NULL return value expected, it simply
// prepends a `return` call to the code, thus breaking simple scripts that
// would otherwise work. Thus, we need to wrap the code in a closure, and
// call it immediately.
s := C.CString("call_user_func(function(){" + script + "});")
s := C.CString(script)
defer C.free(unsafe.Pointer(s))

result, err := C.context_eval(c.context, s)
Expand All @@ -109,6 +105,8 @@ func (c *Context) Eval(script string) (*Value, error) {
return nil, err
}

c.values = append(c.values, val)

return val, nil
}

Expand Down
12 changes: 3 additions & 9 deletions engine/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,19 +260,14 @@ func TestContextBind(t *testing.T) {
c, _ := NewContext()
c.Output = &w

script, err := NewScript("bind.php", "<?php $i = (isset($i)) ? $i += 1 : 0; echo serialize($$i);")
if err != nil {
t.Fatalf("Could not create temporary file for testing: %s", err)
}

for i, tt := range bindTests {
if err := c.Bind(fmt.Sprintf("%d", i), tt.value); err != nil {
if err := c.Bind(fmt.Sprintf("t%d", i), tt.value); err != nil {
t.Errorf("Context.Bind('%v'): %s", tt.value, err)
continue
}

if err := c.Exec(script.Name()); err != nil {
t.Errorf("Context.Exec(): %s", err)
if _, err := c.Eval(fmt.Sprintf("echo serialize($t%d);", i)); err != nil {
t.Errorf("Context.Eval(): %s", err)
continue
}

Expand All @@ -284,7 +279,6 @@ func TestContextBind(t *testing.T) {
}
}

script.Remove()
c.Destroy()
}

Expand Down
35 changes: 35 additions & 0 deletions engine/include/php5/_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,39 @@
ZEND_SET_SYMBOL(EG(active_symbol_table), n, v); \
} while (0)

#define CONTEXT_EXECUTE(o, v) do { \
zend_op_array *oparr = EG(active_op_array); \
zval *retval=NULL; \
zval **retvalptr = EG(return_value_ptr_ptr); \
zend_op **opline = EG(opline_ptr); \
int interact = CG(interactive); \
EG(return_value_ptr_ptr) = &retval; \
EG(active_op_array) = o; \
EG(no_extensions) = 1; \
if (!EG(active_symbol_table)) { \
zend_rebuild_symbol_table(); \
} \
CG(interactive) = 0; \
zend_try { \
zend_execute(o); \
} zend_catch { \
destroy_op_array(o); \
efree(o); \
zend_bailout(); \
} zend_end_try(); \
destroy_op_array(o); \
efree(o); \
CG(interactive) = interact; \
if (retval) { \
ZVAL_COPY_VALUE(v, retval); \
zval_copy_ctor(v); \
} else { \
ZVAL_NULL(v); \
} \
EG(no_extensions)=0; \
EG(opline_ptr) = opline; \
EG(active_op_array) = oparr; \
EG(return_value_ptr_ptr) = retvalptr; \
} while (0)

#endif
15 changes: 15 additions & 0 deletions engine/include/php7/_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,19 @@
zend_hash_str_update(&EG(symbol_table), n, strlen(n), v); \
} while (0)

#define CONTEXT_EXECUTE(o, v) do { \
EG(no_extensions) = 1; \
zend_try { \
ZVAL_NULL(v); \
zend_execute(o, v); \
} zend_catch { \
destroy_op_array(o); \
efree_size(o, sizeof(zend_op_array)); \
zend_bailout(); \
} zend_end_try(); \
destroy_op_array(o); \
efree_size(o, sizeof(zend_op_array)); \
EG(no_extensions) = 0; \
} while (0)

#endif
13 changes: 7 additions & 6 deletions engine/include/php7/_receiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
#define RECEIVER_FUNC_SET_ARGFLAGS(f) zend_set_function_arg_flags((zend_function *) f);

#define RECEIVER_OBJECT(o) (o)
#define RECEIVER_OBJECT_CREATE(r, t) do { \
r = ecalloc(1, sizeof(engine_receiver) + zend_object_properties_size(t)); \
zend_object_std_init(&r->obj, t); \
object_properties_init(&r->obj, t); \
r->obj.handlers = &receiver_handlers; \
return &r->obj; \
#define RECEIVER_OBJECT_CREATE(r, t) do { \
r = emalloc(sizeof(engine_receiver)); \
memset(r, 0, sizeof(engine_receiver)); \
zend_object_std_init(&r->obj, t); \
object_properties_init(&r->obj, t); \
r->obj.handlers = &receiver_handlers; \
return &r->obj; \
} while (0)

#define RECEIVER_OBJECT_DESTROY(r) do { \
Expand Down

0 comments on commit 088b4b4

Please sign in to comment.