mirror of
https://github.com/phpv8/v8js.git
synced 2025-01-03 10: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:
commit
ebec3a64f5
@ -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<v8::Value>& info)
|
||||
#define V8JS_THROW(type, message, message_len) v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len)))
|
||||
|
42
tests/js-construct-basic.phpt
Normal file
42
tests/js-construct-basic.phpt
Normal 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===
|
40
tests/js-construct-direct-call.phpt
Normal file
40
tests/js-construct-direct-call.phpt
Normal 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===
|
96
tests/js-construct-protected-ctor.phpt
Normal file
96
tests/js-construct-protected-ctor.phpt
Normal 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===
|
||||
|
59
tests/js-construct-with-ctor.phpt
Normal file
59
tests/js-construct-with-ctor.phpt
Normal 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===
|
||||
|
@ -36,28 +36,16 @@ extern "C" {
|
||||
#include <stdexcept>
|
||||
|
||||
/* 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;
|
||||
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_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<zend_function *>(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<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) /* {{{ */
|
||||
{
|
||||
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->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) {
|
||||
/* Got a closure, mustn't cache ... */
|
||||
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
|
||||
// decides to dispose the JS object, we add a weak persistent handle and register
|
||||
// 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);
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
// 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<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New(); // @todo re-use template likewise
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user