Skip to content

Commit af804b5

Browse files
committed
Add Js\ByRef() class to allow values to be passed by reference to JS.
1 parent 4f5a23c commit af804b5

File tree

5 files changed

+92
-32
lines changed

5 files changed

+92
-32
lines changed

lib/startup.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,15 @@
33
// helpers which are easier to write in PHP than in C.
44
namespace Js;
55

6+
// Simple marker class to indicate that a given value should be
7+
// passed by reference. Internally we'll call `getValue` to
8+
// unwrap the reference inside.
9+
class ByRef {
10+
public $value;
11+
public function __construct(&$value) { $this->value =& $value; }
12+
public function &getValue() {
13+
return $this->value;
14+
}
15+
}
16+
617
?>

src/node_php_jsobject_class.cc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,14 @@ class JsInvokeMsg : public MessageToJs {
359359
objid_t objId, zval *member, ulong argc, zval **argv TSRMLS_DC)
360360
: MessageToJs(m, callback, isSync),
361361
object_(), member_(m, member TSRMLS_CC),
362-
argc_(argc), argv_(Value::NewArray(m, argc, argv TSRMLS_CC)) {
362+
argc_(argc), argv_() {
363363
object_.SetJsObject(objId);
364+
argv_.SetArrayByValue(argc, [m, argv TSRMLS_CC](uint32_t idx, Value& v) {
365+
ZVal z(argv[idx] ZEND_FILE_LINE_CC);
366+
z.UnwrapByRef(TSRMLS_C); // Unwrap Js\ByRef values.
367+
v.Set(m, z TSRMLS_CC);
368+
});
364369
}
365-
~JsInvokeMsg() override { delete[] argv_; }
366370

367371
protected:
368372
void InJs(JsObjectMapper *m) override {
@@ -431,7 +435,7 @@ class JsInvokeMsg : public MessageToJs {
431435
Value object_;
432436
Value member_;
433437
ulong argc_;
434-
Value *argv_;
438+
Value argv_;
435439
};
436440

437441
// XXX Figure out how to actually invoke methods async.
@@ -442,8 +446,6 @@ ZEND_BEGIN_ARG_INFO_EX(node_php_jsobject_call_args, 0, 1/*return by ref*/, 1)
442446
ZEND_ARG_ARRAY_INFO(0, args, 0)
443447
ZEND_END_ARG_INFO()
444448

445-
// XXX can't figure out how to pass arrays by reference instead of
446-
// by value.
447449
PHP_METHOD(JsObject, __call) {
448450
zval *member; zval *args;
449451
TRACE(">");

src/values.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
extern "C" {
2020
#include "main/php.h"
21+
#include "Zend/zend_interfaces.h" // for zend_call_method_with_*
2122
}
2223

2324
#include "src/macros.h"
@@ -101,6 +102,24 @@ class ZVal {
101102
return z;
102103
}
103104

105+
// Unwrap references protected by Js\ByRef
106+
inline void UnwrapByRef(TSRMLS_D) {
107+
if (!IsObject()) { return; }
108+
assert(zvalp && !transferred_);
109+
zend_class_entry *ce = Z_OBJCE_P(zvalp);
110+
// XXX cache the zend_class_entry at startup so we can do a simple
111+
// pointer comparison instead of looking at the class name
112+
if (ce->name_length == 8 && strcmp("Js\\ByRef", ce->name) == 0) {
113+
// Unwrap!
114+
zval *rv;
115+
zend_call_method_with_0_params(&zvalp, nullptr, nullptr, "getValue", &rv);
116+
if (rv) {
117+
zval_ptr_dtor(&zvalp);
118+
zvalp = rv;
119+
}
120+
}
121+
}
122+
104123
// Support a PHP calling convention where the actual zval object
105124
// is owned by the caller, but the contents are transferred to the
106125
// callee.
@@ -694,6 +713,10 @@ class Value {
694713
inline bool IsArrayByValue() const {
695714
return (type_ == VALUE_ARRAY_BY_VALUE);
696715
}
716+
inline Value& operator[](int i) const {
717+
assert(IsArrayByValue());
718+
return array_by_value_.item_[i];
719+
}
697720
bool AsBool() const {
698721
switch (type_) {
699722
case VALUE_BOOL:

test/byref.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Test cases for passing values "by reference".
2+
var StringStream = require('../test-stream.js');
3+
4+
var path = require('path');
5+
var should = require('should');
6+
7+
describe('Wrapped PHP objects passed by reference to JavaScript', function() {
8+
var php = require('../');
9+
var defaultCode = [
10+
'call_user_func(function () {',
11+
' $ctxt = $_SERVER["CONTEXT"];',
12+
' #include $ctxt->file;',
13+
'',
14+
' $a = {{VALUE}};',
15+
' $b = $a;',
16+
' $ctxt->jsfunc(new Js\\ByRef($a));',
17+
' var_dump($a);',
18+
' var_dump($b);',
19+
'})',
20+
].join('\n');
21+
var test = function(value, f) {
22+
var code = defaultCode.replace('{{VALUE}}', value);
23+
var out = new StringStream();
24+
return php.request({ source: code, context: {
25+
jsfunc: f,
26+
file: path.join(__dirname, 'byref.php'),
27+
}, stream: out, }).then(function(v) { return [v, out.toString()]; });
28+
};
29+
it('arrays', function() {
30+
return test('array("a"=>"b")',function(a, b) {
31+
a[1] = 2;
32+
}).spread(function(v, out) {
33+
out.should.equal([
34+
'array(2) {',
35+
' ["a"]=>',
36+
' string(1) "b"',
37+
' [1]=>',
38+
' int(2)',
39+
'}',
40+
'array(1) {',
41+
' ["a"]=>',
42+
' string(1) "b"',
43+
'}',
44+
'',
45+
].join('\n'));
46+
});
47+
});
48+
});

test/phparray.js

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -535,38 +535,14 @@ describe('Wrapped PHP arrays accessed from JavaScript', function() {
535535
});
536536
it('can be passed by reference', function() {
537537
return test(function(arr) {
538-
arr.offsetSet(1, 2);
539-
arr.offsetSet('foo', 'bar');
540-
// XXX make the below work.
541-
// `arr[1] = 2;`
542-
// `arr['foo'] = 'bar';`
538+
arr[1] = 2;
539+
arr.set('foo', 'bar');
543540
}, [
544541
'call_user_func(function() {',
545-
// XXX this `wrap` class should be built-in as Js\ArrayByRef
546-
' class wrap implements ArrayAccess, Countable {',
547-
' private $a;',
548-
' public function __construct(&$a) { $this->a =& $a; }',
549-
' public function offsetSet($offset, $value) {',
550-
' if (is_null($offset)) { $this->a[] = $value; }',
551-
' else { $this->a[$offset] = $value; }',
552-
' }',
553-
' public function offsetExists($offset) {',
554-
' return isset($this->a[$offset]);',
555-
' }',
556-
' public function offsetUnset($offset) {',
557-
' unset($this->a[$offset]);',
558-
' }',
559-
' public function offsetGet($offset) {',
560-
' return isset($this->a[$offset]) ? $this->a[$offset] : null;',
561-
' }',
562-
' public function count() {',
563-
' return count($this->a);',
564-
' }',
565-
' }',
566542
' $a = array("a" => "b");',
567543
' $b = $a;',
568544
' $ctxt = $_SERVER["CONTEXT"];',
569-
' $ctxt->jsfunc(new wrap($a));',
545+
' $ctxt->jsfunc(new Js\\ByRef($a));',
570546
' var_dump($a);',
571547
' var_dump($b);',
572548
'})',

0 commit comments

Comments
 (0)