Explorar el Código

Improve ArrayAccess enumeration

When enumerating an ArrayAccess-style object the array keys should be
returned, not the method names & properties of the PHP object.
Stefan Siegl hace 10 años
padre
commit
6399b49b3f
Se han modificado 4 ficheros con 172 adiciones y 12 borrados
  1. 63 0
      tests/array_access_006.phpt
  2. 97 10
      v8js_array_access.cc
  3. 2 0
      v8js_array_access.h
  4. 10 2
      v8js_object_export.cc

+ 63 - 0
tests/array_access_006.phpt

@@ -0,0 +1,63 @@
+--TEST--
+Test V8::executeString() : Enumerate ArrayAccess keys
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--INI--
+v8js.use_array_access = 1
+--FILE--
+<?php
+
+class MyArray implements ArrayAccess, Countable {
+    private $data = Array('one', 'two', 'three', null, 'five');
+
+    public function offsetExists($offset) {
+	return isset($this->data[$offset]);
+    }
+
+    public function offsetGet($offset) {
+	return $this->data[$offset];
+    }
+
+    public function offsetSet($offset, $value) {
+	echo "set[$offset] = $value\n";
+	$this->data[$offset] = $value;
+    }
+
+    public function offsetUnset($offset) {
+        throw new Exception('Not implemented');
+    }
+
+    public function count() {
+        return count($this->data);
+    }
+}
+
+$v8 = new V8Js();
+$v8->myarr = new MyArray();
+
+$js = <<<EOF
+var jsarr = [ "one", "two", "three", , "five" ];
+for(var i in jsarr) {
+  var_dump(i);
+}
+
+for(var i in PHP.myarr) {
+  var_dump(i);
+}
+
+EOF;
+
+$v8->executeString($js);
+
+?>
+===EOF===
+--EXPECT--
+string(1) "0"
+string(1) "1"
+string(1) "2"
+string(1) "4"
+string(1) "0"
+string(1) "1"
+string(1) "2"
+string(1) "4"
+===EOF===

+ 97 - 10
v8js_array_access.cc

@@ -131,15 +131,9 @@ void php_v8js_array_access_setter(uint32_t index, v8::Local<v8::Value> value,
 }
 /* }}} */
 
-void php_v8js_array_access_length(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) /* {{{ */
-{
-	v8::Isolate *isolate = info.GetIsolate();
-	v8::Local<v8::Object> self = info.Holder();
-
-	V8JS_TSRMLS_FETCH();
 
-	v8::Local<v8::Value> php_object = self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY));
-	zval *object = reinterpret_cast<zval *>(v8::External::Cast(*php_object)->Value());
+static int php_v8js_array_access_get_count_result(zval *object TSRMLS_DC) /* {{{ */
+{
 	zend_class_entry *ce = Z_OBJCE_P(object);
 
 	zend_fcall_info fci;
@@ -163,13 +157,106 @@ void php_v8js_array_access_length(v8::Local<v8::String> property, const v8::Prop
 
 	zend_call_function(&fci, NULL TSRMLS_CC);
 
-	v8::Local<v8::Value> ret_value = zval_to_v8js(php_value, isolate TSRMLS_CC);
+	if(Z_TYPE_P(php_value) != IS_LONG) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Non-numeric return value from count() method");
+		return 0;
+	}
+
+	int result = Z_LVAL_P(php_value);
 	zval_ptr_dtor(&php_value);
 
-	info.GetReturnValue().Set(ret_value);
+	return result;
+}
+/* }}} */
+
+static bool php_v8js_array_access_isset_p(zval *object, int index TSRMLS_DC) /* {{{ */
+{
+	zend_class_entry *ce = Z_OBJCE_P(object);
+
+	/* Okay, let's call offsetExists. */
+	zend_fcall_info fci;
+	zval *php_value;
+
+	zval fmember;
+	INIT_ZVAL(fmember);
+	ZVAL_STRING(&fmember, "offsetExists", 0);
+
+	zval zindex;
+	INIT_ZVAL(zindex);
+	ZVAL_LONG(&zindex, index);
+
+	fci.size = sizeof(fci);
+	fci.function_table = &ce->function_table;
+	fci.function_name = &fmember;
+	fci.symbol_table = NULL;
+	fci.retval_ptr_ptr = &php_value;
+
+	zval *zindex_ptr = &zindex;
+	zval **zindex_ptr_ptr = &zindex_ptr;
+	fci.param_count = 1;
+	fci.params = &zindex_ptr_ptr;
+
+	fci.object_ptr = object;
+	fci.no_separation = 0;
+
+	zend_call_function(&fci, NULL TSRMLS_CC);
+
+	if(Z_TYPE_P(php_value) != IS_BOOL) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Non-boolean return value from offsetExists() method");
+		return false;
+	}
+
+	bool result = Z_LVAL_P(php_value);
+	zval_ptr_dtor(&php_value);
+
+	return result;
 }
 /* }}} */
 
