I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

Running JavaScript inside PHP code

02.29.2012
| 23919 views |
  • submit to reddit

v8js is a new PHP extension able to run JavaScript code inside V8, Google's JavaScript interpreter that powers for example Chrome and NodeJS.

This extension is highly alpha - and its API would probably change in the months ahead. Since documentation is lacking, I invite you to repeat the discovering process I follow in this post in case you find some differences in a new version of v8js.

Installation

V8 must be present on the machine in order to install the extension. On a Debian/Ubuntu system, run the following:

sudo apt-get install libv8-dev libv8-dbg

libv8-dev will also install libv8 in its latest version.

Afterwards, download and compile the extension with:

sudo pecl install v8js

There are no more requirements for compilation apart from the usual dependencies for PECL packages, like build-essential.

After that, add:

extension=v8js.so

to php.ini or to a section in conf.d.

$ php -m | grep v8
v8js

will confirm you the extension is loaded.

Some introspection

PHP's reflection let us take a look at the classes and methods provided by this extension, even without documentation available. It probably hasn't been written yet, due to the unstable API.

$ php -r 'var_dump(get_declared_classes());' | grep V8
  string(8) "V8Object"
  string(10) "V8Function"
  string(4) "V8Js"
  string(13) "V8JsException"
$ php -r '$class = new ReflectionClass("V8Js"); var_dump($class->getMethods());'
array(5) {
  [0]=>
  &object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(11) "__construct"
    ["class"]=>
    string(4) "V8Js"
  }
  [1]=>
  &object(ReflectionMethod)#3 (2) {
    ["name"]=>
    string(13) "executeString"
    ["class"]=>
    string(4) "V8Js"
  }
  [2]=>
  &object(ReflectionMethod)#4 (2) {
    ["name"]=>
    string(19) "getPendingException"
    ["class"]=>
    string(4) "V8Js"
  }
  [3]=>
  &object(ReflectionMethod)#5 (2) {
    ["name"]=>
    string(17) "registerExtension"
    ["class"]=>
    string(4) "V8Js"
  }
  [4]=>
  &object(ReflectionMethod)#6 (2) {
    ["name"]=>
    string(13) "getExtensions"
    ["class"]=>
    string(4) "V8Js"
  }
}
$ php -r '$v8 = new V8Js(); var_dump($v8->executeString("1+2+3"));'
int(6)

Interesting! We have just executed our first JavaScript expression inside a PHP process. Apparently the last statement's value is returned by the executeString() method, with a rough conversion preserving the type:

$ php -r '$v8 = new V8Js(); var_dump($v8->executeString("var obj = {}; obj.field = 1; obj.field++; obj.field;"));'
int(2)

Syntax or runtime errors are signaled with a V8JsException:

$ php -r '$v8 = new V8Js(); var_dump($v8->executeString("var obj = {"));'
PHP Fatal error:  Uncaught exception 'V8JsException' with message 'V8Js::executeString():1: SyntaxError: Unexpected end of input' in Command line code:1
Stack trace:
#0 Command line code(1): V8Js->executeString('var obj = {')
#1 {main}
  thrown in Command line code on line 1

Let's add more difficulty

The FizzBuzz kata OO solution is an example of JavaScript code creating an object and executing anonymous functions: it's a good test bench for our integration.Since evaluating a variable as the last line returns it, that is our channel of communication, supporting integers, strings, floats, booleans, arrays (not objects at this time). Meanwhile, input for JavaScript code can be embedded into the executed string.

This code will output string(8) "FizzBuzz":

<?php
$javaScriptCode = '
function FizzBuzz(correspondences) {
    this.correspondences = correspondences;
    this.accept = function (number) {
        var result = ""
        for (var divisor in this.correspondences) {
            if (number % divisor == 0) {
                result = result + this.correspondences[divisor];
            }
        }
        if (result) {
            return result;
        } else {
            return number;
        }
    }
}
var myFizzBuzz = new FizzBuzz({3 : "Fizz", 5 : "Buzz"});
myFizzBuzz.accept(15);
';
$v8 = new V8Js();
var_dump($v8->executeString($javaScriptCode));

By changing it a bit, we can build a JSON string by backslashing the double quotes ("), and returns it in lieu of an object to communicate to the PHP process a complex result:

...
var myFizzBuzz = new FizzBuzz({3 : "Fizz", 5 : "Buzz"});
"{\"15\" : \"" + myFizzBuzz.accept(15) + "\", \"5\" : \"" + myFizzBuzz.accept(5) + "\"}";
';
$v8 = new V8Js();
$result = $v8->executeString($javaScriptCode);
var_dump($result);
var_dump(json_decode($result));

The output is:

string(33) "{"15" : "FizzBuzz", "5" : "Buzz"}"
object(stdClass)#2 (2) {
  ["15"]=>
  string(8) "FizzBuzz"
  ["5"]=>
  string(4) "Buzz"
}

Wiring

Executing an external script would be nice: it would provide better stack traces, with traceable line numbers at the JavaScript level. It would also mean we won't need backslashing for single quotes, simplifying the syntax.

We cannot load scripts on the JavaScript side out of the box, due to V8 missing this functionality (Node JS adds this feature) but we can load the code on the PHP Side:

// test.js
$ php loadingfiles.php
string(24) "test.js file was loaded."
// loadingfiles.php
<?php
$javascriptCode = file_get_contents("test.js");
$v8 = new V8Js();
$result = $v8->executeString($javascriptCode);
var_dump($result);

Execution results in:

$ php loadingfiles.php
string(24) "test.js file was loaded."

However, we still miss the capability of including JavaScript libraries.

Conclusion

There are many possible use cases for v8js, like sandboxed scripting or the integration of some code which was written for the client side. That would not be the most clean solution, but it's a Turing complete approach, so why not?

After all, it's already possible to run PHP on Java or Python and Ruby on .NET. JavaScript is becoming ubiquitous, so why not providing support for it, even in a caged box?

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)