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:
commit
ebec3a64f5
@ -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)))
|
||||||
|
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>
|
#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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user