0
0
mirror of https://github.com/phpv8/v8js.git synced 2024-12-22 08:11:52 +00:00

Initial import

This commit is contained in:
Jani Taskinen 2010-12-30 14:04:51 +00:00
commit 1c23a38026
51 changed files with 5426 additions and 0 deletions

2
CREDITS Normal file
View File

@ -0,0 +1,2 @@
v8js
Jani Taskinen

11
Makefile.frag Normal file
View File

@ -0,0 +1,11 @@
testv8: all
$(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
debugv8: all
gdb --arg $(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
valgrindv8: all
USE_ZEND_ALLOC=0 valgrind --leak-check=full --show-reachable=yes --track-origins=yes $(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php 2> valgrind.dump

64
README Normal file
View File

@ -0,0 +1,64 @@
V8Js
====
This is a PHP extension for Google's V8 Javascript engine
Minimum requirements
--------------------
- V8 library version 2.5.8 <http://code.google.com/p/v8/> (trunk)
- PHP 5.3.3+ (non-ZTS build preferred)
Note: V8 engine is not natively thread safe and this extension
has not been designed to work around it either yet and might or
might not work properly with ZTS enabled PHP. :)
API
===
class V8Js
{
/* Constants */
const string V8_VERSION;
const int FLAG_NONE;
const int FLAG_FORCE_ARRAY;
/* Methods */
// Initializes and starts V8 engine and Returns new V8Js object with it's own V8 context.
public __construct ( [string object_name = "PHP" [, array variables = NULL [, array extensions = NULL [, bool report_uncaught_exceptions = TRUE]]] )
// Compiles and executes script in object's context with optional identifier string.
public mixed V8Js::executeString( string script [, string identifier [, int flags = V8Js::FLAG_NONE]])
// Returns uncaught pending exception or null if there is no pending exception.
public V8JsException V8Js::getPendingException( void )
/** Static methods **/
// Registers persistent context independent global Javascript extension.
// NOTE! These extensions exist until PHP is shutdown and they need to be registered before V8 is initialized.
// For best performance V8 is initialized only once per process thus this call has to be done before any V8Js objects are created!
public static bool V8Js::registerExtension(string ext_name, string script [, array deps [, bool auto_enable = FALSE]])
// Returns extensions successfully registered with V8Js::registerExtension().
public static array V8Js::getExtensions( void )
}
final class V8JsException extends Exception
{
/* Properties */
protected string JsFileName = NULL;
protected int JsLineNumber = NULL;
protected string JsSourceLine = NULL;
protected string JsTrace = NULL;
/* Methods */
final public string getJsFileName( void )
final public int getJsLineNumber( void )
final public string getJsSourceLine( void )
final public string getJsTrace( void )
}

5
TODO Normal file
View File

@ -0,0 +1,5 @@
- Feature: Extension registering from php.ini
- Feature: Thread safety
- Missing: Indexed property handlers
- Bug: exception propagation fails when property getter is set
- Bug: method_exists() leaks when used with V8 objects

73
config.m4 Normal file
View File

@ -0,0 +1,73 @@
dnl $Id$
PHP_ARG_WITH(v8js, for V8 Javascript Engine,
[ --with-v8js Include V8 JavaScript Engine])
if test "$PHP_V8JS" != "no"; then
SEARCH_PATH="/usr/local /usr"
SEARCH_FOR="/include/v8.h"
if test -r $PHP_V8JS/$SEARCH_FOR; then
V8_DIR=$PHP_V8JS
else
AC_MSG_CHECKING([for V8 files in default path])
for i in $SEARCH_PATH ; do
if test -r $i/$SEARCH_FOR; then
V8_DIR=$i
AC_MSG_RESULT(found in $i)
fi
done
fi
if test -z "$V8_DIR"; then
AC_MSG_RESULT([not found])
AC_MSG_ERROR([Please reinstall the v8 distribution])
fi
PHP_ADD_INCLUDE($V8_DIR/include)
PHP_ADD_LIBRARY_WITH_PATH(v8, $V8_DIR/$PHP_LIBDIR, V8JS_SHARED_LIBADD)
PHP_SUBST(V8JS_SHARED_LIBADD)
PHP_REQUIRE_CXX()
AC_CACHE_CHECK(for V8 version, ac_cv_v8_version, [
old_LIBS=$LIBS
old_LDFLAGS=$LDFLAGS
LDFLAGS=-L$V8_DIR/$PHP_LIBDIR
LIBS=-lv8
AC_LANG_SAVE
AC_LANG_CPLUSPLUS
AC_TRY_RUN([#include <v8.h>
#include <iostream>
#include <fstream>
using namespace std;
int main ()
{
ofstream testfile ("conftestval");
if (testfile.is_open()) {
testfile << v8::V8::GetVersion();
testfile << "\n";
testfile.close();
return 0;
}
return 1;
}], [ac_cv_v8_version=`cat ./conftestval`], [ac_cv_v8_version=NONE], [ac_cv_v8_version=NONE])
AC_LANG_RESTORE
LIBS=$old_LIBS
LDFLAGS=$old_LDFLAGS
])
if test "$ac_cv_v8_version" != "NONE"; then
ac_IFS=$IFS
IFS=.
set $ac_cv_v8_version
IFS=$ac_IFS
V8_API_VERSION=`expr [$]1 \* 1000000 + [$]2 \* 1000 + [$]3`
AC_DEFINE_UNQUOTED([PHP_V8_API_VERSION], $V8_API_VERSION, [ ])
AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ])
fi
PHP_NEW_EXTENSION(v8js, v8js.cc v8js_convert.cc v8js_methods.cc v8js_variables.cc, $ext_shared)
PHP_ADD_MAKEFILE_FRAGMENT
fi

839
js/json-template.js Normal file
View File

@ -0,0 +1,839 @@
// Copyright (C) 2009 Andy Chu
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// $Id$
//
// JavaScript implementation of json-template.
//
// This is predefined in tests, shouldn't be defined anywhere else. TODO: Do
// something nicer.
var log = log || function() {};
var repr = repr || function() {};
// The "module" exported by this script is called "jsontemplate":
var jsontemplate = function() {
// Regex escaping for metacharacters
function EscapeMeta(meta) {
return meta.replace(/([\{\}\(\)\[\]\|\^\$\-\+\?])/g, '\\$1');
}
var token_re_cache = {};
function _MakeTokenRegex(meta_left, meta_right) {
var key = meta_left + meta_right;
var regex = token_re_cache[key];
if (regex === undefined) {
var str = '(' + EscapeMeta(meta_left) + '.*?' + EscapeMeta(meta_right) +
'\n?)';
regex = new RegExp(str, 'g');
}
return regex;
}
//
// Formatters
//
function HtmlEscape(s) {
return s.replace(/&/g,'&amp;').
replace(/>/g,'&gt;').
replace(/</g,'&lt;');
}
function HtmlTagEscape(s) {
return s.replace(/&/g,'&amp;').
replace(/>/g,'&gt;').
replace(/</g,'&lt;').
replace(/"/g,'&quot;');
}
// Default ToString can be changed
function ToString(s) {
if (s === null) {
return 'null';
}
return s.toString();
}
// Formatter to pluralize words
function _Pluralize(value, unused_context, args) {
var s, p;
switch (args.length) {
case 0:
s = ''; p = 's';
break;
case 1:
s = ''; p = args[0];
break;
case 2:
s = args[0]; p = args[1];
break;
default:
// Should have been checked at compile time
throw {
name: 'EvaluationError', message: 'pluralize got too many args'
};
}
return (value > 1) ? p : s;
}
function _Cycle(value, unused_context, args) {
// Cycle between various values on consecutive integers.
// @index starts from 1, so use 1-based indexing.
return args[(value - 1) % args.length];
}
var DEFAULT_FORMATTERS = {
'html': HtmlEscape,
'htmltag': HtmlTagEscape,
'html-attr-value': HtmlTagEscape,
'str': ToString,
'raw': function(x) { return x; },
'AbsUrl': function(value, context) {
// TODO: Normalize leading/trailing slashes
return context.get('base-url') + '/' + value;
}
};
var DEFAULT_PREDICATES = {
'singular?': function(x) { return x == 1; },
'plural?': function(x) { return x > 1; },
'Debug?': function(unused, context) {
try {
return context.get('debug');
} catch(err) {
if (err.name == 'UndefinedVariable') {
return false;
} else {
throw err;
}
}
}
};
var FunctionRegistry = function() {
return {
lookup: function(user_str) {
return [null, null];
}
};
};
var SimpleRegistry = function(obj) {
return {
lookup: function(user_str) {
var func = obj[user_str] || null;
return [func, null];
}
};
};
var CallableRegistry = function(callable) {
return {
lookup: function(user_str) {
var func = callable(user_str);
return [func, null];
}
};
};
// Default formatters which can't be expressed in DEFAULT_FORMATTERS
var PrefixRegistry = function(functions) {
return {
lookup: function(user_str) {
for (var i = 0; i < functions.length; i++) {
var name = functions[i].name, func = functions[i].func;
if (user_str.slice(0, name.length) == name) {
// Delimiter is usually a space, but could be something else
var args;
var splitchar = user_str.charAt(name.length);
if (splitchar === '') {
args = []; // No arguments
} else {
args = user_str.split(splitchar).slice(1);
}
return [func, args];
}
}
return [null, null]; // No formatter
}
};
};
var ChainedRegistry = function(registries) {
return {
lookup: function(user_str) {
for (var i=0; i<registries.length; i++) {
var result = registries[i].lookup(user_str);
if (result[0]) {
return result;
}
}
return [null, null]; // Nothing found
}
};
};
//
// Template implementation
//
function _ScopedContext(context, undefined_str) {
// The stack contains:
// The current context (an object).
// An iteration index. -1 means we're NOT iterating.
var stack = [{context: context, index: -1}];
return {
PushSection: function(name) {
if (name === undefined || name === null) {
return null;
}
var new_context;
if (name == '@') {
new_context = stack[stack.length-1].context;
} else {
new_context = stack[stack.length-1].context[name] || null;
}
stack.push({context: new_context, index: -1});
return new_context;
},
Pop: function() {
stack.pop();
},
next: function() {
var stacktop = stack[stack.length-1];
// Now we're iterating -- push a new mutable object onto the stack
if (stacktop.index == -1) {
stacktop = {context: null, index: 0};
stack.push(stacktop);
}
// The thing we're iterating over
var context_array = stack[stack.length-2].context;
// We're already done
if (stacktop.index == context_array.length) {
stack.pop();
return undefined; // sentinel to say that we're done
}
stacktop.context = context_array[stacktop.index++];
return true; // OK, we mutated the stack
},
_Undefined: function(name) {
if (undefined_str === undefined) {
throw {
name: 'UndefinedVariable', message: name + ' is not defined'
};
} else {
return undefined_str;
}
},
_LookUpStack: function(name) {
var i = stack.length - 1;
while (true) {
var frame = stack[i];
if (name == '@index') {
if (frame.index != -1) { // -1 is undefined
return frame.index;
}
} else {
var context = frame.context;
if (typeof context === 'object') {
var value = context[name];
if (value !== undefined) {
return value;
}
}
}
i--;
if (i <= -1) {
return this._Undefined(name);
}
}
},
get: function(name) {
if (name == '@') {
return stack[stack.length-1].context;
}
var parts = name.split('.');
var value = this._LookUpStack(parts[0]);
if (parts.length > 1) {
for (var i=1; i<parts.length; i++) {
value = value[parts[i]];
if (value === undefined) {
return this._Undefined(parts[i]);
}
}
}
return value;
}
};
}
// Crockford's "functional inheritance" pattern
var _AbstractSection = function(spec) {
var that = {};
that.current_clause = [];
that.Append = function(statement) {
that.current_clause.push(statement);
};
that.AlternatesWith = function() {
throw {
name: 'TemplateSyntaxError',
message:
'{.alternates with} can only appear with in {.repeated section ...}'
};
};
that.NewOrClause = function(pred) {
throw { name: 'NotImplemented' }; // "Abstract"
};
return that;
};
var _Section = function(spec) {
var that = _AbstractSection(spec);
that.statements = {'default': that.current_clause};
that.section_name = spec.section_name;
that.Statements = function(clause) {
clause = clause || 'default';
return that.statements[clause] || [];
};
that.NewOrClause = function(pred) {
if (pred) {
throw {
name: 'TemplateSyntaxError',
message: '{.or} clause only takes a predicate inside predicate blocks'
};
}
that.current_clause = [];
that.statements['or'] = that.current_clause;
};
return that;
};
// Repeated section is like section, but it supports {.alternates with}
var _RepeatedSection = function(spec) {
var that = _Section(spec);
that.AlternatesWith = function() {
that.current_clause = [];
that.statements['alternate'] = that.current_clause;
};
return that;
};
// Represents a sequence of predicate clauses.
var _PredicateSection = function(spec) {
var that = _AbstractSection(spec);
// Array of func, statements
that.clauses = [];
that.NewOrClause = function(pred) {
// {.or} always executes if reached, so use identity func with no args
pred = pred || [function(x) { return true; }, null];
that.current_clause = [];
that.clauses.push([pred, that.current_clause]);
};
return that;
};
function _Execute(statements, context, callback) {
for (var i=0; i<statements.length; i++) {
var statement = statements[i];
if (typeof(statement) == 'string') {
callback(statement);
} else {
var func = statement[0];
var args = statement[1];
func(args, context, callback);
}
}
}
function _DoSubstitute(statement, context, callback) {
var value;
value = context.get(statement.name);
// Format values
for (var i=0; i<statement.formatters.length; i++) {
var pair = statement.formatters[i];
var formatter = pair[0];
var args = pair[1];
value = formatter(value, context, args);
}
callback(value);
}
// for [section foo]
function _DoSection(args, context, callback) {
var block = args;
var value = context.PushSection(block.section_name);
var do_section = false;
// "truthy" values should have their sections executed.
if (value) {
do_section = true;
}
// Except: if the value is a zero-length array (which is "truthy")
if (value && value.length === 0) {
do_section = false;
}
if (do_section) {
_Execute(block.Statements(), context, callback);
context.Pop();
} else { // Empty list, None, False, etc.
context.Pop();
_Execute(block.Statements('or'), context, callback);
}
}
// {.pred1?} A {.or pred2?} B ... {.or} Z {.end}
function _DoPredicates(args, context, callback) {
// Here we execute the first clause that evaluates to true, and then stop.
var block = args;
var value = context.get('@');
for (var i=0; i<block.clauses.length; i++) {
var clause = block.clauses[i];
var predicate = clause[0][0];
var pred_args = clause[0][1];
var statements = clause[1];
var do_clause = predicate(value, context, pred_args);
if (do_clause) {
_Execute(statements, context, callback);
break;
}
}
}
function _DoRepeatedSection(args, context, callback) {
var block = args;
items = context.PushSection(block.section_name);
pushed = true;
if (items && items.length > 0) {
// TODO: check that items is an array; apparently this is hard in JavaScript
//if type(items) is not list:
// raise EvaluationError('Expected a list; got %s' % type(items))
// Execute the statements in the block for every item in the list.
// Execute the alternate block on every iteration except the last. Each
// item could be an atom (string, integer, etc.) or a dictionary.
var last_index = items.length - 1;
var statements = block.Statements();
var alt_statements = block.Statements('alternate');
for (var i=0; context.next() !== undefined; i++) {
_Execute(statements, context, callback);
if (i != last_index) {
_Execute(alt_statements, context, callback);
}
}
} else {
_Execute(block.Statements('or'), context, callback);
}
context.Pop();
}
var _SECTION_RE = /(repeated)?\s*(section)\s+(\S+)?/;
var _OR_RE = /or(?:\s+(.+))?/;
var _IF_RE = /if(?:\s+(.+))?/;
// Turn a object literal, function, or Registry into a Registry
function MakeRegistry(obj) {
if (!obj) {
// if null/undefined, use a totally empty FunctionRegistry
return new FunctionRegistry();
} else if (typeof obj === 'function') {
return new CallableRegistry(obj);
} else if (obj.lookup !== undefined) {
// TODO: Is this a good pattern? There is a namespace conflict where get
// could be either a formatter or a method on a FunctionRegistry.
// instanceof might be more robust.
return obj;
} else if (typeof obj === 'object') {
return new SimpleRegistry(obj);
}
}
// TODO: The compile function could be in a different module, in case we want to
// compile on the server side.
function _Compile(template_str, options) {
var more_formatters = MakeRegistry(options.more_formatters);
// default formatters with arguments
var default_formatters = PrefixRegistry([
{name: 'pluralize', func: _Pluralize},
{name: 'cycle', func: _Cycle}
]);
var all_formatters = new ChainedRegistry([
more_formatters,
SimpleRegistry(DEFAULT_FORMATTERS),
default_formatters
]);
var more_predicates = MakeRegistry(options.more_predicates);
// TODO: Add defaults
var all_predicates = new ChainedRegistry([
more_predicates, SimpleRegistry(DEFAULT_PREDICATES)
]);
// We want to allow an explicit null value for default_formatter, which means
// that an error is raised if no formatter is specified.
var default_formatter;
if (options.default_formatter === undefined) {
default_formatter = 'str';
} else {
default_formatter = options.default_formatter;
}
function GetFormatter(format_str) {
var pair = all_formatters.lookup(format_str);
if (!pair[0]) {
throw {
name: 'BadFormatter',
message: format_str + ' is not a valid formatter'
};
}
return pair;
}
function GetPredicate(pred_str) {
var pair = all_predicates.lookup(pred_str);
if (!pair[0]) {
throw {
name: 'BadPredicate',
message: pred_str + ' is not a valid predicate'
};
}
return pair;
}
var format_char = options.format_char || '|';
if (format_char != ':' && format_char != '|') {
throw {
name: 'ConfigurationError',
message: 'Only format characters : and | are accepted'
};
}
var meta = options.meta || '{}';
var n = meta.length;
if (n % 2 == 1) {
throw {
name: 'ConfigurationError',
message: meta + ' has an odd number of metacharacters'
};
}
var meta_left = meta.substring(0, n/2);
var meta_right = meta.substring(n/2, n);
var token_re = _MakeTokenRegex(meta_left, meta_right);
var current_block = _Section({});
var stack = [current_block];
var strip_num = meta_left.length; // assume they're the same length
var token_match;
var last_index = 0;
while (true) {
token_match = token_re.exec(template_str);
if (token_match === null) {
break;
} else {
var token = token_match[0];
}
// Add the previous literal to the program
if (token_match.index > last_index) {
var tok = template_str.slice(last_index, token_match.index);
current_block.Append(tok);
}
last_index = token_re.lastIndex;
var had_newline = false;
if (token.slice(-1) == '\n') {
token = token.slice(null, -1);
had_newline = true;
}
token = token.slice(strip_num, -strip_num);
if (token.charAt(0) == '#') {
continue; // comment
}
if (token.charAt(0) == '.') { // Keyword
token = token.substring(1, token.length);
var literal = {
'meta-left': meta_left,
'meta-right': meta_right,
'space': ' ',
'tab': '\t',
'newline': '\n'
}[token];
if (literal !== undefined) {
current_block.Append(literal);
continue;
}
var new_block, func;
var section_match = token.match(_SECTION_RE);
if (section_match) {
var repeated = section_match[1];
var section_name = section_match[3];
if (repeated) {
func = _DoRepeatedSection;
new_block = _RepeatedSection({section_name: section_name});
} else {
func = _DoSection;
new_block = _Section({section_name: section_name});
}
current_block.Append([func, new_block]);
stack.push(new_block);
current_block = new_block;
continue;
}
var pred_str, pred;
// Check {.or pred?} before {.pred?}
var or_match = token.match(_OR_RE);
if (or_match) {
pred_str = or_match[1];
pred = pred_str ? GetPredicate(pred_str) : null;
current_block.NewOrClause(pred);
continue;
}
// Match either {.pred?} or {.if pred?}
var matched = false;
var if_match = token.match(_IF_RE);
if (if_match) {
pred_str = if_match[1];
matched = true;
} else if (token.charAt(token.length-1) == '?') {
pred_str = token;
matched = true;
}
if (matched) {
pred = pred_str ? GetPredicate(pred_str) : null;
new_block = _PredicateSection();
new_block.NewOrClause(pred);
current_block.Append([_DoPredicates, new_block]);
stack.push(new_block);
current_block = new_block;
continue;
}
if (token == 'alternates with') {
current_block.AlternatesWith();
continue;
}
if (token == 'end') {
// End the block
stack.pop();
if (stack.length > 0) {
current_block = stack[stack.length-1];
} else {
throw {
name: 'TemplateSyntaxError',
message: 'Got too many {end} statements'
};
}
continue;
}
}
// A variable substitution
var parts = token.split(format_char);
var formatters;
var name;
if (parts.length == 1) {
if (default_formatter === null) {
throw {
name: 'MissingFormatter',
message: 'This template requires explicit formatters.'
};
}
// If no formatter is specified, use the default.
formatters = [GetFormatter(default_formatter)];
name = token;
} else {
formatters = [];
for (var j=1; j<parts.length; j++) {
formatters.push(GetFormatter(parts[j]));
}
name = parts[0];
}
current_block.Append([_DoSubstitute, {name: name, formatters: formatters}]);
if (had_newline) {
current_block.Append('\n');
}
}
// Add the trailing literal
current_block.Append(template_str.slice(last_index));
if (stack.length !== 1) {
throw {
name: 'TemplateSyntaxError',
message: 'Got too few {end} statements'
};
}
return current_block;
}
// The Template class is defined in the traditional style so that users can add
// methods by mutating the prototype attribute. TODO: Need a good idiom for
// inheritance without mutating globals.
function Template(template_str, options) {
// Add 'new' if we were not called with 'new', so prototyping works.
if(!(this instanceof Template)) {
return new Template(template_str, options);
}
this._options = options || {};
this._program = _Compile(template_str, this._options);
}
Template.prototype.render = function(data_dict, callback) {
// options.undefined_str can either be a string or undefined
var context = _ScopedContext(data_dict, this._options.undefined_str);
_Execute(this._program.Statements(), context, callback);
};
Template.prototype.expand = function(data_dict) {
var tokens = [];
this.render(data_dict, function(x) { tokens.push(x); });
return tokens.join('');
};
// fromString is a construction method that allows metadata to be written at the
// beginning of the template string. See Python's FromFile for a detailed
// description of the format.
//
// The argument 'options' takes precedence over the options in the template, and
// can be used for non-serializable options like template formatters.
var OPTION_RE = /^([a-zA-Z\-]+):\s*(.*)/;
var OPTION_NAMES = [
'meta', 'format-char', 'default-formatter', 'undefined-str'];
// Use this "linear search" instead of Array.indexOf, which is nonstandard
var OPTION_NAMES_RE = new RegExp(OPTION_NAMES.join('|'));
function fromString(s, options) {
var parsed = {};
var begin = 0, end = 0;
while (true) {
var parsedOption = false;
end = s.indexOf('\n', begin);
if (end == -1) {
break;
}
var line = s.slice(begin, end);
begin = end+1;
var match = line.match(OPTION_RE);
if (match !== null) {
var name = match[1].toLowerCase(), value = match[2];
if (name.match(OPTION_NAMES_RE)) {
name = name.replace('-', '_');
value = value.replace(/^\s+/, '').replace(/\s+$/, '');
if (name == 'default_formatter' && value.toLowerCase() == 'none') {
value = null;
}
parsed[name] = value;
parsedOption = true;
}
}
if (!parsedOption) {
break;
}
}
// TODO: This doesn't enforce the blank line between options and template, but
// that might be more trouble than it's worth
if (parsed !== {}) {
body = s.slice(begin);
} else {
body = s;
}
for (var o in options) {
parsed[o] = options[o];
}
return Template(body, parsed);
}
// We just export one name for now, the Template "class".
// We need HtmlEscape in the browser tests, so might as well export it.
return {
Template: Template, HtmlEscape: HtmlEscape,
FunctionRegistry: FunctionRegistry, SimpleRegistry: SimpleRegistry,
CallableRegistry: CallableRegistry, ChainedRegistry: ChainedRegistry,
fromString: fromString,
// Private but exposed for testing
_Section: _Section
};
}();

339
js/jstparser.js Normal file
View File

@ -0,0 +1,339 @@
/*
Copyright 2008, mark turansky (www.markturansky.com)
Copyright 2010, Andrew Kelley (superjoesoftware.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
(This is the license from www.json.org and I think it's awesome)
*/
String.prototype.endsWith = function endsWith(c) {
if (this.charAt(this.length - 1) == c) {
return true;
} else {
return false;
}
};
String.prototype.startsWith = function startsWith(c) {
if (this.charAt(0) == c) {
return true;
} else {
return false;
}
};
RegExp.quote = function(str) {
return str.replace(/([.?*+\^$\[\]\\(){}\-])/g, "\\$1");
};
String.prototype.replaceAll = function replaceAll(a, b) {
return this.replace(new RegExp(RegExp.quote(a), 'g'), b);
};
var Jst = function () {
// private variables:
var that; // reference to the public object
// all write and writeln functions eval'd by Jst
// concatenate to this internal variable
var html = "";
// private functions
function CharacterStack(str) {
var i;
this.characters = [];
this.peek = function peek() {
return this.characters[this.characters.length - 1];
};
this.pop = function pop() {
return this.characters.pop();
};
this.push = function push(c) {
this.characters.push(c);
};
this.hasMore = function hasMore() {
if (this.characters.length > 0) {
return true;
} else {
return false;
}
};
for (i = str.length; i >= 0; i -= 1) {
this.characters.push(str.charAt(i));
}
}
function StringWriter() {
this.str = "";
this.write = function write(s) {
this.str += s;
};
this.toString = function toString() {
return this.str;
};
}
function parseScriptlet(stack) {
var fragment = new StringWriter();
var c; // character
while (stack.hasMore()) {
if (stack.peek() == '%') { //possible end delimiter
c = stack.pop();
if (stack.peek() == '>') { //end delimiter
// pop > so that it is not available to main parse loop
stack.pop();
if (stack.peek() == '\n') {
fragment.write(stack.pop());
}
break;
} else {
fragment.write(c);
}
} else {
fragment.write(stack.pop());
}
}
return fragment.toString();
}
function isOpeningDelimiter(c) {
if (c == "<" || c == "%lt;") {
return true;
} else {
return false;
}
}
function isClosingDelimiter(c) {
if (c == ">" || c == "%gt;") {
return true;
} else {
return false;
}
}
function appendExpressionFragment(writer, fragment, jstWriter) {
var i,j;
var c;
// check to be sure quotes are on both ends of a string literal
if (fragment.startsWith("\"") && !fragment.endsWith("\"")) {
//some scriptlets end with \n, especially if the script ends the file
if (fragment.endsWith("\n") && fragment.charAt(fragment.length - 2) == '"') {
//we're ok...
} else {
throw { "message":"'" + fragment + "' is not properly quoted"};
}
}
if (!fragment.startsWith("\"") && fragment.endsWith("\"")) {
throw { "message":"'" + fragment + "' is not properly quoted"};
}
// print or println?
if (fragment.endsWith("\n")) {
writer.write(jstWriter + "ln(");
//strip the newline
fragment = fragment.substring(0, fragment.length - 1);
} else {
writer.write(jstWriter + "(");
}
if (fragment.startsWith("\"") && fragment.endsWith("\"")) {
//strip the quotes
fragment = fragment.substring(1, fragment.length - 1);
writer.write("\"");
for (i = 0; i < fragment.length; i += 1) {
c = fragment.charAt(i);
if (c == '"') {
writer.write("\\");
writer.write(c);
}
}
writer.write("\"");
} else {
for (j = 0; j < fragment.length; j += 1) {
writer.write(fragment.charAt(j));
}
}
writer.write(");");
}
function appendTextFragment(writer, fragment) {
var i;
var c;
if (fragment.endsWith("\n")) {
writer.write("writeln(\"");
} else {
writer.write("write(\"");
}
for (i = 0; i < fragment.length; i += 1) {
c = fragment.charAt(i);
if (c == '"') {
writer.write("\\");
}
// we took care of the line break with print vs. println
if (c != '\n' && c != '\r') {
writer.write(c);
}
}
writer.write("\");");
}
function parseExpression(stack) {
var fragment = new StringWriter();
var c;
while (stack.hasMore()) {
if (stack.peek() == '%') { //possible end delimiter
c = stack.pop();
if (isClosingDelimiter(stack.peek())) { //end delimiter
//pop > so that it is not available to main parse loop
stack.pop();
if (stack.peek() == '\n') {
fragment.write(stack.pop());
}
break;
} else {
fragment.write("%");
}
} else {
fragment.write(stack.pop());
}
}
return fragment.toString();
}
function parseText(stack) {
var fragment = new StringWriter();
var c,d;
while (stack.hasMore()) {
if (isOpeningDelimiter(stack.peek())) { //possible delimiter
c = stack.pop();
if (stack.peek() == '%') { // delimiter!
// push c onto the stack to be used in main parse loop
stack.push(c);
break;
} else {
fragment.write(c);
}
} else {
d = stack.pop();
fragment.write(d);
if (d == '\n') { //done with this fragment. println it.
break;
}
}
}
return fragment.toString();
}
function safeWrite(s) {
s = s.toString();
s = s.replaceAll('&', '&amp;');
s = s.replaceAll('"', '&quot;');
s = s.replaceAll('<', '&lt;');
s = s.replaceAll('>', '&gt;');
html += s;
}
function safeWriteln(s) {
safeWrite(s + "\n");
}
function write(s) {
html += s;
}
function writeln(s) {
write(s + "\n");
}
that = {
// public methods:
// pre-compile a template for quicker rendering. save the return value and
// pass it to evaluate.
compile: function (src) {
var stack = new CharacterStack(src);
var writer = new StringWriter();
var c;
var fragment;
while (stack.hasMore()) {
if (isOpeningDelimiter(stack.peek())) { //possible delimiter
c = stack.pop();
if (stack.peek() == '%') { //delimiter!
c = stack.pop();
if (stack.peek() == "=") {
// expression, escape all html
stack.pop();
fragment = parseExpression(stack);
appendExpressionFragment(writer, fragment,
"safeWrite");
} else if (stack.peek() == "+") {
// expression, don't escape html
stack.pop();
fragment = parseExpression(stack);
appendExpressionFragment(writer, fragment,
"write");
} else {
fragment = parseScriptlet(stack);
writer.write(fragment);
}
} else { //not a delimiter
stack.push(c);
fragment = parseText(stack);
appendTextFragment(writer, fragment);
}
} else {
fragment = parseText(stack);
appendTextFragment(writer, fragment);
}
}
return writer.toString();
},
// evaluate a pre-compiled script. recommended approach
evaluate: function (script, args) {
with(args) {
html = "";
eval(script);
return html;
}
},
// if you're lazy, you can use this
evaluateSingleShot: function (src, args) {
return this.evaluate(this.compile(src), args);
}
};
return that;
}();

41
package.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0">
<package version="1.0" packagerversion="1.9.1">
<name>v8js</name>
<summary>V8 Javascript Engine for PHP</summary>
<description>This extension embeds the Google&apos;s open source JavaScript engine V8 in PHP.
</description>
<maintainers>
<maintainer>
<user>jani</user>
<name>Jani Taskinen</name>
<email>jani@php.net</email>
<role>lead</role>
</maintainer>
</maintainers>
<release>
<version>0.1.0</version>
<date>2010-12-02</date>
<license>PHP</license>
<state>beta</state>
<notes>- Initial PECL release
</notes>
<deps>
<dep type="php" rel="ge" version="5.3.3"/>
</deps>
<configureoptions>
<configureoption name="with-v8js" default="autodetect" prompt="Please provide the installation prefix of libv8"/>
</configureoptions>
<filelist>
<file role="src" name="config.m4"/>
<file role="doc" name="CREDITS"/>
<file role="src" name="Makefile.frag"/>
<file role="src" name="php_v8js.h"/>
<file role="src" name="php_v8js_macros.h"/>
<file role="src" name="v8js.cc"/>
<file role="src" name="v8js_convert.cc"/>
<file role="src" name="v8js_methods.cc"/>
<file role="src" name="v8js_variables.cc"/>
</filelist>
</release>
</package>

73
package2.xml Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.4.11" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>v8js</name>
<channel>pecl.php.net</channel>
<summary>V8 Javascript Engine for PHP</summary>
<description>
This extension embeds the Google's open source JavaScript engine V8 in PHP.
</description>
<lead>
<name>Jani Taskinen</name>
<user>jani</user>
<email>jani@php.net</email>
<active>yes</active>
</lead>
<date>2010-12-12</date>
<time>12:12:12</time>
<version>
<release>0.1.0</release>
<api>0.1.0</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<license uri="http://www.php.net/license">PHP</license>
<notes>- Initial PECL release
</notes>
<contents>
<dir name="/">
<file name="CREDITS" role="doc" />
<file name="config.m4" role="src" />
<file name="Makefile.frag" role="src" />
<file name="v8js.cc" role="src" />
<file name="v8js_convert.cc" role="src" />
<file name="v8js_methods.cc" role="src" />
<file name="v8js_variables.cc" role="src" />
<file name="php_v8js.h" role="src" />
<file name="php_v8js_macros.h" role="src" />
</dir> <!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.3.3</min>
</php>
<pearinstaller>
<min>1.4.0</min>
</pearinstaller>
</required>
</dependencies>
<providesextension>v8js</providesextension>
<extsrcrelease>
<configureoption default="autodetect" name="with-v8js" prompt="Please provide the installation prefix of libv8" />
</extsrcrelease>
<changelog>
<release>
<version>
<release>0.1.0</release>
<api>0.1.0</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
<date>2010-12-12</date>
<notes>- Initial PECL release.
</notes>
</release>
</changelog>
</package>

36
php_v8js.h Normal file
View File

@ -0,0 +1,36 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
+----------------------------------------------------------------------+
*/
/* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */
#ifndef PHP_V8JS_H
#define PHP_V8JS_H
extern zend_module_entry v8js_module_entry;
#define phpext_v8js_ptr &v8js_module_entry
#endif /* PHP_V8JS_H */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

100
php_v8js_macros.h Normal file
View File

@ -0,0 +1,100 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
+----------------------------------------------------------------------+
*/
/* $Id: header 297205 2010-03-30 21:09:07Z johannes $ */
#ifndef PHP_V8JS_MACROS_H
#define PHP_V8JS_MACROS_H
#include <v8.h>
/* V8Js Version */
#define V8JS_VERSION "0.1.0"
/* Helper macros */
#define V8JS_SYM(v) v8::String::NewSymbol(v, sizeof(v) - 1)
#define V8JS_SYML(v, l) v8::String::NewSymbol(v, l)
#define V8JS_STR(v) v8::String::New(v)
#define V8JS_STRL(v, l) v8::String::New(v, l)
#define V8JS_INT(v) v8::Integer::New(v)
#define V8JS_FLOAT(v) v8::Number::New(v)
#define V8JS_BOOL(v) v8::Boolean::New(v)
#define V8JS_NULL v8::Null()
#define V8JS_MN(name) v8js_method_##name
#define V8JS_METHOD(name) v8::Handle<v8::Value> V8JS_MN(name)(const v8::Arguments& args)
#define V8JS_THROW(type, message, message_len) v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len)))
#define V8JS_GLOBAL v8::Context::GetCurrent()->Global()
/* Helper macros */
#if PHP_V8_API_VERSION < 2005009
# define V8JS_GET_CLASS_NAME(var, obj) \
/* Hack to prevent calling possibly set user-defined __toString() messing class name */ \
v8::Local<v8::Function> constructor = v8::Local<v8::Function>::Cast(obj->Get(V8JS_SYM("constructor"))); \
v8::String::Utf8Value var(constructor->GetName());
#else
# define V8JS_GET_CLASS_NAME(var, obj) \
v8::String::Utf8Value var(obj->GetConstructorName());
#endif
/* Global flags */
#define V8JS_GLOBAL_SET_FLAGS(flags) V8JS_GLOBAL->SetHiddenValue(V8JS_SYM("__php_flags__"), V8JS_INT(flags))
#define V8JS_GLOBAL_GET_FLAGS() V8JS_GLOBAL->GetHiddenValue(V8JS_SYM("__php_flags__"))->IntegerValue();
/* Options */
#define V8JS_FLAG_NONE (1<<0)
#define V8JS_FLAG_FORCE_ARRAY (1<<1)
/* Extracts a C string from a V8 Utf8Value. */
static const char * ToCString(const v8::String::Utf8Value &value) /* {{{ */
{
return *value ? *value : "<string conversion failed>";
}
/* }}} */
/* Extern Class entries */
extern zend_class_entry *php_ce_v8_object;
extern zend_class_entry *php_ce_v8_function;
/* Create PHP V8 object */
void php_v8js_create_v8(zval *, v8::Handle<v8::Value>, int TSRMLS_DC);
/* Fetch V8 object properties */
int php_v8js_v8_get_properties_hash(v8::Handle<v8::Value>, HashTable *, int TSRMLS_DC);
/* Convert zval into V8 value */
v8::Handle<v8::Value> zval_to_v8js(zval * TSRMLS_DC);
/* Convert V8 value into zval */
int v8js_to_zval(v8::Handle<v8::Value>, zval *, int TSRMLS_DC);
/* Register builtin methods into passed object */
void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate>);
/* Register accessors into passed object */
void php_v8js_register_accessors(v8::Local<v8::ObjectTemplate>, zval * TSRMLS_DC);
#endif /* PHP_V8JS_MACROS_H */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

15
samples/dlopen.supp Normal file
View File

@ -0,0 +1,15 @@
{
Ignore dlopen bug #1.
Memcheck:Leak
...
fun:_dl_open
...
}
{
Ignore dlopen bug #2.
Memcheck:Leak
...
fun:_dl_close
...
}

74
samples/test_call.php Normal file
View File

@ -0,0 +1,74 @@
<?php
class Foo {
var $bar = 'foobar';
function MyOwnFunc() {}
function __Get($name) {
echo "Called __get(): ";
var_dump($name);
}
function __Set($name, $args) {
echo "Called __set(): ";
var_dump($name, $args);
}
function __Isset($name) {
echo "Called __isset(): ";
var_dump($name);
return true;
}
function __call($name, $args) {
echo "Called __call(): ";
var_dump($name, $args);
}
function __Invoke($name, $arg1, $arg2) {
echo "Called __invoke(): ";
var_dump(func_get_args());
return 'foobar';
}
function __toString() {
echo "Called __tostring: ";
return $this->bar;
}
}
$blaa = new V8Js();
$blaa->obj = new Foo;
try {
echo "__invoke()\n";
$blaa->executeString("var_dump(PHP.obj('arg1','arg2','arg3'));", "invoke_test1 #1.js");
echo "------------\n";
echo " __invoke() with new\n";
$blaa->executeString("myobj = new PHP.obj('arg1','arg2','arg3'); var_dump(myobj);", "invoke_test2 #2.js");
echo "------------\n";
echo " __tostring()\n";
$blaa->executeString('print(PHP.obj + "\n");', "tostring_test #3.js");
echo "------------\n";
echo " __isset() not called with existing property\n";
$blaa->executeString('if ("bar" in PHP.obj) print("bar exists\n");', "isset_test1 #4.js");
echo "------------\n";
echo " __isset() with non-existing property\n";
$blaa->executeString('if (!("foobar" in PHP.obj)) print("foobar does not exist\n"); else print("We called __isset and it said yes!\n");', "isset_test2 #5.js");
echo "------------\n";
echo " __get() not called with existing property\n";
$blaa->executeString('var_dump(PHP.obj.bar);', "get_test1 #6.js");
echo "------------\n";
echo " __get() with non-existing property\n";
$blaa->executeString('var_dump(PHP.obj.foo);', "get_test2 #7.js");
echo "------------\n";
echo " __call()\n";
$blaa->executeString('PHP.obj.foo(1,2,3);', "call_test1 #8.js");
echo "------------\n";
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}

32
samples/test_callback.php Normal file
View File

@ -0,0 +1,32 @@
<?php
$a = new V8Js();
// Should not work with closure
$a->test = function ($params) { return (method_exists($params, 'cb1')) ? $params->cb1("hello") : false; };
$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
var_dump(__LINE__, $ret);
// Test is_a()
$a->test = function ($params) { return (is_a($params, 'V8Object')) ? $params->cb1("hello") : false; };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
var_dump(__LINE__, $ret);
// Test is_a()
$a->test = function ($params) { return (is_a($params, 'V8Function')) ? $params("hello") : false; };
$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
var_dump(__LINE__, $ret);
// Should not work with object
$a->test = function ($params) { return (is_a($params, 'Closure')) ? $params("hello") : false; };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
var_dump(__LINE__, $ret);
$a->test = function ($params) { var_dump($params); return $params->cb1("hello"); };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
var_dump(__LINE__, $ret);
// FIX! method_exists() Leaks!
$a->test = function ($params) { var_dump($params, method_exists($params, 'cb1'), $params->cb1); };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');

11
samples/test_closure.php Normal file
View File

@ -0,0 +1,11 @@
<?php
$a = new V8Js();
$a->func = function ($a) { echo "Closure..\n"; };
try {
$a->executeString("print(PHP.func); PHP.func(1);", "closure_test.js");
$a->executeString("print(PHP.func); PHP.func(1);", "closure_test.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}

5
samples/test_crash.php Normal file
View File

@ -0,0 +1,5 @@
<?php {
$a = new V8Js();
var_dump($a->executeString('Jst.write = function(s) { html += "EI TOIMI"; };' ."\n" .' Jst.evaluate("lol testi <%= 1 %>", {});'));
}

9
samples/test_date.php Normal file
View File

@ -0,0 +1,9 @@
<?php
$a = new V8Js();
try {
var_dump($a->executeString("date = new Date('September 8, 1975 09:00:00'); print(date + '\\n'); date;", "test.js"));
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}

59
samples/test_dumper.php Normal file
View File

@ -0,0 +1,59 @@
<?php
class Foo {
var $foo = 'bar';
var $true = true;
var $false = false;
var $bar = array(1,2,3,1.23456789);
var $ass = array("life" => 42, "foo" => "bar");
function __set($name, $value)
{
echo "I'm setter!\n";
var_dump($name, $value);
}
function __get($name)
{
echo "I'm getter!\n";
var_dump($name);
}
function __call($name, $args)
{
echo "I'm caller!\n";
var_dump($name, $args);
}
}
$a = new V8Js();
$obj = new Foo;
$a->arr = array("foobar" => $obj);
$JS = <<< 'EOF'
var example = new Object;
example.foo = function () {
print("this is foo");
}
example.bar = function () {
print("this is bar");
}
example.__noSuchMethod__ = function (id, args) {
print("tried to handle unknown method " + id);
if (args.length != 0)
print("it had arguments: " + args);
}
example.foo(); // alerts "this is foo"
example.bar(); // alerts "this is bar"
example.grill(); // alerts "tried to handle unknown method grill"
example.ding("dong"); // alerts "tried to handle unknown method ding"
EOF;
try {
$a->executeString("var myarr = new Array(); myarr[0] = 'foo'; myarr[1] = 'bar'; var_dump(myarr); var_dump(new Date('September 8, 1975 09:00:00'))", "call_test1.js");
$a->executeString("var_dump(PHP.arr.foobar.bar);", "call_test2.js");
$a->executeString("var_dump(PHP.arr.foobar.bar[0]);", "call_test3.js");
$a->executeString("var_dump(var_dump(PHP.arr));", "call_test4.js");
$a->executeString("var patt1=/[^a-h]/g; var_dump(patt1);", "call_test5.js");
$a->executeString("var_dump(Math.PI, Infinity, null, undefined);", "call_test6.js");
// $a->executeString($JS);
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}

View File

@ -0,0 +1,26 @@
<?php {
class Foo {
private $v8 = NULL;
public function __construct()
{
$this->v8 = new V8Js();
$this->v8->foo = $this;
var_dump($this->v8->executeString('throw 1; PHP.foo.bar();', 'trycatch1'));
var_dump($this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print("catched!\n"); }', 'trycatch2'));
}
public function bar()
{
echo "To Bar!\n";
var_dump($this->v8->executeString('throw new Error();', 'throw'));
}
}
try {
$foo = new Foo();
} catch (V8JsException $e) {
echo "PHP Exception: ", $e->getMessage(), "\n"; //var_dump($e);
}
}

View File

@ -0,0 +1,34 @@
<?php {
class Foo {
private $v8 = NULL;
public function __construct()
{
$this->v8 = new V8Js(null, array(), false);
$this->v8->foo = $this;
// $this->v8->executeString('asdasda< / sd', 'trycatch0');
// $this->v8->executeString('blahnothere', 'trycatch1');
// $this->v8->executeString('throw new SyntaxError();', 'throw');
// $this->v8->executeString('function foo() {throw new SyntaxError();}', 'trycatch2');
// $this->v8->executeString('try { foo(); } catch (e) { print(e + " catched by pure JS!\n"); }', 'trycatch3');
// $this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " catched via PHP callback!\n"); }', 'trycatch4');
// $this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print("catched!\n"); }', 'trycatch5');
// $this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print("catched!\n"); }', 'trycatch5');
var_dump($this->v8->getPendingException());
}
public function bar()
{
// $this->v8->executeString('asdasda< / sd', 'trycatch0');
// $this->v8->executeString('blahnothere', 'trycatch1');
$this->v8->executeString('throw new Error();', 'throw');
}
}
try {
$foo = new Foo();
} catch (V8JsException $e) {
echo "PHP Exception: ", $e->getMessage(), "\n";
}
}

23
samples/test_extend.php Normal file
View File

@ -0,0 +1,23 @@
<?php
// Test class
class Testing extends V8Js
{
public $foo = 'ORIGINAL';
private $my_private = 'arf'; // Should not show in JS side
protected $my_protected = 'argh'; // Should not show in JS side
public function mytest($a, $b, $c = NULL)
{
var_dump(func_get_args());
}
}
$a = new Testing();
echo $a;
try {
$a->executeString("PHP.mytest(PHP.foo, PHP.my_private, PHP.my_protected);", "test7.js");
} catch (V8JsException $e) {
var_dump($e);
}

View File

@ -0,0 +1,9 @@
<?php
V8Js::registerExtension('a', file_get_contents('js/json-template.js'), array('b'));
V8Js::registerExtension('b', file_get_contents('js/jstparser.js'), array('a'));
var_dump(V8JS::getExtensions());
$a = new V8Js('myobj', array(), array('b'));

48
samples/test_method.php Normal file
View File

@ -0,0 +1,48 @@
<?php
// Test class
class Testing
{
public $foo = 'ORIGINAL';
private $my_private = 'arf'; // Should not show in JS side
protected $my_protected = 'argh'; // Should not show in JS side
function mytest($a, $b, $c = NULL)
{
var_dump(func_get_args());
}
}
$a = new V8Js();
$a->myobj = new Testing();
$a->executeString("PHP.myobj.mytest('arg1', 'arg2');", "test1.js");
$a->executeString("PHP.myobj.mytest(true, false, 1234567890);", "test2.js");
$a->executeString("PHP.myobj.mytest(3.14, 42, null);", "test3.js");
// Invalid parameters
try {
$a->executeString("PHP.myobj.mytest();", "test4.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
try {
$a->executeString("PHP.myobj.mytest('arg1', 'arg2', 'arg3', 'extra_arg');", "test5.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
// Array / Object
try {
// date_default_timezone_set("UTC");
$a->executeString("date = new Date('September 8, 1975 09:00:00'); PHP.print(date); PHP.myobj.mytest(date, PHP.myobj, new Array(1,2,3));", "test6.js");
} catch (V8JsException $e) {
var_dump($e);
}
try {
$a->executeString("PHP.myobj.mytest(PHP.myobj, new Array(1,2,3), new Array('foo', 'bar', PHP.myobj));", "test7.js");
} catch (V8JsException $e) {
var_dump($e);
}

173
test.php Normal file
View File

@ -0,0 +1,173 @@
<?php
/*
$v8 = new V8Js;
$v8->func = function ($a) { return var_export(func_get_args(), TRUE); };
$v8->executeString("PHP.func();", "arg_test1.js");
exit;
*/
var_dump(V8Js::registerExtension('myparser.js', 'function foo() { print("foobar!\n"}', array('jstparser.js', 'json-template.js'), false));
var_dump(V8Js::registerExtension('myparser.js', 'function foo() { print("foobar!\n"}', array('jstparser.js', 'json-template.js'), false));
var_dump(V8Js::registerExtension('jstparser.js', file_get_contents('js/jstparser.js'), array(), false));
//V8Js::registerExtension('json-template.js', file_get_contents('js/json-template.js'), array(), false);
var_dump(V8JS::getExtensions());
$a = new V8Js('myobj', array(), array('jstparser.js'));
$jstparser = <<< 'EOT'
var template = 'Gold &amp; Hot Rod Red, as seen in the new <a href="http://blog.markturansky.com/archives/51">Iron Man trailer</a>!' + "\n" +
'<table cellspacing="0" cellpadding="4">' + "\n" +
' <% for(var i = 0; i < 10; i++){ %> ' + "\n" +
' <tr>' + "\n" +
' <td style="background-color: <%= i % 2 == 0 ? \'red\' : \'gold\' %>">' + "\n" +
' Hi, <%=name%>! i is <%= i %>' + "\n" +
' </td>' + "\n" +
' </tr>' + "\n" +
' <% } %>' + "\n" +
'</table>' + "\n" +
'Note that name is HTML escaped by default. Here it is without escaping:'+
'<%+ name %>';
Jst.evaluateSingleShot(template, {"name":"foobar"});
EOT;
echo($a->executeString($jstparser, "ext_test1.js")), "\n";
$a->_SERVER = $_SERVER;
$a->func = function ($a) { echo "Closure..\n"; };
$a->executeString("print(myobj._SERVER['HOSTNAME']);", "test1.js");
$a->executeString("print(myobj.func); myobj.func(1);", "closure_test.js");
$JS = <<<'EOT'
function dump(a)
{
for (var i in a) {
var val = a[i];
print(i + ' => ' + val + "\n");
}
}
function foo()
{
var bar = 'bar';
var foo = 'foo';
return foo + bar;
}
function test()
{
var a = 'PHP version: ' + PHP.phpver;
phpver = 'changed in JS!';
return a;
}
function loop()
{
var foo = 'foo';
while(true)
{
foo + 'bar';
}
}
function output()
{
while(true)
{
print("output:foo\n");
sleep(5);
exit();
}
};
function simplearray()
{
print(myarray.a + "\n");
print(myarray.b + "\n");
print(myarray.c + "\n");
print(myarray.d + "\n");
}
function bigarray()
{
print(PHP.$_SERVER['HOSTNAME'] + "\n");
print(PHP.$_SERVER.argv + "\n");
}
EOT;
$jsontemplate = <<< EOT
var t = jsontemplate.Template("{# This is a comment and will be removed from the output.}{.section songs}<h2>Songs in '{playlist-name}'</h2><table width=\"100%\">{.repeated section @}<tr><td><a href=\"{url-base|htmltag}{url|htmltag}\">Play</a><td><i>{title}</i></td><td>{artist}</td></tr>{.end}</table>{.or}<p><em>(No page content matches)</em></p>{.end}");
t.expand({
"url-base": "http://example.com/music/",
"playlist-name": "Epic Playlist",
"songs": [
{
"url": "1.mp3",
"artist": "Grayceon",
"title": "Sounds Like Thunder"
},
{
"url": "2.mp3",
"artist": "Thou",
"title": "Their Hooves Carve Craters in the Earth"
}]});
EOT;
class tester
{
public $foo = 'bar';
private $my_private = 'arf';
protected $my_protected = 'argh';
function mytest() { echo 'Here be monsters..', "\n"; }
}
$a = new V8Js();
$a->obj = new tester();
$a->phpver = phpversion();
$a->argv = $_SERVER['argv'];
$a->integer = 1;
$a->float = 3.14;
$a->{'$'._SERVER} = $_SERVER;
$a->GLOBALS = $GLOBALS;
$a->myarray = array(
'a' => 'Array value for key A',
'b' => 'Array value for key B',
'c' => 'Array value for key C',
'd' => 'Array value for key D',
);
$a->arr = array("first", "second", "third");
$a->executeString($JS, "test1.js");
$a->executeString("bigarray()", "test1.js");
try {
echo($a->executeString($jstparser, "test2.js")), "\n";
var_dump($a->executeString($jsontemplate, "test1.js"));
} catch (V8JsException $e) {
echo $e->getMessage();
}
// Test for context handling
$a->executeString($JS, "test1.js");
$a->executeString("bigarray();");
echo '$a->obj: ', "\n"; $a->executeString("dump(PHP.obj);");
echo '$a->arr: ', "\n"; $a->executeString("dump(PHP.arr);");
echo '$a->argv: ', "\n"; $a->executeString("dump(PHP.argv);");
var_dump($a->argv);
var_dump($a->executeString("test();"));
var_dump($a->executeString("test();"));
$b = new V8Js();
var_dump($a->phpver, $a->executeString("test();"));
$b->executeString($JS, "test2.js");
var_dump($b->executeString("test();"));
var_dump($b->executeString("print('foobar\\n');"));
// Exception methods
try {
$b->executeString("foobar; foo();", "extest.js");
} catch (V8JsException $e) {
var_dump($e, $e->getJsFileName(), $e->getJsLineNumber(), $e->getJsSourceLine(), $e->getJsTrace());
}

25
tests/basic.phpt Normal file
View File

@ -0,0 +1,25 @@
--TEST--
Test V8::executeString() : Simple test
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
len = print('Hello' + ' ' + 'World!' + "\\n");
len;
EOT;
$v8 = new V8Js();
try {
var_dump($v8->executeString($JS, 'basic.js'));
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECT--
Hello World!
int(13)
===EOF===

48
tests/callbacks.phpt Normal file
View File

@ -0,0 +1,48 @@
--TEST--
Test V8::executeString() : Call JS from PHP
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$a = new V8Js();
// Should not work with closure
$a->test = function ($params) { return (method_exists($params, 'cb1')) ? $params->cb1("hello") : false; };
$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
var_dump(__LINE__, $ret);
// Test is_a()
$a->test = function ($params) { return (is_a($params, 'V8Object')) ? $params->cb1("hello") : false; };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
var_dump(__LINE__, $ret);
// Test is_a()
$a->test = function ($params) { return (is_a($params, 'V8Function')) ? $params("hello") : false; };
$ret = $a->executeString('PHP.test(function (foo) { return foo + " world"; });');
var_dump(__LINE__, $ret);
// Should not work with object
$a->test = function ($params) { return (is_a($params, 'Closure')) ? $params("hello") : false; };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
var_dump(__LINE__, $ret);
// Works
$a->test = function ($params) { return $params->cb1("hello"); };
$ret = $a->executeString('PHP.test({ "cb1" : function (foo) { return foo + " world"; } });');
var_dump(__LINE__, $ret);
?>
===EOF===
--EXPECT--
int(8)
bool(false)
int(13)
string(11) "hello world"
int(18)
string(11) "hello world"
int(23)
bool(false)
int(28)
string(11) "hello world"
===EOF===

21
tests/closures_basic.phpt Normal file
View File

@ -0,0 +1,21 @@
--TEST--
Test V8::executeString() : Simple test
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$a = new V8Js();
$a->func = function ($arg) { echo "Hello {$arg}, I'm Closure!\n"; };
try {
$a->executeString('print(PHP.func + "\n"); PHP.func("foobar");', "closure_test.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
?>
===EOF===
--EXPECT--
[object Closure]
Hello foobar, I'm Closure!
===EOF===

View File

@ -0,0 +1,30 @@
--TEST--
Test V8::executeString() : Dynamic closure call test
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
class Foo
{
public static function bar($s)
{
return 'Hello ' . $s;
}
}
$a = new V8Js();
$b = array('Foo', 'bar');
$a->func = function ($arg) use ($b) { return call_user_func($b, $arg); };
try {
$a->executeString('print(PHP.func + "\n"); print(PHP.func("world") + "\n");', "closure_test.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
?>
===EOF===
--EXPECT--
[object Closure]
Hello world
===EOF===

24
tests/construct.phpt Normal file
View File

@ -0,0 +1,24 @@
--TEST--
Test V8::executeString() : Calling construct twice
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
print('Hello' + ' ' + 'World!' + "\\n");
EOT;
$v8 = new V8Js("myObj");
$v8->__construct();
try {
$v8->executeString($JS, 'basic.js');
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECT--
Hello World!
===EOF===

View File

@ -0,0 +1,78 @@
--TEST--
Test V8::executeString() : test context preserving
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS_set = <<< EOT
var preserved = "ORIGINAL";
print("Set variable (" + PHP.ctx + ")\\n");
EOT;
$JS_change = <<< EOT
preserved = "CHANGED";
print("Change variable (" + PHP.ctx + ")\\n");
EOT;
$JS_read = <<< EOT
print("Read variable (" + PHP.ctx + ")\\n");
print("Result: " + preserved + "\\n");
EOT;
// First context: Set variable
$a = new V8Js();
$a->ctx = '#1';
try {
echo '1. ';
$a->executeString($JS_set, 'set.js');
} catch (V8JsException $e) {
var_dump($e);
}
// Second context: Change variable
$b = new V8Js();
$b->ctx = '#2';
try {
echo '2. ';
$b->executeString($JS_change, 'change.js');
} catch (V8JsException $e) {
var_dump($e);
}
// First context: Read variable
try {
echo '3. ';
$a->executeString($JS_read, 'read.js');
} catch (V8JsException $e) {
var_dump($e);
}
// First context: Change variable
try {
echo '4. ';
$a->executeString($JS_change, 'change.js');
} catch (V8JsException $e) {
var_dump($e);
}
// First context: Read variable again
try {
echo '5. ';
$a->executeString($JS_read, 'read.js');
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECT--
1. Set variable (#1)
2. Change variable (#2)
3. Read variable (#1)
Result: ORIGINAL
4. Change variable (#1)
5. Read variable (#1)
Result: CHANGED
===EOF===

View File

@ -0,0 +1,38 @@
--TEST--
Test V8::executeString() : test context separation
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
print(PHP.foo);
EOT;
$a = new V8Js();
$a->foo = 'from first.js';
try {
$a->executeString($JS, 'first.js');
} catch (V8JsException $e) {
var_dump($e);
}
echo "\n";
$b = new V8Js();
$b->foo = 'from second.js';
try {
$b->executeString($JS, 'second.js');
} catch (V8JsException $e) {
var_dump($e);
}
echo "\n";
?>
===EOF===
--EXPECTF--
from first.js
from second.js
===EOF===

68
tests/exception.phpt Normal file
View File

@ -0,0 +1,68 @@
--TEST--
Test V8::executeString() : V8JsException
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
this_function_does_not_exist();
EOT;
$v8 = new V8Js();
try {
$v8->executeString($JS, 'exception.js');
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECTF--
object(V8JsException)#2 (11) {
["message":protected]=>
string(75) "exception.js:1: ReferenceError: this_function_does_not_exist is not defined"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(%d) "%s"
["line":protected]=>
int(10)
["trace":"Exception":private]=>
array(1) {
[0]=>
array(6) {
["file"]=>
string(%d) "%s"
["line"]=>
int(10)
["function"]=>
string(13) "executeString"
["class"]=>
string(4) "V8Js"
["type"]=>
string(2) "->"
["args"]=>
array(2) {
[0]=>
string(31) "this_function_does_not_exist();"
[1]=>
string(12) "exception.js"
}
}
}
["previous":"Exception":private]=>
NULL
["JsFileName":protected]=>
string(12) "exception.js"
["JsLineNumber":protected]=>
int(1)
["JsSourceLine":protected]=>
string(31) "this_function_does_not_exist();"
["JsTrace":protected]=>
string(83) "ReferenceError: this_function_does_not_exist is not defined
at exception.js:1:1"
}
===EOF===

View File

@ -0,0 +1,37 @@
--TEST--
Test V8::executeString() : Exception propagation test 1
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
class Foo {
private $v8 = NULL;
public function __construct()
{
$this->v8 = new V8Js();
$this->v8->foo = $this;
$this->v8->executeString('fooobar', 'throw_0');
var_dump($this->v8->getPendingException());
$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch1');
$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch2');
}
public function bar()
{
echo "To Bar!\n";
$this->v8->executeString('throw new Error();', 'throw_1');
}
}
try {
$foo = new Foo();
} catch (V8JsException $e) {
echo "PHP Exception: ", $e->getMessage(), "\n"; //var_dump($e);
}
?>
===EOF===
--EXPECTF--
PHP Exception: throw_0:1: ReferenceError: fooobar is not defined
===EOF===

View File

@ -0,0 +1,101 @@
--TEST--
Test V8::executeString() : Exception propagation test 2
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
class Foo {
private $v8 = NULL;
public function __construct()
{
$this->v8 = new V8Js(null, array(), array(), false);
$this->v8->foo = $this;
$this->v8->executeString('fooobar', 'throw_0');
var_dump($this->v8->getPendingException());
$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch1');
$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught!\n"); }', 'trycatch2');
}
public function bar()
{
echo "To Bar!\n";
$this->v8->executeString('throw new Error();', 'throw_1');
}
}
try {
$foo = new Foo();
} catch (V8JsException $e) {
echo "PHP Exception: ", $e->getMessage(), "\n"; //var_dump($e);
}
?>
===EOF===
--EXPECTF--
object(V8JsException)#3 (11) {
["message":protected]=>
string(49) "throw_0:1: ReferenceError: fooobar is not defined"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(%d) "%s"
["line":protected]=>
int(10)
["trace":"Exception":private]=>
array(2) {
[0]=>
array(6) {
["file"]=>
string(%d) "%s"
["line"]=>
int(10)
["function"]=>
string(13) "executeString"
["class"]=>
string(4) "V8Js"
["type"]=>
string(2) "->"
["args"]=>
array(2) {
[0]=>
string(7) "fooobar"
[1]=>
string(7) "throw_0"
}
}
[1]=>
array(6) {
["file"]=>
string(%d) "%s"
["line"]=>
int(24)
["function"]=>
string(11) "__construct"
["class"]=>
string(3) "Foo"
["type"]=>
string(2) "->"
["args"]=>
array(0) {
}
}
}
["previous":"Exception":private]=>
NULL
["JsFileName":protected]=>
string(7) "throw_0"
["JsLineNumber":protected]=>
int(1)
["JsSourceLine":protected]=>
string(7) "fooobar"
["JsTrace":protected]=>
string(57) "ReferenceError: fooobar is not defined
at throw_0:1:1"
}
To Bar!
Error caught!
PHP Exception: throw_0:1: ReferenceError: fooobar is not defined
===EOF===

View File

@ -0,0 +1,38 @@
--TEST--
Test V8::executeString() : Exception propagation test 3
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
class Foo {
private $v8 = NULL;
public function __construct()
{
$this->v8 = new V8Js(null, array(), array(), false);
$this->v8->foo = $this;
$this->v8->executeString('function foobar() { throw new SyntaxError(); }', 'throw_1');
$this->v8->executeString('try { foobar(); } catch (e) { print(e + " caught in JS!\n"); }', 'trycatch1');
$this->v8->executeString('try { PHP.foo.bar(); } catch (e) { print(e + " caught via PHP callback!\n"); }', 'trycatch2');
}
public function bar()
{
echo "To Bar!\n";
$this->v8->executeString('throw new Error();', 'throw_2');
}
}
try {
$foo = new Foo();
} catch (V8JsException $e) {
echo "PHP Exception: ", $e->getMessage(), "\n";
}
?>
===EOF===
--EXPECT--
SyntaxError caught in JS!
To Bar!
Error caught via PHP callback!
===EOF===

33
tests/execute_flags.phpt Normal file
View File

@ -0,0 +1,33 @@
--TEST--
Test V8::executeString() : Forcing to arrays
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$js = <<<'EOT'
var a = { 'hello' : 'world' }; a;
EOT;
$v8 = new V8Js();
try {
var_dump($v8->executeString($js, 'assoc_no_flags.js'));
echo "---\n";
var_dump($v8->executeString($js, 'assoc_force_to_array.js', V8Js::FLAG_FORCE_ARRAY));
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECT--
object(V8Object)#2 (1) {
["hello"]=>
string(5) "world"
}
---
array(1) {
["hello"]=>
string(5) "world"
}
===EOF===

View File

@ -0,0 +1,75 @@
--TEST--
Test V8::executeString() : Forcing to arrays
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$js = <<<'EOT'
String.prototype.test = function(){ return PHP.test(this, arguments); };
"Foobar".test("foo", "bar");
EOT;
$v8 = new V8Js();
$v8->test = function ($value) { var_dump(func_get_args()); };
try {
$v8->executeString($js, 'no_flags.js');
echo "---\n";
$v8->executeString($js, 'force_to_array.js', V8Js::FLAG_FORCE_ARRAY);
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECT--
array(2) {
[0]=>
object(V8Object)#3 (6) {
["0"]=>
string(1) "F"
["1"]=>
string(1) "o"
["2"]=>
string(1) "o"
["3"]=>
string(1) "b"
["4"]=>
string(1) "a"
["5"]=>
string(1) "r"
}
[1]=>
object(V8Object)#4 (2) {
["0"]=>
string(3) "foo"
["1"]=>
string(3) "bar"
}
}
---
array(2) {
[0]=>
array(6) {
[0]=>
string(1) "F"
[1]=>
string(1) "o"
[2]=>
string(1) "o"
[3]=>
string(1) "b"
[4]=>
string(1) "a"
[5]=>
string(1) "r"
}
[1]=>
array(2) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
}
}
===EOF===

View File

@ -0,0 +1,35 @@
--TEST--
Test V8::registerExtension() : Basic extension registering
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
V8Js::registerExtension('a', 'print("world!\n");', array('b'));
V8Js::registerExtension('b', 'print("Hello ");');
var_dump(V8JS::getExtensions());
$a = new V8Js('myobj', array(), array('a'));
?>
===EOF===
--EXPECT--
array(2) {
["a"]=>
array(2) {
["auto_enable"]=>
bool(false)
["deps"]=>
array(1) {
[0]=>
string(1) "b"
}
}
["b"]=>
array(1) {
["auto_enable"]=>
bool(false)
}
}
Hello world!
===EOF===

View File

@ -0,0 +1,39 @@
--TEST--
Test V8::registerExtension() : Circular dependencies
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
V8Js::registerExtension('a', 'print("A");', array('b'));
V8Js::registerExtension('b', 'print("B");', array('a'));
var_dump(V8JS::getExtensions());
$a = new V8Js('myobj', array(), array('a'));
?>
--EXPECTF--
array(2) {
["a"]=>
array(2) {
["auto_enable"]=>
bool(false)
["deps"]=>
array(1) {
[0]=>
string(1) "b"
}
}
["b"]=>
array(2) {
["auto_enable"]=>
bool(false)
["deps"]=>
array(1) {
[0]=>
string(1) "a"
}
}
}
Fatal error: v8::Context::New() Circular extension dependency in %s on line 8

41
tests/get_accessor.phpt Normal file
View File

@ -0,0 +1,41 @@
--TEST--
Test V8::executeString() : PHP variables via get accessor
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--INI--
display_errors=off
--FILE--
<?php
$JS = <<<'EOT'
print(typeof $foobar + "\n"); // Undefined
print(myobj.$foobar + "\n"); // Undefined (in 1st run!)
print(myobj.$_SERVER['REQUEST_TIME'] + "\n");
myobj.$foobar = 'CHANGED'; // should be read only!
print(myobj.$foobar + "\n"); // Undefined (in 1st run!)
EOT;
$a = new V8Js("myobj", array('$_SERVER' => '_SERVER', '$foobar' => 'myfoobar'));
$a->executeString($JS, "test1.js");
$myfoobar = 'myfoobarfromphp';
$a->executeString($JS, "test2.js");
// Check that variables do not get in object ..
var_dump($a->myfoobar, $a->foobar);
?>
===EOF===
--EXPECTF--
undefined
undefined
%d
undefined
undefined
myfoobarfromphp
%d
myfoobarfromphp
NULL
NULL
===EOF===

50
tests/object.phpt Normal file
View File

@ -0,0 +1,50 @@
--TEST--
Test V8::executeString() : Object passed from PHP
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
function dump(a)
{
for (var i in a) {
var val = a[i];
print(i + ' => ' + val + "\\n");
}
}
function test()
{
dump(PHP.myobj);
PHP.myobj.foo = 'CHANGED';
PHP.myobj.mytest();
}
test();
print(PHP.myobj.foo + "\\n");
EOT;
// Test class
class Testing
{
public $foo = 'ORIGINAL';
private $my_private = 'arf'; // Should not show in JS side
protected $my_protected = 'argh'; // Should not show in JS side
function mytest() { echo 'Here be monsters..', "\n"; }
}
$a = new V8Js();
$a->myobj = new Testing();
$a->executeString($JS, "test.js");
// Check that variable has not been modified
var_dump($a->myobj->foo);
?>
===EOF===
--EXPECT--
mytest => function () { [native code] }
foo => ORIGINAL
Here be monsters..
ORIGINAL
string(8) "ORIGINAL"
===EOF===

View File

@ -0,0 +1,144 @@
--TEST--
Test V8::executeString() : Calling methods of object passed from PHP
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
// Test class
class Testing
{
public $foo = 'ORIGINAL';
private $my_private = 'arf'; // Should not show in JS side
protected $my_protected = 'argh'; // Should not show in JS side
function mytest($a, $b, $c = NULL)
{
var_dump(func_get_args());
}
}
$a = new V8Js();
$a->myobj = new Testing();
$a->executeString("PHP.myobj.mytest('arg1', 'arg2');", "test1.js");
$a->executeString("PHP.myobj.mytest(true, false, 1234567890);", "test2.js");
$a->executeString("PHP.myobj.mytest(3.14, 42, null);", "test3.js");
// Invalid parameters
try {
$a->executeString("PHP.myobj.mytest();", "test4.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
try {
$a->executeString("PHP.myobj.mytest('arg1', 'arg2', 'arg3', 'extra_arg');", "test5.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
try {
date_default_timezone_set("UTC");
echo "\nTEST: Javascript Date -> PHP DateTime\n";
echo "======================================\n";
$a->executeString("date = new Date('September 8, 1975 09:00:00'); print(date + '\\n'); PHP.myobj.mytest(date, 'foo');", "test6.js");
} catch (V8JsException $e) {
echo $e->getMessage(), "\n";
}
// Array / Object
try {
$a->executeString("PHP.myobj.mytest(PHP.myobj, new Array(1,2,3), new Array('foo', 'bar', PHP.myobj));", "test7.js");
} catch (V8JsException $e) {
var_dump($e);
}
?>
===EOF===
--EXPECT--
array(2) {
[0]=>
string(4) "arg1"
[1]=>
string(4) "arg2"
}
array(3) {
[0]=>
bool(true)
[1]=>
bool(false)
[2]=>
int(1234567890)
}
array(3) {
[0]=>
float(3.14)
[1]=>
int(42)
[2]=>
NULL
}
test4.js:1: TypeError: Testing::mytest() expects at least 2 parameters, 0 given
array(4) {
[0]=>
string(4) "arg1"
[1]=>
string(4) "arg2"
[2]=>
string(4) "arg3"
[3]=>
string(9) "extra_arg"
}
TEST: Javascript Date -> PHP DateTime
======================================
Mon Sep 08 1975 09:00:00 GMT+0200 (EET)
array(2) {
[0]=>
object(DateTime)#4 (3) {
["date"]=>
string(19) "1975-09-08 09:00:00"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+02:00"
}
[1]=>
string(3) "foo"
}
array(3) {
[0]=>
object(V8Object)#4 (2) {
["mytest"]=>
object(V8Function)#6 (0) {
}
["foo"]=>
string(8) "ORIGINAL"
}
[1]=>
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
[2]=>
array(3) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
[2]=>
object(V8Object)#5 (2) {
["mytest"]=>
object(V8Function)#6 (0) {
}
["foo"]=>
string(8) "ORIGINAL"
}
}
}
===EOF===

View File

@ -0,0 +1,82 @@
--TEST--
Test V8::executeString() : Prototype with PHP callbacks
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$js = <<<'EOT'
String.prototype.test = function(){ return PHP.test(this.toString(), arguments); };
String.prototype.test_two = function(){ return PHP.test_two.func(this.toString(), arguments); };
Array.prototype.test = function(){ return PHP.test(this.toString(), arguments); };
Array.prototype.test_two = function(){ return PHP.test_two.func(this.toString(), arguments); };
"Foobar".test("foo", "bar");
"Foobar".test_two("foo", "bar");
["a","b","c"].test("foo", "bar");
["a","b","c"].test_two("foo", "bar");
EOT;
class A
{
public function __call($name, $args)
{
var_dump($args);
return NULL;
}
}
$a = new V8Js();
$a->test = function ($value) { var_dump(func_get_args()); return 'HELLO: ' . md5($value); };
$a->test_two = new A();
$a->executeString($js, 'foo');
?>
===EOF===
--EXPECT--
array(2) {
[0]=>
string(6) "Foobar"
[1]=>
object(V8Object)#4 (2) {
["0"]=>
string(3) "foo"
["1"]=>
string(3) "bar"
}
}
array(2) {
[0]=>
string(6) "Foobar"
[1]=>
object(V8Object)#4 (2) {
["0"]=>
string(3) "foo"
["1"]=>
string(3) "bar"
}
}
array(2) {
[0]=>
string(5) "a,b,c"
[1]=>
object(V8Object)#4 (2) {
["0"]=>
string(3) "foo"
["1"]=>
string(3) "bar"
}
}
array(2) {
[0]=>
string(5) "a,b,c"
[1]=>
object(V8Object)#4 (2) {
["0"]=>
string(3) "foo"
["1"]=>
string(3) "bar"
}
}
===EOF===

22
tests/object_reuse.phpt Normal file
View File

@ -0,0 +1,22 @@
--TEST--
Test V8::executeString() : Test PHP object reusage
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$v8 = new V8Js();
// Reusing should work..
$v8 = new V8Js();
$foo = array('foo' => 'Destructor did not mess anything!');
$v8->foobar = $foo;
$v8->executeString("print(PHP.foobar['foo'] + '\\n');", "dtor.js");
?>
===EOF===
--EXPECT--
Destructor did not mess anything!
===EOF===

82
tests/return_value.phpt Normal file
View File

@ -0,0 +1,82 @@
--TEST--
Test V8::executeString() : Return values
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
function test(passed)
{
return passed;
}
EOT;
// Test class
class Testing
{
public $foo = 'ORIGINAL';
private $my_private = 'arf'; // Should not show in JS side
protected $my_protected = 'argh'; // Should not show in JS side
function mytest() { echo 'Here be monsters..', "\n"; }
}
$a = new V8Js();
$a->myobj = new Testing();
var_dump($a->executeString($JS, "test.js"));
var_dump($a->executeString("test(PHP.myobj);", "test1.js"));
var_dump($a->executeString("test(new Array(1,2,3));", "test2.js"));
var_dump($a->executeString("test(new Array('foo', 'bar'));", "test3.js"));
var_dump($a->executeString("test(new Array('foo', 'bar'));", "test3.js"));
var_dump($a->executeString("test(new Date('September 8, 1975 09:00:00'));", "test4.js"));
var_dump($a->executeString("test(1234567890);", "test5.js"));
var_dump($a->executeString("test(123.456789);", "test6.js"));
var_dump($a->executeString("test('some string');", "test7.js"));
var_dump($a->executeString("test(true);", "test8.js"));
var_dump($a->executeString("test(false);", "test9.js"));
?>
===EOF===
--EXPECT--
NULL
object(V8Object)#3 (2) {
["mytest"]=>
object(V8Function)#4 (0) {
}
["foo"]=>
string(8) "ORIGINAL"
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(2) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
}
array(2) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
}
object(DateTime)#3 (3) {
["date"]=>
string(19) "1975-09-08 09:00:00"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+02:00"
}
int(1234567890)
float(123.456789)
string(11) "some string"
bool(true)
bool(false)
===EOF===

5
tests/skipif.inc Normal file
View File

@ -0,0 +1,5 @@
<?php
if (!extension_loaded('v8js')) {
die("skip");
}

View File

@ -0,0 +1,58 @@
--TEST--
Test V8::executeString() : simple variables passed from PHP
--SKIPIF--
<?php require_once(dirname(__FILE__) . '/skipif.inc'); ?>
--FILE--
<?php
$JS = <<< EOT
function dump(a)
{
for (var i in a) {
var val = a[i];
print(i + ' => ' + val + "\\n");
}
}
function test()
{
var a = 'From PHP: ' + PHP.somevar;
PHP.somevar = 'changed in JS!'; // Should not change..
dump(PHP.myarray);
return a;
}
print(test() + "\\n");
print(PHP.myinteger + "\\n");
print(PHP.myfloat + "\\n");
EOT;
$a = new V8Js();
$a->somevar = "From PHP with love!";
$a->myinteger = 123;
$a->myfloat = 3.14;
$a->_SERVER = $_SERVER;
$a->GLOBALS = $GLOBALS;
$a->myarray = array(
'a' => 'value for key A',
'b' => 'value for key B',
'c' => 'value for key C',
'd' => 'value for key D',
);
$a->executeString($JS, "test.js");
// Check that variable has not been modified
var_dump($a->somevar);
?>
===EOF===
--EXPECT--
a => value for key A
b => value for key B
c => value for key C
d => value for key D
From PHP: From PHP with love!
123
3.14
string(19) "From PHP with love!"
===EOF===

1282
v8js.cc Normal file

File diff suppressed because it is too large Load Diff

595
v8js_convert.cc Normal file
View File

@ -0,0 +1,595 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
extern "C" {
#include "php.h"
#include "ext/date/php_date.h"
#include "zend_interfaces.h"
#include "zend_closures.h"
}
#include "php_v8js_macros.h"
#include <v8.h>
/* Callback for PHP methods and functions */
static v8::Handle<v8::Value> php_v8js_php_callback(const v8::Arguments &args) /* {{{ */
{
v8::Handle<v8::Value> return_value;
zval *value = reinterpret_cast<zval *>(args.This()->GetPointerFromInternalField(0));
zend_function *method_ptr;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
zval fname, *retval_ptr = NULL, **argv = NULL;
zend_class_entry *ce = Z_OBJCE_P(value);
zend_uint argc = args.Length(), min_num_args = 0, max_num_args = 0;
char *error;
int error_len, i, flags = V8JS_FLAG_NONE;
TSRMLS_FETCH();
/* Set method_ptr from v8::External or fetch the closure invoker */
if (!args.Data().IsEmpty() && args.Data()->IsExternal()) {
method_ptr = static_cast<zend_function *>(v8::External::Unwrap(args.Data()));
} else {
method_ptr = zend_get_closure_invoke_method(value TSRMLS_CC);
}
/* Set parameter limits */
min_num_args = method_ptr->common.required_num_args;
max_num_args = method_ptr->common.num_args;
/* Function name to call */
ZVAL_STRING(&fname, method_ptr->common.function_name, 0);
/* zend_fcall_info */
fci.size = sizeof(fci);
fci.function_table = &ce->function_table;
fci.function_name = &fname;
fci.symbol_table = NULL;
fci.object_ptr = value;
fci.retval_ptr_ptr = &retval_ptr;
fci.param_count = 0;
/* Check for passed vs required number of arguments */
if (argc < min_num_args)
{
error_len = spprintf(&error, 0,
"%s::%s() expects %s %d parameter%s, %d given",
ce->name,
method_ptr->common.function_name,
min_num_args == max_num_args ? "exactly" : argc < min_num_args ? "at least" : "at most",
argc < min_num_args ? min_num_args : max_num_args,
(argc < min_num_args ? min_num_args : max_num_args) == 1 ? "" : "s",
argc);
return_value = V8JS_THROW(TypeError, error, error_len);
if (ce == zend_ce_closure) {
efree(method_ptr->internal_function.function_name);
efree(method_ptr);
}
efree(error);
return return_value;
}
/* Convert parameters passed from V8 */
if (argc) {
flags = V8JS_GLOBAL_GET_FLAGS();
fci.params = (zval ***) safe_emalloc(argc, sizeof(zval **), 0);
argv = (zval **) safe_emalloc(argc, sizeof(zval *), 0);
for (i = 0; i < argc; i++) {
MAKE_STD_ZVAL(argv[i]);
if (v8js_to_zval(args[i], argv[i], flags TSRMLS_CC) == FAILURE) {
fci.param_count++;
error_len = spprintf(&error, 0, "converting parameter #%d passed to %s() failed", i + 1, method_ptr->common.function_name);
return_value = V8JS_THROW(Error, error, error_len);
efree(error);
goto failure;
}
fci.params[fci.param_count++] = &argv[i];
}
} else {
fci.params = NULL;
}
fci.no_separation = 1;
/* zend_fcall_info_cache */
fcc.initialized = 1;
fcc.function_handler = method_ptr;
fcc.calling_scope = ce;
fcc.called_scope = ce;
fcc.object_ptr = value;
/* Call the method */
zend_call_function(&fci, &fcc TSRMLS_CC);
failure:
/* Cleanup */
if (argc) {
for (i = 0; i < fci.param_count; i++) {
zval_ptr_dtor(&argv[i]);
}
efree(argv);
efree(fci.params);
}
if (retval_ptr != NULL) {
return_value = zval_to_v8js(retval_ptr TSRMLS_CC);
zval_ptr_dtor(&retval_ptr);
} else {
return_value = V8JS_NULL;
}
return return_value;
}
/* }}} */
static int _php_v8js_is_assoc_array(HashTable *myht TSRMLS_DC) /* {{{ */
{
int i;
char *key;
ulong index, idx = 0;
uint key_len;
HashPosition pos;
zend_hash_internal_pointer_reset_ex(myht, &pos);
for (;; zend_hash_move_forward_ex(myht, &pos)) {
i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
if (i == HASH_KEY_NON_EXISTANT)
break;
if (i == HASH_KEY_IS_STRING || index != idx) {
return 1;
}
idx++;
}
return 0;
}
/* }}} */
static v8::Handle<v8::Value> php_v8js_property_caller(const v8::Arguments &args) /* {{{ */
{
v8::Local<v8::Object> self = args.Holder();
v8::Local<v8::String> cname = args.Callee()->GetName()->ToString();
v8::Local<v8::Value> value;
v8::Local<v8::String> cb_func = v8::Local<v8::String>::Cast(args.Data());
value = self->GetHiddenValue(cb_func);
if (!value.IsEmpty() && value->IsFunction())
{
int argc = args.Length(), i = 0;
v8::Local<v8::Value> *argv = new v8::Local<v8::Value>[argc];
v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(value);
if (cb_func->Equals(V8JS_SYM(ZEND_INVOKE_FUNC_NAME))) {
for (; i < argc; ++i) {
argv[i] = args[i];
}
value = cb->Call(self, argc, argv);
}
else /* __call() */
{
v8::Local<v8::Array> argsarr = v8::Array::New(argc);
for (; i < argc; ++i) {
argsarr->Set(i, args[i]);
}
v8::Local<v8::Value> argsv[2] = { cname, argsarr };
value = cb->Call(self, 2, argsv);
}
}
if (args.IsConstructCall()) {
if (!value.IsEmpty() && !value->IsNull()) {
return value;
}
return self;
}
return value;
}
/* }}} */
static v8::Handle<v8::Value> php_v8js_property_getter(v8::Local<v8::String> property, const v8::AccessorInfo &info) /* {{{ */
{
v8::Local<v8::Object> self = info.Holder();
v8::Local<v8::Value> value;
v8::Local<v8::Function> cb;
/* Check first if JS object has the named property */
value = self->GetRealNamedProperty(property);
if (!value.IsEmpty()) {
return value;
}
/* If __get() is set for PHP object, call it */
value = self->GetHiddenValue(V8JS_SYM(ZEND_GET_FUNC_NAME));
if (!value.IsEmpty() && value->IsFunction()) {
cb = v8::Local<v8::Function>::Cast(value);
v8::Local<v8::Value> argv[1] = {property};
value = cb->Call(self, 1, argv);
}
/* If __get() does not exist or returns NULL, create new function with callback for __call() */
if ((value.IsEmpty() || value->IsNull()) && info.Data()->IsTrue()) {
v8::Local<v8::FunctionTemplate> cb_t = v8::FunctionTemplate::New(php_v8js_property_caller, V8JS_SYM(ZEND_CALL_FUNC_NAME));
cb = cb_t->GetFunction();
cb->SetName(property);
return cb;
}
return value;
}
/* }}} */
static v8::Handle<v8::Integer> php_v8js_property_query(v8::Local<v8::String> property, const v8::AccessorInfo &info) /* {{{ */
{
v8::Local<v8::Object> self = info.Holder();
v8::Local<v8::Value> value;
/* Return early if property is set in JS object */
if (self->HasRealNamedProperty(property)) {
return V8JS_INT(v8::ReadOnly);
}
value = self->GetHiddenValue(V8JS_SYM(ZEND_ISSET_FUNC_NAME));
if (!value.IsEmpty() && value->IsFunction()) {
v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(value);
v8::Local<v8::Value> argv[1] = {property};
value = cb->Call(self, 1, argv);
}
return (!value.IsEmpty() && value->IsTrue()) ? V8JS_INT(v8::ReadOnly) : v8::Local<v8::Integer>();
}
/* }}} */
/* These are not defined by Zend */
#define ZEND_WAKEUP_FUNC_NAME "__wakeup"
#define ZEND_SLEEP_FUNC_NAME "__sleep"
#define ZEND_SET_STATE_FUNC_NAME "__set_state"
#define IS_MAGIC_FUNC(mname) \
((key_len == sizeof(mname)) && \
!strncasecmp(key, mname, key_len - 1))
#define PHP_V8JS_CALLBACK(mptr) \
v8::FunctionTemplate::New(php_v8js_php_callback, v8::External::New(mptr))->GetFunction()
static v8::Handle<v8::Value> php_v8js_hash_to_jsobj(zval *value TSRMLS_DC) /* {{{ */
{
v8::Local<v8::FunctionTemplate> new_tpl = v8::FunctionTemplate::New();
v8::Local<v8::Object> newobj;
int i;
char *key = NULL;
ulong index;
uint key_len;
HashTable *myht;
HashPosition pos;
zend_class_entry *ce = NULL;
zend_function *method_ptr, *call_ptr = NULL, *get_ptr = NULL, *invoke_ptr = NULL, *isset_ptr = NULL;
if (Z_TYPE_P(value) == IS_ARRAY) {
myht = HASH_OF(value);
} else {
myht = Z_OBJPROP_P(value);
ce = Z_OBJCE_P(value);
}
/* Prevent recursion */
if (myht && myht->nApplyCount > 1) {
return V8JS_NULL;
}
/* Object methods */
if (ce) {
new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length));
new_tpl->InstanceTemplate()->SetInternalFieldCount(1);
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;
}
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)
) {
/* 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));
}
newobj = new_tpl->InstanceTemplate()->NewInstance();
if (call_ptr) {
newobj->SetHiddenValue(V8JS_SYM(ZEND_CALL_FUNC_NAME), PHP_V8JS_CALLBACK(call_ptr));
}
if (get_ptr) {
newobj->SetHiddenValue(V8JS_SYM(ZEND_GET_FUNC_NAME), PHP_V8JS_CALLBACK(get_ptr));
}
if (invoke_ptr) {
newobj->SetHiddenValue(V8JS_SYM(ZEND_INVOKE_FUNC_NAME), PHP_V8JS_CALLBACK(invoke_ptr));
}
if (isset_ptr) {
newobj->SetHiddenValue(V8JS_SYM(ZEND_ISSET_FUNC_NAME), PHP_V8JS_CALLBACK(isset_ptr));
}
}
newobj->SetPointerInInternalField(0, (void *) value);
} else {
new_tpl->SetClassName(V8JS_SYM("Array"));
newobj = new_tpl->InstanceTemplate()->NewInstance();
}
/* Object properties */
i = myht ? zend_hash_num_elements(myht) : 0;
if (i > 0)
{
zval **data;
HashTable *tmp_ht;
zend_hash_internal_pointer_reset_ex(myht, &pos);
for (;; zend_hash_move_forward_ex(myht, &pos)) {
i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
if (i == HASH_KEY_NON_EXISTANT)
break;
if (zend_hash_get_current_data_ex(myht, (void **) &data, &pos) == SUCCESS)
{
tmp_ht = HASH_OF(*data);
if (tmp_ht) {
tmp_ht->nApplyCount++;
}
if (i == HASH_KEY_IS_STRING)
{
if (key[0] == '\0' && Z_TYPE_P(value) == IS_OBJECT) {
/* Skip protected and private members. */
if (tmp_ht) {
tmp_ht->nApplyCount--;
}
continue;
}
newobj->Set(V8JS_STRL(key, key_len - 1), zval_to_v8js(*data TSRMLS_CC), v8::ReadOnly);
} else {
newobj->Set(index, zval_to_v8js(*data TSRMLS_CC));
}
if (tmp_ht) {
tmp_ht->nApplyCount--;
}
}
}
}
return newobj;
}
/* }}} */
static v8::Handle<v8::Value> php_v8js_hash_to_jsarr(zval *value TSRMLS_DC) /* {{{ */
{
HashTable *myht = HASH_OF(value);
int i = myht ? zend_hash_num_elements(myht) : 0;
/* Return object if dealing with assoc array */
if (i > 0 && _php_v8js_is_assoc_array(myht TSRMLS_CC)) {
return php_v8js_hash_to_jsobj(value TSRMLS_CC);
}
v8::Local<v8::Array> newarr;
/* Prevent recursion */
if (myht && myht->nApplyCount > 1) {
return V8JS_NULL;
}
newarr = v8::Array::New(i);
if (i > 0)
{
zval **data;
ulong index = 0;
HashTable *tmp_ht;
HashPosition pos;
for (zend_hash_internal_pointer_reset_ex(myht, &pos);
SUCCESS == zend_hash_get_current_data_ex(myht, (void **) &data, &pos);
zend_hash_move_forward_ex(myht, &pos)
) {
tmp_ht = HASH_OF(*data);
if (tmp_ht) {
tmp_ht->nApplyCount++;
}
newarr->Set(index++, zval_to_v8js(*data TSRMLS_CC));
if (tmp_ht) {
tmp_ht->nApplyCount--;
}
}
}
return newarr;
}
/* }}} */
v8::Handle<v8::Value> zval_to_v8js(zval *value TSRMLS_DC) /* {{{ */
{
v8::Handle<v8::Value> jsValue;
switch (Z_TYPE_P(value))
{
case IS_ARRAY:
jsValue = php_v8js_hash_to_jsarr(value TSRMLS_CC);
break;
case IS_OBJECT:
jsValue = php_v8js_hash_to_jsobj(value TSRMLS_CC);
break;
case IS_STRING:
jsValue = V8JS_STRL(Z_STRVAL_P(value), Z_STRLEN_P(value));
break;
case IS_LONG:
jsValue = V8JS_INT(Z_LVAL_P(value));
break;
case IS_DOUBLE:
jsValue = V8JS_FLOAT(Z_DVAL_P(value));
break;
case IS_BOOL:
jsValue = V8JS_BOOL(Z_BVAL_P(value));
break;
default:
case IS_NULL:
jsValue = V8JS_NULL;
break;
}
return jsValue;
}
/* }}} */
int v8js_to_zval(v8::Handle<v8::Value> jsValue, zval *return_value, int flags TSRMLS_DC) /* {{{ */
{
if (jsValue->IsString())
{
v8::String::Utf8Value str(jsValue);
const char *cstr = ToCString(str);
RETVAL_STRING(cstr, 1);
}
else if (jsValue->IsBoolean())
{
RETVAL_BOOL(jsValue->Uint32Value());
}
else if (jsValue->IsInt32() || jsValue->IsUint32())
{
RETVAL_LONG((long) jsValue->IntegerValue());
}
else if (jsValue->IsNumber())
{
RETVAL_DOUBLE(jsValue->NumberValue());
}
else if (jsValue->IsDate()) /* Return as a PHP DateTime object */
{
v8::String::Utf8Value str(jsValue);
const char *cstr = ToCString(str);
zend_class_entry *ce = php_date_get_date_ce();
#if PHP_VERSION_ID < 50304
zval *param;
MAKE_STD_ZVAL(param);
ZVAL_STRING(param, cstr, 1);
object_init_ex(return_value, ce TSRMLS_CC);
zend_call_method_with_1_params(&return_value, ce, &ce->constructor, "__construct", NULL, param);
zval_ptr_dtor(&param);
if (EG(exception)) {
return FAILURE;
}
#else
php_date_instantiate(ce, return_value TSRMLS_CC);
if (!php_date_initialize((php_date_obj *) zend_object_store_get_object(return_value TSRMLS_CC), (char *) cstr, strlen(cstr), NULL, NULL, 0 TSRMLS_CC)) {
return FAILURE;
}
#endif
}
else if (jsValue->IsObject())
{
if ((flags & V8JS_FLAG_FORCE_ARRAY) || jsValue->IsArray()) {
array_init(return_value TSRMLS_CC);
return php_v8js_v8_get_properties_hash(jsValue, Z_ARRVAL_P(return_value), flags TSRMLS_CC);
} else {
php_v8js_create_v8(return_value, jsValue, flags TSRMLS_CC);
return SUCCESS;
}
}
else /* types External, RegExp, Undefined and Null are considered NULL */
{
RETVAL_NULL();
}
return SUCCESS;
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

183
v8js_methods.cc Normal file
View File

@ -0,0 +1,183 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
extern "C" {
#include "php.h"
}
#include "php_v8js_macros.h"
#include <v8.h>
/* global.exit - terminate execution */
V8JS_METHOD(exit) /* {{{ */
{
v8::V8::TerminateExecution();
return v8::Undefined();
}
/* }}} */
/* global.sleep - sleep for passed seconds */
V8JS_METHOD(sleep) /* {{{ */
{
php_sleep(args[0]->Int32Value());
return v8::Undefined();
}
/* }}} */
/* global.print - php print() */
V8JS_METHOD(print) /* {{{ */
{
int ret = 0;
TSRMLS_FETCH();
for (int i = 0; i < args.Length(); i++) {
v8::String::Utf8Value str(args[i]);
const char *cstr = ToCString(str);
ret = PHPWRITE(cstr, strlen(cstr));
}
return V8JS_INT(ret);
}
/* }}} */
static void _php_v8js_dumper(v8::Local<v8::Value> var, int level TSRMLS_CC) /* {{{ */
{
v8::String::Utf8Value str(var->ToDetailString());
const char *valstr = ToCString(str);
size_t valstr_len = (valstr) ? strlen(valstr) : 0;
if (level > 1) {
php_printf("%*c", (level - 1) * 2, ' ');
}
if (var->IsString())
{
php_printf("string(%d) \"%s\"\n", valstr_len, valstr);
}
else if (var->IsBoolean())
{
php_printf("bool(%s)\n", valstr);
}
else if (var->IsInt32() || var->IsUint32())
{
php_printf("int(%s)\n", valstr);
}
else if (var->IsNumber())
{
php_printf("float(%s)\n", valstr);
}
else if (var->IsDate())
{
php_printf("Date(%s)\n", valstr);
}
else if (var->IsRegExp())
{
php_printf("RegExp(%s)\n", valstr);
}
else if (var->IsArray())
{
v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(var);
uint32_t length = array->Length();
php_printf("array(%d) {\n", length);
for (unsigned i = 0; i < length; i++) {
php_printf("%*c[%d] =>\n", level * 2, ' ', i);
_php_v8js_dumper(array->Get(i), level + 1 TSRMLS_CC);
}
if (level > 1) {
php_printf("%*c", (level - 1) * 2, ' ');
}
ZEND_PUTS("}\n");
}
else if (var->IsObject())
{
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(var);
V8JS_GET_CLASS_NAME(cname, object);
if (var->IsFunction())
{
v8::String::Utf8Value csource(object->ToString());
php_printf("object(%s)#%d {\n%*c%s\n", ToCString(cname), object->GetIdentityHash(), level * 2 + 2, ' ', ToCString(csource));
}
else
{
v8::Local<v8::Array> keys = object->GetPropertyNames();
uint32_t length = keys->Length();
php_printf("object(%s)#%d (%d) {\n", ToCString(cname), object->GetIdentityHash(), length);
for (unsigned i = 0; i < length; i++) {
v8::Local<v8::String> key = keys->Get(i)->ToString();
v8::String::Utf8Value kname(key);
php_printf("%*c[\"%s\"] =>\n", level * 2, ' ', ToCString(kname));
_php_v8js_dumper(object->Get(key), level + 1 TSRMLS_CC);
}
}
if (level > 1) {
php_printf("%*c", (level - 1) * 2, ' ');
}
ZEND_PUTS("}\n");
}
else /* null, undefined, etc. */
{
php_printf("<%s>\n", valstr);
}
}
/* }}} */
/* global.var_dump - Dump JS values */
V8JS_METHOD(var_dump) /* {{{ */
{
int i;
TSRMLS_FETCH();
for (int i = 0; i < args.Length(); i++) {
_php_v8js_dumper(args[i], 1 TSRMLS_CC);
}
return V8JS_NULL;
}
/* }}} */
void php_v8js_register_methods(v8::Handle<v8::ObjectTemplate> global) /* {{{ */
{
global->Set(V8JS_SYM("exit"), v8::FunctionTemplate::New(V8JS_MN(exit)), v8::ReadOnly);
global->Set(V8JS_SYM("sleep"), v8::FunctionTemplate::New(V8JS_MN(sleep)), v8::ReadOnly);
global->Set(V8JS_SYM("print"), v8::FunctionTemplate::New(V8JS_MN(print)), v8::ReadOnly);
global->Set(V8JS_SYM("var_dump"), v8::FunctionTemplate::New(V8JS_MN(var_dump)), v8::ReadOnly);
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

91
v8js_variables.cc Normal file
View File

@ -0,0 +1,91 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Jani Taskinen <jani.taskinen@iki.fi> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
extern "C" {
#include "php.h"
}
#include "php_v8js_macros.h"
#include <v8.h>
static v8::Handle<v8::Value> php_v8js_fetch_php_variable(v8::Local<v8::String> name, const v8::AccessorInfo &info) /* {{{ */
{
v8::String::Utf8Value variable_name(info.Data()->ToString());
const char *variable_name_string = ToCString(variable_name);
uint variable_name_string_len = strlen(variable_name_string);
zval **variable;
TSRMLS_FETCH();
zend_is_auto_global(variable_name_string, variable_name_string_len TSRMLS_CC);
if (zend_hash_find(&EG(symbol_table), variable_name_string, variable_name_string_len + 1, (void **) &variable) == SUCCESS) {
return zval_to_v8js(*variable TSRMLS_CC);
}
return v8::Undefined();
}
/* }}} */
void php_v8js_register_accessors(v8::Local<v8::ObjectTemplate> php_obj, zval *array TSRMLS_DC) /* {{{ */
{
char *property_name;
uint property_name_len;
ulong index;
zval **item;
for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(array));
zend_hash_get_current_data(Z_ARRVAL_P(array), (void **) &item) != FAILURE;
zend_hash_move_forward(Z_ARRVAL_P(array))
) {
switch (Z_TYPE_PP(item))
{
/*
case IS_OBJECT:
case IS_ARRAY:
*/
case IS_STRING:
break;
default:
continue; /* Ignore invalid values */
}
if (zend_hash_get_current_key_ex(Z_ARRVAL_P(array), &property_name, &property_name_len, &index, 0, NULL) != HASH_KEY_IS_STRING) {
continue; /* Ignore invalid property names */
}
/* Set the variable fetch callback for given symbol on named property */
php_obj->SetAccessor(V8JS_STRL(property_name, property_name_len - 1), php_v8js_fetch_php_variable, NULL, V8JS_STRL(Z_STRVAL_PP(item), Z_STRLEN_PP(item)), v8::PROHIBITS_OVERWRITING, v8::ReadOnly);
}
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/