Browse Source

Bug fixes for JavaScript var_dump implementation (make it match PHP).

C. Scott Ananian 11 years ago
parent
commit
27a140c9fb
2 changed files with 359 additions and 18 deletions
  1. 315 0
      tests/var_dump.phpt
  2. 44 18
      v8js_methods.cc

+ 315 - 0
tests/var_dump.phpt

@@ -0,0 +1,315 @@
+--TEST--
+Test V8::executeString() : var_dump
+--SKIPIF--
+<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
+--INI--
+date.timezone=UTC
+--FILE--
+<?php
+# Test var_dump of various types
+
+$JS = <<< EOT
+
+print("--- JS var_dump of PHP object ----\\n");
+var_dump(PHP.phptypes);
+
+print("--- JS var_dump of JS object ----\\n");
+var types = {
+	undefined: undefined,
+	null: null,
+	bool: true,
+	string: "string",
+	uint: 1,
+	int: -1,
+	number: 3.141592654,
+	// XXX this gets parsed with local timezone,
+	//     which is bad for test repeatability.
+	//date: new Date('September 27, 1976 09:00:00 GMT'),
+	regexp: /regexp/,
+	array: [1,2,3],
+	object: { field: "foo" },
+	function: function id(x) { return x; },
+	phpobject: PHP.obj
+};
+
+var_dump(types);
+print("--- PHP var_dump of JS object ----\\n");
+types;
+EOT;
+
+class Foo {
+	  var $field = "php";
+}
+
+$v8 = new V8Js();
+$v8->obj = new Foo;
+
+$phptypes = $v8->phptypes = array(
+	"null" => NULL,
+	"bool" => true,
+	"string" => "string",
+	"uint" => 1,
+	"int" => -1,
+	"number" => 3.141592654,
+	"date" => new DateTime('September 27, 1976 09:00:00 UTC', new DateTimeZone('UTC')),
+	//"regexp" => new Regexp('/regexp/'), /* no native PHP regex type */
+	"array" => array(1,2,3),
+	"object" => array( "field" => "foo" ),
+	"function" => (function ($x) { return $x; }),
+	"phpobject" => new Foo
+);
+
+echo "---- PHP var_dump of PHP object ----\n";
+var_dump($phptypes);
+
+try {
+	var_dump($v8->executeString($JS, 'var_dump.js'));
+} catch (V8JsScriptException $e) {
+	echo "Error!\n";
+	var_dump($e);
+}
+?>
+===EOF===
+--EXPECTF--
+---- PHP var_dump of PHP object ----
+array(11) {
+  ["null"]=>
+  NULL
+  ["bool"]=>
+  bool(true)
+  ["string"]=>
+  string(6) "string"
+  ["uint"]=>
+  int(1)
+  ["int"]=>
+  int(-1)
+  ["number"]=>
+  float(3.141592654)
+  ["date"]=>
+  object(DateTime)#%d (3) {
+    ["date"]=>
+    string(19) "1976-09-27 09:00:00"
+    ["timezone_type"]=>
+    int(3)
+    ["timezone"]=>
+    string(3) "UTC"
+  }
+  ["array"]=>
+  array(3) {
+    [0]=>
+    int(1)
+    [1]=>
+    int(2)
+    [2]=>
+    int(3)
+  }
+  ["object"]=>
+  array(1) {
+    ["field"]=>
+    string(3) "foo"
+  }
+  ["function"]=>
+  object(Closure)#%d (1) {
+    ["parameter"]=>
+    array(1) {
+      ["$x"]=>
+      string(10) "<required>"
+    }
+  }
+  ["phpobject"]=>
+  object(Foo)#%d (1) {
+    ["field"]=>
+    string(3) "php"
+  }
+}
+--- JS var_dump of PHP object ----
+array (11) {
+  ["null"] =>
+  NULL
+  ["bool"] =>
+  bool(true)
+  ["string"] =>
+  string(6) "string"
+  ["uint"] =>
+  int(1)
+  ["int"] =>
+  int(-1)
+  ["number"] =>
+  float(3.141593)
+  ["date"] =>
+  object(DateTime)#%d (18) {
+    ["createFromFormat"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["getLastErrors"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["format"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["modify"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["add"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["sub"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["getTimezone"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["setTimezone"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["getOffset"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["setTime"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["setDate"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["setISODate"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["setTimestamp"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["getTimestamp"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["diff"] =>
+    object(Closure)#%d {
+        function () { [native code] }
+    }
+    ["date"] =>
+    string(19) "1976-09-27 09:00:00"
+    ["timezone_type"] =>
+    int(3)
+    ["timezone"] =>
+    string(3) "UTC"
+  }
+  ["array"] =>
+  array(3) {
+    [0] =>
+    int(1)
+    [1] =>
+    int(2)
+    [2] =>
+    int(3)
+  }
+  ["object"] =>
+  array (1) {
+    ["field"] =>
+    string(3) "foo"
+  }
+  ["function"] =>
+  object(Closure)#%d (0) {
+  }
+  ["phpobject"] =>
+  object(Foo)#%d (1) {
+    ["field"] =>
+    string(3) "php"
+  }
+}
+--- JS var_dump of JS object ----
+object(Object)#%d (12) {
+  ["undefined"] =>
+  NULL
+  ["null"] =>
+  NULL
+  ["bool"] =>
+  bool(true)
+  ["string"] =>
+  string(6) "string"
+  ["uint"] =>
+  int(1)
+  ["int"] =>
+  int(-1)
+  ["number"] =>
+  float(3.141593)
+  ["regexp"] =>
+  regexp(/regexp/)
+  ["array"] =>
+  array(3) {
+    [0] =>
+    int(1)
+    [1] =>
+    int(2)
+    [2] =>
+    int(3)
+  }
+  ["object"] =>
+  object(Object)#%d (1) {
+    ["field"] =>
+    string(3) "foo"
+  }
+  ["function"] =>
+  object(Closure)#%d {
+      function id(x) { return x; }
+  }
+  ["phpobject"] =>
+  object(Foo)#%d (1) {
+    ["field"] =>
+    string(3) "php"
+  }
+}
+--- PHP var_dump of JS object ----
+object(V8Object)#%d (12) {
+  ["undefined"]=>
+  NULL
+  ["null"]=>
+  NULL
+  ["bool"]=>
+  bool(true)
+  ["string"]=>
+  string(6) "string"
+  ["uint"]=>
+  int(1)
+  ["int"]=>
+  int(-1)
+  ["number"]=>
+  float(3.141592654)
+  ["regexp"]=>
+  object(V8Object)#%d (0) {
+  }
+  ["array"]=>
+  array(3) {
+    [0]=>
+    int(1)
+    [1]=>
+    int(2)
+    [2]=>
+    int(3)
+  }
+  ["object"]=>
+  object(V8Object)#%d (1) {
+    ["field"]=>
+    string(3) "foo"
+  }
+  ["function"]=>
+  object(V8Function)#%d (0) {
+  }
+  ["phpobject"]=>
+  object(Foo)#%d (1) {
+    ["field"]=>
+    string(3) "php"
+  }
+}
+===EOF===

+ 44 - 18
v8js_methods.cc

@@ -61,40 +61,59 @@ static void _php_v8js_dumper(v8::Local<v8::Value> var, int level TSRMLS_DC) /* {
 		php_printf("%*c", (level - 1) * 2, ' ');
 		php_printf("%*c", (level - 1) * 2, ' ');
 	}
 	}
 
 
-	if (var->IsNull())
+	if (var.IsEmpty())
+	{
+		php_printf("<empty>\n");
+		return;
+	}
+	if (var->IsNull() || var->IsUndefined() /* PHP compat */)
 	{
 	{
 		php_printf("NULL\n");
 		php_printf("NULL\n");
 		return;
 		return;
 	}
 	}