+
+void php_v8js_array_access_length(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) /* {{{ */
+{
+	v8::Isolate *isolate = info.GetIsolate();
+	v8::Local<v8::Object> self = info.Holder();
+
+	V8JS_TSRMLS_FETCH();
+
+	v8::Local<v8::Value> php_object = self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY));
+	zval *object = reinterpret_cast<zval *>(v8::External::Cast(*php_object)->Value());
+
+	int length = php_v8js_array_access_get_count_result(object TSRMLS_CC);
+	info.GetReturnValue().Set(V8JS_INT(length));
+}
+/* }}} */
+
+void php_v8js_array_access_enumerator(const v8::PropertyCallbackInfo<v8::Array>& info) /* {{{ */
+{
+	v8::Isolate *isolate = info.GetIsolate();
+	v8::Local<v8::Object> self = info.Holder();
+
+	V8JS_TSRMLS_FETCH();
+
+	v8::Local<v8::Value> php_object = self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY));
+	zval *object = reinterpret_cast<zval *>(v8::External::Cast(*php_object)->Value());
+
+	int length = php_v8js_array_access_get_count_result(object TSRMLS_CC);
+	v8::Local<v8::Array> result = v8::Array::New(isolate, length);
+
+	int i = 0;
+
+	for(int j = 0; j < length; j ++) {
+		if(php_v8js_array_access_isset_p(object, j TSRMLS_CC)) {
+			result->Set(i ++, V8JS_INT(j));
+		}
+	}
+
+	result->Set(V8JS_STR("length"), V8JS_INT(i));
+	info.GetReturnValue().Set(result);
+}
+/* }}} */
+
+
+
 void php_v8js_array_access_named_getter(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value> &info) /* {{{ */
 {
 	v8::String::Utf8Value cstr(property);

+ 2 - 0
v8js_array_access.h

@@ -20,6 +20,8 @@ void php_v8js_array_access_setter(uint32_t index, v8::Local<v8::Value> value,
 				  const v8::PropertyCallbackInfo<v8::Value>& info);
 void php_v8js_array_access_length(v8::Local<v8::String> property,
 				  const v8::PropertyCallbackInfo<v8::Value>& info);
+void php_v8js_array_access_enumerator(const v8::PropertyCallbackInfo<v8::Array>& info);
+
 
 /* Named Property Handlers */
 void php_v8js_array_access_named_getter(v8::Local<v8::String> property,

+ 10 - 2
v8js_object_export.cc

@@ -866,6 +866,7 @@ static v8::Handle<v8::Object> php_v8js_wrap_object(v8::Isolate *isolate, zend_cl
 
 			v8::Local<v8::ObjectTemplate> inst_tpl = new_tpl->InstanceTemplate();
 			v8::NamedPropertyGetterCallback getter = php_v8js_named_property_getter;
+			v8::NamedPropertyEnumeratorCallback enumerator = php_v8js_named_property_enumerator;
 
 			/* Check for ArrayAccess object */
 			if (V8JSG(use_array_access) && ce) {
@@ -883,12 +884,19 @@ static v8::Handle<v8::Object> php_v8js_wrap_object(v8::Isolate *isolate, zend_cl
 
 				if(has_array_access && has_countable) {
 					inst_tpl->SetIndexedPropertyHandler(php_v8js_array_access_getter,
-														php_v8js_array_access_setter);
+														php_v8js_array_access_setter,
+														0, /* query */
+														0, /* deleter */
+														php_v8js_array_access_enumerator);
 
 					/* Switch to special ArrayAccess getter, which falls back to
 					 * php_v8js_named_property_getter, but possibly bridges the
 					 * call to Array.prototype functions. */
 					getter = php_v8js_array_access_named_getter;
+
+					/* Overwrite enumerator, since for(... in ...) loop should
+					 * not see the methods but iterate over the elements. */
+					enumerator = 0;
 				}
 			}
 
@@ -899,7 +907,7 @@ static v8::Handle<v8::Object> php_v8js_wrap_object(v8::Isolate *isolate, zend_cl
 				 php_v8js_named_property_setter, /* setter */
 				 php_v8js_named_property_query, /* query */
 				 php_v8js_named_property_deleter, /* deleter */
-				 php_v8js_named_property_enumerator, /* enumerator */
+				 enumerator, /* enumerator */
 				 V8JS_NULL /* data */
 				 );
 			// add __invoke() handler