diff --git a/php_v8js_macros.h b/php_v8js_macros.h index c04c41e..fe3d2d3 100644 --- a/php_v8js_macros.h +++ b/php_v8js_macros.h @@ -49,6 +49,7 @@ extern "C" { #define V8JS_FLOAT(v) v8::Number::New(v) #define V8JS_BOOL(v) v8::Boolean::New(v) #define V8JS_NULL v8::Null() +#define V8JS_UNDEFINED v8::Undefined() #define V8JS_MN(name) v8js_method_##name #define V8JS_METHOD(name) void V8JS_MN(name)(const v8::FunctionCallbackInfo& info) #define V8JS_THROW(type, message, message_len) v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len))) diff --git a/tests/js-construct-basic.phpt b/tests/js-construct-basic.phpt new file mode 100644 index 0000000..7ecb451 --- /dev/null +++ b/tests/js-construct-basic.phpt @@ -0,0 +1,42 @@ +--TEST-- +Test V8::executeString() : Test PHP object construction controlled by JavaScript (simple) +--SKIPIF-- + +--FILE-- +greeter = new Greeter(); +$v8->executeString(' + function JsGreeter() { }; + JsGreeter.prototype.sayHello = function(a) { + print("Hello " + a + "\n"); + }; + + jsGreeter = new JsGreeter(); + jsGreeter.sayHello("Paul"); + + jsGreeterNg = new jsGreeter.constructor(); + jsGreeterNg.sayHello("George"); + + // ----- now the same using v8Js ----- + + PHP.greeter.sayHello("John"); + + var ngGreeter = new PHP.greeter.constructor(); + ngGreeter.sayHello("Ringo"); +'); +?> +===EOF=== +--EXPECT-- +Hello Paul +Hello George +Hello John +Hello Ringo +===EOF=== diff --git a/tests/js-construct-direct-call.phpt b/tests/js-construct-direct-call.phpt new file mode 100644 index 0000000..43a6cef --- /dev/null +++ b/tests/js-construct-direct-call.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test V8::executeString() : Test PHP object construction controlled by JavaScript (non-construction call) +--SKIPIF-- + +--FILE-- +greeter = new Greeter(); +$v8->executeString(' + function JsGreeter() { }; + JsGreeter.prototype.sayHello = function(a) { + print("Hello " + a + "\n"); + }; + + jsGreeter = new JsGreeter(); + jsGreeter.sayHello("Paul"); + print(jsGreeter.constructor()); + print("\n"); + + // ----- now the same using v8Js ----- + + PHP.greeter.sayHello("John"); + print(PHP.greeter.constructor()); + print("\n"); +'); +?> +===EOF=== +--EXPECT-- +Hello Paul +undefined +Hello John +undefined +===EOF=== diff --git a/tests/js-construct-protected-ctor.phpt b/tests/js-construct-protected-ctor.phpt new file mode 100644 index 0000000..3256cd8 --- /dev/null +++ b/tests/js-construct-protected-ctor.phpt @@ -0,0 +1,96 @@ +--TEST-- +Test V8::executeString() : Test PHP object construction controlled by JavaScript (protected ctor) +--SKIPIF-- + +--FILE-- +_name = $name; + } + + static function getInstance($name) { + return new Greeter($name); + } + + function sayHello() { + echo "Hello ".$this->_name."\n"; + } +} + +$v8->greeter = Greeter::getInstance("John"); + +try { + $v8->executeString(' + PHP.greeter.sayHello(); + + var ngGreeter = new PHP.greeter.constructor("Ringo"); + ngGreeter.sayHello(); + ', 'ctor-test'); +} catch(V8JsScriptException $e) { + echo "caught js exception\n"; + var_dump($e); +} +?> +===EOF=== +--EXPECTF-- +ctor called (php) +Hello John +caught js exception +object(V8JsScriptException)#3 (11) { + ["message":protected]=> + string(56) "ctor-test:4: Call to protected __construct() not allowed" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(0) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(29) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(6) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(29) + ["function"]=> + string(13) "executeString" + ["class"]=> + string(4) "V8Js" + ["type"]=> + string(2) "->" + ["args"]=> + array(2) { + [0]=> + string(109) " + PHP.greeter.sayHello(); + + var ngGreeter = new PHP.greeter.constructor("Ringo"); + ngGreeter.sayHello(); + " + [1]=> + string(9) "ctor-test" + } + } + } + ["previous":"Exception":private]=> + NULL + ["JsFileName":protected]=> + string(9) "ctor-test" + ["JsLineNumber":protected]=> + int(4) + ["JsSourceLine":protected]=> + string(55) " var ngGreeter = new PHP.greeter.constructor("Ringo");" + ["JsTrace":protected]=> + NULL +} +===EOF=== + diff --git a/tests/js-construct-with-ctor.phpt b/tests/js-construct-with-ctor.phpt new file mode 100644 index 0000000..72b5fbc --- /dev/null +++ b/tests/js-construct-with-ctor.phpt @@ -0,0 +1,59 @@ +--TEST-- +Test V8::executeString() : Test PHP object construction controlled by JavaScript (with ctor) +--SKIPIF-- + +--FILE-- +_name = $name; + } + + function sayHello() { + echo "Hello ".$this->_name."\n"; + } +} + +$v8->greeter = new Greeter("John"); + +$v8->executeString(' + function JsGreeter(name) { + print("ctor called (js)\n"); + this.name = name; + }; + + JsGreeter.prototype.sayHello = function() { + print("Hello " + this.name + "\n"); + }; + + jsGreeter = new JsGreeter("Paul"); + jsGreeter.sayHello(); + + jsGreeterNg = new jsGreeter.constructor("George"); + jsGreeterNg.sayHello(); + + // ----- now the same using v8Js ----- + + PHP.greeter.sayHello(); + + var ngGreeter = new PHP.greeter.constructor("Ringo"); + ngGreeter.sayHello(); +'); +?> +===EOF=== +--EXPECT-- +ctor called (php) +ctor called (js) +Hello Paul +ctor called (js) +Hello George +Hello John +ctor called (php) +Hello Ringo +===EOF=== + diff --git a/v8js_convert.cc b/v8js_convert.cc index bdeb4bb..a5ee99b 100644 --- a/v8js_convert.cc +++ b/v8js_convert.cc @@ -36,28 +36,16 @@ extern "C" { #include /* Callback for PHP methods and functions */ -static void php_v8js_php_callback(const v8::FunctionCallbackInfo& info) /* {{{ */ +static void php_v8js_call_php_func(zval *value, zend_class_entry *ce, zend_function *method_ptr, v8::Isolate *isolate, const v8::FunctionCallbackInfo& info) /* {{{ */ { v8::Handle return_value; - zval *value = reinterpret_cast(info.This()->GetAlignedPointerFromInternalField(0)); - v8::Isolate *isolate = reinterpret_cast(info.This()->GetAlignedPointerFromInternalField(1)); - zend_function *method_ptr; zend_fcall_info fci; zend_fcall_info_cache fcc; zval fname, *retval_ptr = NULL, **argv = NULL; - TSRMLS_FETCH(); - zend_class_entry *ce = Z_OBJCE_P(value); zend_uint argc = info.Length(), min_num_args = 0, max_num_args = 0; char *error; int error_len, i, flags = V8JS_FLAG_NONE; - /* Set method_ptr from v8::External or fetch the closure invoker */ - if (!info.Data().IsEmpty() && info.Data()->IsExternal()) { - method_ptr = static_cast(v8::External::Cast(*info.Data())->Value()); - } else { - method_ptr = zend_get_closure_invoke_method(value TSRMLS_CC); - } - /* Set parameter limits */ min_num_args = method_ptr->common.required_num_args; max_num_args = method_ptr->common.num_args; @@ -148,6 +136,68 @@ failure: } /* }}} */ +/* Callback for PHP methods and functions */ +static void php_v8js_php_callback(const v8::FunctionCallbackInfo& info) /* {{{ */ +{ + zval *value = reinterpret_cast(info.This()->GetAlignedPointerFromInternalField(0)); + v8::Isolate *isolate = reinterpret_cast(info.This()->GetAlignedPointerFromInternalField(1)); + zend_function *method_ptr; + zend_class_entry *ce = Z_OBJCE_P(value); + TSRMLS_FETCH(); + + /* Set method_ptr from v8::External or fetch the closure invoker */ + if (!info.Data().IsEmpty() && info.Data()->IsExternal()) { + method_ptr = static_cast(v8::External::Cast(*info.Data())->Value()); + } else { + method_ptr = zend_get_closure_invoke_method(value TSRMLS_CC); + } + + return php_v8js_call_php_func(value, ce, method_ptr, isolate, info); +} + +/* Callback for PHP constructor calls */ +static void php_v8js_construct_callback(const v8::FunctionCallbackInfo& info) /* {{{ */ +{ + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + info.GetReturnValue().Set(V8JS_UNDEFINED); + + // @todo assert constructor call + v8::Handle newobj = info.This(); + zval *value; + TSRMLS_FETCH(); + + if (!info.IsConstructCall()) { + return; + } + + if (info[0]->IsExternal()) { + // Object created by v8js in php_v8js_hash_to_jsobj, PHP object passed as v8::External. + value = static_cast(v8::External::Cast(*info[0])->Value()); + } else { + // Object created from JavaScript context. Need to create PHP object first. + zend_class_entry *ce = static_cast(v8::External::Cast(*info.Data())->Value()); + zend_function *ctor_ptr = ce->constructor; + + // Check access on __construct function, if any + if (ctor_ptr != NULL && (ctor_ptr->common.fn_flags & ZEND_ACC_PUBLIC) == 0) { + info.GetReturnValue().Set(v8::ThrowException(v8::String::New("Call to protected __construct() not allowed"))); + return; + } + + MAKE_STD_ZVAL(value); + object_init_ex(value, ce TSRMLS_CC); + + // Call __construct function + if (ctor_ptr != NULL) { + php_v8js_call_php_func(value, ce, ctor_ptr, isolate, info); + } + } + + newobj->SetAlignedPointerInInternalField(0, value); + newobj->SetAlignedPointerInInternalField(1, (void *) isolate); +} +/* }}} */ + static int _php_v8js_is_assoc_array(HashTable *myht TSRMLS_DC) /* {{{ */ { int i; @@ -341,6 +391,9 @@ static v8::Handle php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length)); new_tpl->InstanceTemplate()->SetInternalFieldCount(2); + v8::Handle wrapped_ce = v8::External::New(ce); + new_tpl->SetCallHandler(php_v8js_construct_callback, wrapped_ce); + if (ce == zend_ce_closure) { /* Got a closure, mustn't cache ... */ new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback); @@ -415,10 +468,16 @@ static v8::Handle php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is } } + // Increase the reference count of this value because we're storing it internally for use later + // See https://github.com/preillyme/v8js/issues/6 + Z_ADDREF_P(value); + // Since we got to decrease the reference count again, in case v8 garbage collector // decides to dispose the JS object, we add a weak persistent handle and register // a callback function that removes the reference. - v8::Persistent persist_newobj = v8::Persistent::New(isolate, new_tpl->GetFunction()->NewInstance()); + v8::Handle external = v8::External::New(value); + v8::Persistent persist_newobj = v8::Persistent::New + (isolate, new_tpl->GetFunction()->NewInstance(1, &external)); persist_newobj.MakeWeak(value, php_v8js_weak_object_callback); // Just tell v8 that we're allocating some external memory @@ -443,12 +502,7 @@ static v8::Handle php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is newobj->SetHiddenValue(V8JS_SYM(ZEND_ISSET_FUNC_NAME), PHP_V8JS_CALLBACK(isset_ptr)); } } - // Increase the reference count of this value because we're storing it internally for use later - // See https://github.com/preillyme/v8js/issues/6 - Z_ADDREF_P(value); - newobj->SetAlignedPointerInInternalField(0, (void *) value); - newobj->SetAlignedPointerInInternalField(1, (void *) isolate); } else { v8::Local new_tpl = v8::FunctionTemplate::New(); // @todo re-use template likewise