-
-	v8::String::Utf8Value str(var->ToDetailString());
-	const char *valstr = ToCString(str);
-	size_t valstr_len = (valstr) ? strlen(valstr) : 0;
-
-	if (var->IsString())
+	if (var->IsInt32())
 	{
 	{
-		php_printf("string(%zu) \"%s\"\n", valstr_len, valstr);
+		php_printf("int(%ld)\n", (long) var->IntegerValue());
+		return;
+	}
+	if (var->IsUint32())
+	{
+		php_printf("int(%lu)\n", (unsigned long) var->IntegerValue());
+		return;
 	}
 	}
-	else if (var->IsBoolean())
+	if (var->IsNumber())
 	{
 	{
-		php_printf("bool(%s)\n", valstr);
+		php_printf("float(%f)\n", var->NumberValue());
+		return;
 	}
 	}
-	else if (var->IsInt32() || var->IsUint32())
+	if (var->IsBoolean())
 	{
 	{
-		php_printf("int(%s)\n", valstr);
+		php_printf("bool(%s)\n", var->BooleanValue() ? "true" : "false");
+		return;
+	}
+
+	v8::TryCatch try_catch; /* object.toString() can throw an exception */
+	v8::Local<v8::String> details = var->ToDetailString();
+	if (try_catch.HasCaught()) {
+		details = V8JS_SYM("<toString threw exception>");
 	}
 	}
