0
0
mirror of https://github.com/phpv8/v8js.git synced 2025-01-03 11:21:51 +00:00

Merge pull request #18 from stesie/fix-constructor-call-master

Allow PHP object construction from JavaScript context
This commit is contained in:
Patrick Reilly 2013-07-17 15:55:31 -07:00
commit ebec3a64f5
6 changed files with 311 additions and 19 deletions

View File

@ -49,6 +49,7 @@ extern "C" {
#define V8JS_FLOAT(v) v8::Number::New(v) #define V8JS_FLOAT(v) v8::Number::New(v)
#define V8JS_BOOL(v) v8::Boolean::New(v) #define V8JS_BOOL(v) v8::Boolean::New(v)
#define V8JS_NULL v8::Null() #define V8JS_NULL v8::Null()
#define V8JS_UNDEFINED v8::Undefined()
#define V8JS_MN(name) v8js_method_##name #define V8JS_MN(name) v8js_method_##name
#define V8JS_METHOD(name) void V8JS_MN(name)(const v8::FunctionCallbackInfo<v8::Value>& info) #define V8JS_METHOD(name) void V8JS_MN(name)(const v8::FunctionCallbackInfo<v8::Value>& info)
#define V8JS_THROW(type, message, message_len) v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len))) #define V8JS_THROW(type, message, message_len) v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len)))

View File

@ -0,0 +1,42 @@
--TEST--
Test V8::executeString() : Test PHP object construction controlled by JavaScript (simple)
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$v8 = new V8Js();
class Greeter {
function sayHello($a) {
echo "Hello $a\n";
}
}
$v8->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===

View File

@ -0,0 +1,40 @@
--TEST--
Test V8::executeString() : Test PHP object construction controlled by JavaScript (non-construction call)
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$v8 = new V8Js();
class Greeter {
function sayHello($a) {
echo "Hello $a\n";
}
}
$v8->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===

View File

@ -0,0 +1,96 @@
--TEST--
Test V8::executeString() : Test PHP object construction controlled by JavaScript (protected ctor)
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$v8 = new V8Js();
class Greeter {
protected $_name = null;
protected function __construct($name) {
echo "ctor called (php)\n";
$this->_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===

View File

@ -0,0 +1,59 @@
--TEST--
Test V8::executeString() : Test PHP object construction controlled by JavaScript (with ctor)
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$v8 = new V8Js();
class Greeter {
protected $_name = null;
function __construct($name) {
echo "ctor called (php)\n";
$this->_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===

View File

@ -36,28 +36,16 @@ extern "C" {
#include <stdexcept> #include <stdexcept>
/* Callback for PHP methods and functions */ /* Callback for PHP methods and functions */
static void php_v8js_php_callback(const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */ static void php_v8js_call_php_func(zval *value, zend_class_entry *ce, zend_function *method_ptr, v8::Isolate *isolate, const v8::FunctionCallbackInfo<v8::Value>& info) /* {{{ */
{ {
v8::Handle<v8::Value> return_value; v8::Handle<v8::Value> return_value;
zval *value = reinterpret_cast<zval *>(info.This()->GetAlignedPointerFromInternalField(0));
v8::Isolate *isolate = reinterpret_cast<v8::Isolate *>(info.This()->GetAlignedPointerFromInternalField(1));
zend_function *method_ptr;
zend_fcall_info fci; zend_fcall_info fci;
zend_fcall_info_cache fcc; zend_fcall_info_cache fcc;
zval fname, *retval_ptr = NULL, **argv = NULL; 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; zend_uint argc = info.Length(), min_num_args = 0, max_num_args = 0;
char *error; char *error;
int error_len, i, flags = V8JS_FLAG_NONE; 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<zend_function *>(v8::External::Cast(*info.Data())->Value());
} else {
method_ptr = zend_get_closure_invoke_method(value TSRMLS_CC);
}
/* Set parameter limits */ /* Set parameter limits */
min_num_args = method_ptr->common.required_num_args; min_num_args = method_ptr->common.required_num_args;
max_num_args = method_ptr->common.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<v8::Value>& info) /* {{{ */
{
zval *value = reinterpret_cast<zval *>(info.This()->GetAlignedPointerFromInternalField(0));
v8::Isolate *isolate = reinterpret_cast<v8::Isolate *>(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<zend_function *>(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<v8::Value>& info) /* {{{ */
{
v8::Isolate *isolate = v8::Isolate::GetCurrent();
info.GetReturnValue().Set(V8JS_UNDEFINED);
// @todo assert constructor call
v8::Handle<v8::Object> 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<zval *>(v8::External::Cast(*info[0])->Value());
} else {
// Object created from JavaScript context. Need to create PHP object first.
zend_class_entry *ce = static_cast<zend_class_entry *>(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) /* {{{ */ static int _php_v8js_is_assoc_array(HashTable *myht TSRMLS_DC) /* {{{ */
{ {
int i; int i;
@ -341,6 +391,9 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length)); new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length));
new_tpl->InstanceTemplate()->SetInternalFieldCount(2); new_tpl->InstanceTemplate()->SetInternalFieldCount(2);
v8::Handle<v8::Value> wrapped_ce = v8::External::New(ce);
new_tpl->SetCallHandler(php_v8js_construct_callback, wrapped_ce);
if (ce == zend_ce_closure) { if (ce == zend_ce_closure) {
/* Got a closure, mustn't cache ... */ /* Got a closure, mustn't cache ... */
new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback); new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback);
@ -415,10 +468,16 @@ static v8::Handle<v8::Value> 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 // 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 // decides to dispose the JS object, we add a weak persistent handle and register
// a callback function that removes the reference. // a callback function that removes the reference.
v8::Persistent<v8::Object> persist_newobj = v8::Persistent<v8::Object>::New(isolate, new_tpl->GetFunction()->NewInstance()); v8::Handle<v8::Value> external = v8::External::New(value);
v8::Persistent<v8::Object> persist_newobj = v8::Persistent<v8::Object>::New
(isolate, new_tpl->GetFunction()->NewInstance(1, &external));
persist_newobj.MakeWeak(value, php_v8js_weak_object_callback); persist_newobj.MakeWeak(value, php_v8js_weak_object_callback);
// Just tell v8 that we're allocating some external memory // Just tell v8 that we're allocating some external memory
@ -443,12 +502,7 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
newobj->SetHiddenValue(V8JS_SYM(ZEND_ISSET_FUNC_NAME), PHP_V8JS_CALLBACK(isset_ptr)); 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 { } else {
v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New(); // @todo re-use template likewise v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New(); // @todo re-use template likewise