Browse Source

Cache and re-use v8::FunctionTemplate on object wrapping

Stefan Siegl 12 years ago
parent
commit
90ed152f40
1 changed files with 83 additions and 60 deletions
  1. 83 60
      v8js_convert.cc

+ 83 - 60
v8js_convert.cc

@@ -32,6 +32,8 @@ extern "C" {
 
 #include "php_v8js_macros.h"
 #include <v8.h>
+#include <map>
+#include <stdexcept>
 
 /* Callback for PHP methods and functions */
 static v8::Handle<v8::Value> php_v8js_php_callback(const v8::Arguments &args) /* {{{ */
@@ -278,7 +280,6 @@ static v8::Handle<v8::Integer> php_v8js_property_query(v8::Local<v8::String> pro
 
 static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *isolate TSRMLS_DC) /* {{{ */
 {
-	v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New();
 	v8::Local<v8::Object> newobj;
 	int i;
 	char *key = NULL;
@@ -303,75 +304,95 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
 
 	/* Object methods */
 	if (ce) {
-		new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length));
-		new_tpl->InstanceTemplate()->SetInternalFieldCount(2);
+		v8::Handle<v8::FunctionTemplate> new_tpl;
+		static std::map<const char *, v8::Persistent<v8::FunctionTemplate> > tpl_map;
+		std::map<const char *, v8::Persistent<v8::FunctionTemplate> >::iterator it;
 
-		if (ce == zend_ce_closure) {
-			new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback);
-			newobj = new_tpl->InstanceTemplate()->NewInstance();
-		} else {
-			zend_hash_internal_pointer_reset_ex(&ce->function_table, &pos);
-			for (;; zend_hash_move_forward_ex(&ce->function_table, &pos)) {
-				if (zend_hash_get_current_key_ex(&ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING  ||
-					zend_hash_get_current_data_ex(&ce->function_table, (void **) &method_ptr, &pos) == FAILURE
-				) {
-					break;
-				}
+		try {
+			new_tpl = tpl_map.at(ce->name);
+		}
+		catch (const std::out_of_range &) {
+			/* No cached v8::FunctionTemplate available as of yet, create one. */
+			new_tpl = v8::FunctionTemplate::New();
+
+			new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length));
+			new_tpl->InstanceTemplate()->SetInternalFieldCount(2);
+
+			if (ce == zend_ce_closure) {
+				/* Got a closure, mustn't cache ... */
+				new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback);
+			} else {
+				/* Add new v8::FunctionTemplate to tpl_map, as long as it is not a closure. */
+				tpl_map[ce->name] = v8::Persistent<v8::FunctionTemplate>::New(isolate, new_tpl);
+
+				/* Attach object methods to the instance template. */
+				zend_hash_internal_pointer_reset_ex(&ce->function_table, &pos);
+				for (;; zend_hash_move_forward_ex(&ce->function_table, &pos)) {
+					if (zend_hash_get_current_key_ex(&ce->function_table, &key, &key_len, &index, 0, &pos) != HASH_KEY_IS_STRING  ||
+						zend_hash_get_current_data_ex(&ce->function_table, (void **) &method_ptr, &pos) == FAILURE
+					) {
+						break;
+					}
 
-				if ((method_ptr->common.fn_flags & ZEND_ACC_PUBLIC)     && /* Allow only public methods */
-					(method_ptr->common.fn_flags & ZEND_ACC_CTOR) == 0  && /* ..and no __construct() */
-					(method_ptr->common.fn_flags & ZEND_ACC_DTOR) == 0  && /* ..or __destruct() */
-					(method_ptr->common.fn_flags & ZEND_ACC_CLONE) == 0 /* ..or __clone() functions */
-				) {
-					/* Override native toString() with __tostring() if it is set in passed object */
-					if (IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME)) {
-						new_tpl->InstanceTemplate()->Set(V8JS_SYM("toString"), PHP_V8JS_CALLBACK(method_ptr));
-					/* TODO: __set(), __unset() disabled as JS is not allowed to modify the passed PHP object yet.
-					 *  __sleep(), __wakeup(), __set_state() are always ignored */
-					} else if (
-						IS_MAGIC_FUNC(ZEND_CALLSTATIC_FUNC_NAME)|| /* TODO */
-						IS_MAGIC_FUNC(ZEND_SLEEP_FUNC_NAME)     ||
-						IS_MAGIC_FUNC(ZEND_WAKEUP_FUNC_NAME)    ||
-						IS_MAGIC_FUNC(ZEND_SET_STATE_FUNC_NAME) ||
-						IS_MAGIC_FUNC(ZEND_SET_FUNC_NAME)       ||
-						IS_MAGIC_FUNC(ZEND_UNSET_FUNC_NAME)
+					if ((method_ptr->common.fn_flags & ZEND_ACC_PUBLIC)     && /* Allow only public methods */
+						(method_ptr->common.fn_flags & ZEND_ACC_CTOR) == 0  && /* ..and no __construct() */
+						(method_ptr->common.fn_flags & ZEND_ACC_DTOR) == 0  && /* ..or __destruct() */
+						(method_ptr->common.fn_flags & ZEND_ACC_CLONE) == 0 /* ..or __clone() functions */
 					) {
-					/* Register all magic function as hidden with lowercase name */
-					} else if (IS_MAGIC_FUNC(ZEND_GET_FUNC_NAME)) {
-						get_ptr = method_ptr;
-					} else if (IS_MAGIC_FUNC(ZEND_CALL_FUNC_NAME)) {
-						call_ptr = method_ptr;
-					} else if (IS_MAGIC_FUNC(ZEND_INVOKE_FUNC_NAME)) {
-						invoke_ptr = method_ptr;
-					} else if (IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) {
-						isset_ptr = method_ptr;
-					} else {
-						new_tpl->InstanceTemplate()->Set(V8JS_STR(method_ptr->common.function_name), PHP_V8JS_CALLBACK(method_ptr), v8::ReadOnly);
+						/* Override native toString() with __tostring() if it is set in passed object */
+						if (IS_MAGIC_FUNC(ZEND_TOSTRING_FUNC_NAME)) {
+							new_tpl->InstanceTemplate()->Set(V8JS_SYM("toString"), PHP_V8JS_CALLBACK(method_ptr));
+						/* TODO: __set(), __unset() disabled as JS is not allowed to modify the passed PHP object yet.
+						 *  __sleep(), __wakeup(), __set_state() are always ignored */
+						} else if (
+							IS_MAGIC_FUNC(ZEND_CALLSTATIC_FUNC_NAME)|| /* TODO */
+							IS_MAGIC_FUNC(ZEND_SLEEP_FUNC_NAME)     ||
+							IS_MAGIC_FUNC(ZEND_WAKEUP_FUNC_NAME)    ||
+							IS_MAGIC_FUNC(ZEND_SET_STATE_FUNC_NAME) ||
+							IS_MAGIC_FUNC(ZEND_SET_FUNC_NAME)       ||
+							IS_MAGIC_FUNC(ZEND_UNSET_FUNC_NAME)
+						) {
+						/* Register all magic function as hidden with lowercase name */
+						} else if (IS_MAGIC_FUNC(ZEND_GET_FUNC_NAME)) {
+							get_ptr = method_ptr;
+						} else if (IS_MAGIC_FUNC(ZEND_CALL_FUNC_NAME)) {
+							call_ptr = method_ptr;
+						} else if (IS_MAGIC_FUNC(ZEND_INVOKE_FUNC_NAME)) {
+							invoke_ptr = method_ptr;
+						} else if (IS_MAGIC_FUNC(ZEND_ISSET_FUNC_NAME)) {
+							isset_ptr = method_ptr;
+						} else {
+							new_tpl->InstanceTemplate()->Set(V8JS_STR(method_ptr->common.function_name), PHP_V8JS_CALLBACK(method_ptr), v8::ReadOnly);
+						}
 					}
 				}
-			}
 
-			/* Only register getter, etc. when they're set in PHP side */
-			if (call_ptr || get_ptr || isset_ptr)
-			{
-				/* Set __get() handler which acts also as __call() proxy */
-				new_tpl->InstanceTemplate()->SetNamedPropertyHandler(
-					php_v8js_property_getter,					/* getter */
-					0,											/* setter */
-					isset_ptr ? php_v8js_property_query : 0,	/* query */
-					0,											/* deleter */
-					0,											/* enumerator */
-					V8JS_BOOL(call_ptr ? true : false)
-				);
-			}
 
-			/* __invoke() handler */
-			if (invoke_ptr) {
-				new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_property_caller, V8JS_SYM(ZEND_INVOKE_FUNC_NAME));
+				/* Only register getter, etc. when they're set in PHP side */
+				if (call_ptr || get_ptr || isset_ptr)
+				{
+					/* Set __get() handler which acts also as __call() proxy */
+					new_tpl->InstanceTemplate()->SetNamedPropertyHandler(
+						php_v8js_property_getter,					/* getter */
+						0,											/* setter */
+						isset_ptr ? php_v8js_property_query : 0,	/* query */
+						0,											/* deleter */
+						0,											/* enumerator */
+						V8JS_BOOL(call_ptr ? true : false)
+					);
+				}
+
+
+				/* __invoke() handler */
+				if (invoke_ptr) {
+					new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_property_caller, V8JS_SYM(ZEND_INVOKE_FUNC_NAME));
+				}
 			}
+		}
 
-			newobj = new_tpl->InstanceTemplate()->NewInstance();
+		newobj = new_tpl->InstanceTemplate()->NewInstance();
 
+		if (ce != zend_ce_closure) {	// @fixme all of those get lost, when using cached templates
 			if (call_ptr) {
 				newobj->SetHiddenValue(V8JS_SYM(ZEND_CALL_FUNC_NAME), PHP_V8JS_CALLBACK(call_ptr));
 			}
@@ -392,6 +413,8 @@ static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is
 		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
+
 		new_tpl->SetClassName(V8JS_SYM("Array"));
 		newobj = new_tpl->InstanceTemplate()->NewInstance();
 	}