-	else if (var->IsNumber())
+	v8::String::Utf8Value str(details);
+	const char *valstr = ToCString(str);
+	size_t valstr_len = (valstr) ? strlen(valstr) : 0;
+
+	if (var->IsString())
 	{
 	{
-		php_printf("float(%s)\n", valstr);
+		php_printf("string(%zu) \"%s\"\n", valstr_len, valstr);
 	}
 	}
 	else if (var->IsDate())
 	else if (var->IsDate())
 	{
 	{
+		// fake the fields of a PHP DateTime
 		php_printf("Date(%s)\n", valstr);
 		php_printf("Date(%s)\n", valstr);
 	}
 	}
 #if PHP_V8_API_VERSION >= 2003007
 #if PHP_V8_API_VERSION >= 2003007
 	else if (var->IsRegExp())
 	else if (var->IsRegExp())
 	{
 	{
-		php_printf("RegExp(%s)\n", valstr);
+		php_printf("regexp(%s)\n", valstr);
 	}
 	}
 #endif
 #endif
 	else if (var->IsArray())
 	else if (var->IsArray())
@@ -119,18 +138,25 @@ static void _php_v8js_dumper(v8::Local<v8::Value> var, int level TSRMLS_DC) /* {
 	{
 	{
 		v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(var);
 		v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(var);
 		V8JS_GET_CLASS_NAME(cname, object);
 		V8JS_GET_CLASS_NAME(cname, object);
+		int hash = object->GetIdentityHash();
 
 
 		if (var->IsFunction())
 		if (var->IsFunction())
 		{
 		{
 			v8::String::Utf8Value csource(object->ToString());
 			v8::String::Utf8Value csource(object->ToString());
-			php_printf("object(%s)#%d {\n%*c%s\n", ToCString(cname), object->GetIdentityHash(), level * 2 + 2, ' ', ToCString(csource));
+			php_printf("object(Closure)#%d {\n%*c%s\n", hash, level * 2 + 2, ' ', ToCString(csource));
 		}
 		}
 		else
 		else
 		{
 		{
-			v8::Local<v8::Array> keys = object->GetPropertyNames();
+			v8::Local<v8::Array> keys = object->GetOwnPropertyNames();
 			uint32_t length = keys->Length();
 			uint32_t length = keys->Length();
 
 
-			php_printf("object(%s)#%d (%d) {\n", ToCString(cname), object->GetIdentityHash(), length);
+			if (strcmp(ToCString(cname), "Array") == 0 ||
+				strcmp(ToCString(cname), "V8Object") == 0) {
+				php_printf("array");
+			} else {
+				php_printf("object(%s)#%d", ToCString(cname), hash);
+			}
+			php_printf(" (%d) {\n", length);
 
 
 			for (unsigned i = 0; i < length; i++) {
 			for (unsigned i = 0; i < length; i++) {
 				v8::Local<v8::String> key = keys->Get(i)->ToString();
 				v8::Local<v8::String> key = keys->Get(i)->ToString();