Using JavaScript in PHP with PECL and SpiderMonkey

June 3, 2009

Tutorials

The Good Old Days

Not too long ago, it seemed like there was a pretty clear distinction between client-side technologies and server-side technologies. Languages like PHP, Perl and Python resided on the server, taking care of tasks like database connectivity, transaction management and remote procedure calls, while tools like JavaScript, CSS and HTML were used exclusively on the client to render pages, perform whizzy effects and respond to user events.

Things aren’t that clear any longer. Projects like Jaxer and Phobos are blurring these distinctions, by making it possible to run JavaScript on the server and use it for tasks ranging from server-side file access to input validation. And in this article, I’m going to show you how to add a JavaScript engine to your PHP build, with a little help from PECL’s SpiderMonkey extension. Keep reading!

Monkeying Around

SpiderMonkey is an ECMAScript-compliant JavaScript engine, developed and maintained by Mozilla. It is one of the components of the Mozilla Firefox Web browser (in addition to being part of many other projects). It is released as a library that can be easily embedded into other applications.

SpiderMonkey support in PHP comes through PECL’s ext/spidermonkey extension, which is maintained by Christophe Robin, and provides an object-oriented API for accessing the SpiderMonkey library. Although this extension is currently in beta, it still allows you to do some fairly interesting things, including registering and using variables, functions and classes from PHP in JavaScript.

To get started with ext/spidermonkey, you’ll need to first make sure that you’re running PHP v5.3.x (beta or RC will do), as the extension will not work on any earlier version. If you’ve got this, download the SpiderMonkey libraries (v1.7.0) from Mozilla’s FTP site and compile them for your system. Assuming you’re on a *NIX system, here’s how:

shell# tar -xzvf js-1.70.tar-gz
shell# cd js/src

shell# make -f Makefile.ref

Manually install the compiled libraries as below (based on these instructions):

shell# mkdir -p /usr/local/include/js/ 
shell# cp *.{h,tbl} /usr/local/include/js/ 

shell# cd Linux_All_DBG.OBJ
shell# cp *.h /usr/local/include/js/ 
shell# cp js /usr/local/bin/ 
shell# cp libjs.so /usr/local/lib/
shell# ldconfig

Once this is done, download, compile and install the latest revision of the SpiderMonkey extension from SVN with the phpize command (this article uses revision 51):

shell# cd sm-51/
shell# phpize
shell# ./configure
shell# make

shell# make install

This procedure should create a loadable PHP module named spidermonkey.so in your PHP extension directory. You should now enable the extension in the php.ini configuration file, restart your Web server, and check that the extension is enabled with a quick call to phpinfo():

Putting It All In Context

Once you’ve got the extension installed and working, it’s time to take it for a test drive. Here’s a simple example, which initializes some PHP variables and then assigns them to the JavaScript context:


<?php
// create JavaScript context
$js = new JSContext();

// define PHP variables
$a = 10;
$b = 2;

// assign variables to JavaScript context
$js->assign('a', $a);
$js->assign('b', $b);

// define script code
$script = <<<END
  c = a + b;
END;

// evaluate script and display result
echo "The sum of $a and $b is: " . $js->evaluateScript($script);
?>

The script begins by initializing the JavaScript interpreter, or “context”, as a JSContext object. This object provides an entry point into SpiderMonkey, exposing methods that can be used to register PHP objects with JavaScript. One of these methods is the assign() method, which assigns a PHP variable to a JavaScript variable. This method is used in the example above to assign the values of PHP variables $a and $b to corresponding JavaScript variables.

Once these JavaScript variables have been created, they can be used in the normal fashion, as though you were writing regular JavaScript. The example above demonstrates this, writing a one-line JavaScript program that adds them and assigns the result to another JavaScript variable. To executing the JavaScript, simply call the JSContext object’s evaluateScript() method, which takes care of actually running the code under SpiderMonkey. The return value of the evaluateScript() method is the last value in the JavaScript context’s global scope.

It’s important to note, at this point, that the SpiderMonkey library merely provides a JavaScript implementation. While it supports all the core JavaScript data types, objects and methods, it does not provide a windowing toolkit or a DOM implementation (these components are provided by the browser application) and, therefore, you can’t expect to pop up alert boxes or dynamically manipulate HTML pages with it.

Here’s the output of the previous example:

In addition to standard variables, you can also assign arrays to the JavaScript context. Here’s an example:

<?php
// create JavaScript context
$js = new JSContext();

// define PHP array
$arr = array('Rachel', 'Phoebe', 'Monica', 'Chandler', 'Joey', 'Ross');

// assign array to JavaScript context
$js->assign('friends', $arr);

// define script code
$script = <<<END
  num = Math.round(Math.random() * 5);
  ret = friends[num] + ' is my BFF.';
END;

// evaluate script and display result
echo $js->evaluateScript($script);
?>

In this case, the PHP array $friends is assigned to the JavaScript context, and JavaScript array notation is then used, together with the JavaScript Math object, to randomly select and display an array element. Here’s what the output might look like:

The Milkman Cometh

Interestingly, you can also define functions in PHP, and then later use them in a JavaScript code block. Here’s an example, which defines a PHP function to calculate the area of a circle and then invokes this function from within a JavaScript code block:

<?php
// define PHP function
function getCircleArea($radius) {
  return pi() * $radius * $radius;
}

// create JavaScript context
$js = new JSContext();

// register function in JavaScript context
$js->registerFunction('getCircleArea', 'gca');

// define script code
$script = <<<END
  ret = 'Area of circle with radius 5 is: ' + gca(5);
END;

// evaluate script and display result
echo $js->evaluateScript($script);
?>

Here’s the output:

What works with functions, also works with classes. As an illustration, consider this example PHP class:

<?php
// class definition
class Cow {

  private $_name;
  private $_milked;

  public function __construct() {
    $this->_milked = 0;
  }

  // name setter/getter
  public function setName($name) {
    $this->_name = $name;
  }

  public function getName() {
    return $this->_name;
  }

  // milking status setter/getter
  public function milk() {
    $this->_milked = 1;
  }

  public function getMilked() {
    return $this->_milked;
  }

  public function output() {
    if ($this->getMilked() == 1) {
      return $this->getName() . ' has been milked today.';
    } else {
      return 'Time to milk ' . $this->getName();
    }

  }
}
?>

Consider this next example, which reads and registers the above class definition in the JavaScript context and then creates a JavaScript object from it:

<?php
// load class file
include_once 'Cow.class.php';

// create JavaScript context
$js = new JSContext();

// register PHP class in JavaScript
$js->registerClass('Cow');

// define script code
$script = <<<END
  var c = new Cow;
  c.setName('Molly');
  var d = new Date();
  var ch = d.getHours();
  if (ch > 8) {
    c.milk();
  }
  c.output();
END;

// evaluate script and display result
echo $js->evaluateScript($script);
?>

When you try accessing this script, you’ll see one of the following messages, depending on what time it is on the server:

An Object Lesson

You can also register PHP objects with assign(), and use them from JavaScript. Here’s a simple example to whet your appetite:

<?php
// create JavaScript context
$js = new JSContext();

// define PHP object
$obj = new stdClass;
$obj->age = 19;
$obj->name = 'Roger';

// assign object to JavaScript context
$js->assign('me', $obj);

// define script code
$script = <<<END
  function whoami() {
    str = 'My name is ' + me.name +
          ' and I am ' + me.age + ' years old.';
    return str;
  }
  whoami();
END;

// evaluate script and display result
echo $js->evaluateScript($script);
?>

In this case, a PHP object is initialized, values are set for several properties and it is then assigned to the JavaScript context with assign(). The executeScript() method then runs a JavaScript code block that uses those object properties, printing them to the output device. Here’s what the output looks like:

This isn’t restricted just to user-defined objects either: consider the next example, which creates a SimpleXML object from an XML file and then uses it to access XML elements in the JavaScript context. First, here’s the example XML file:

<?xml version="1.0"?>
<pet>
  <name>Polly Parrot</name>

  <age>3</age>
  <species>parrot</species>
  <parents>
    <mother>Pia Parrot</mother>

    <father>Peter Parrot</father>
  </parents>
</pet>

And here’s the script that uses this XML:

<?php
// create JavaScript context
$js = new JSContext();

// load XML file as SimpleXML object
$xml = simplexml_load_file('polly.xml') or die ("Unable to load XML!");

// assign object to JavaScript
$js->assign('sxml', $xml);

// define script code
$script = <<<END
  c = sxml.name;
  m = sxml.parents.mother;
  f = sxml.parents.father;
END;

// evaluate code and display result
$js->evaluateScript($script);
printf('%s and %s are the proud parents of little %s.', 
          $js->evaluateScript('f'), 
          $js->evaluateScript('m'), 
          $js->evaluateScript('c')
);
?>

Here, the simplexml_load_file() function is used to create a SimpleXML representation of the source XML document, and the resulting object is transferred to the JavaScript context with assign(). Individual nodes of the XML document can now be accessed in JavaScript as regular object properties. Here’s what the output of the script looks like:

Making Movies

Why stop there? You can also register built-in PHP classes with the JavaScript context, and use them in JavaScript just like you would in PHP. To illustrate, consider the next example, which registers the XMLWriter class in the JavaScript context and then uses it to create an XML document from scratch:

<?php
// define array
$departments = array('Finance', 'Sales', 'Marketing', 'Operations', 'Engineering', 'Procurement');

// create JavaScript context
$js = new JSContext();

// assign array to JavaScript context
$js->assign('departments', $departments);

// register SQLite3 class in JavaScript context
$js->registerClass('XMLWriter');

// define script code
$script = <<<END
  var writer = new XMLWriter();
  writer.openMemory();
  writer.startDocument('1.0');
  writer.startElement('departments');
  for (x=0; x<6; x++) {
    writer.startElement('name');
    writer.text(departments[x]);
    writer.endElement();
  }
  writer.endElement();
  writer.endDocument();
  writer.flush();
END;

// evaluate code and display result
header('Content-Type: text/xml');
echo $js->evaluateScript($script);
?>

This script registers PHP’s XMLWriter class in the JavaScript context, and then uses it to dynamically create an XML document. Notice that although the script uses JavaScript objects, the procedure and method calls to produce the XML are exactly the same as those you would use in PHP.

Here’s what the output will look like in a Web browser:

It’s not all XML either – you can also register database access classes, like PHP’s SQLite3 or MySQLi classes, and use them in JavaScript. To illustrate, first create a simple SQLite3 database named ‘mydb’, as below

shell# sqlite3 mydb

SQLite version 3.6.14.2
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> CREATE TABLE movies (
   ...> id INTEGER PRIMARY KEY,
   ...> title TEXT
   ...> );
sqlite> INSERT INTO movies VALUES (1, 'Rear Window');

sqlite> INSERT INTO movies VALUES (2, 'Batman Begins');
sqlite> INSERT INTO movies VALUES (3, 'Moulin Rouge');
sqlite> INSERT INTO movies VALUES (4, 'A Bridge Too Far');
sqlite> INSERT INTO movies VALUES (5, 'The Blair Witch Project');
sqlite> SELECT * FROM movies;
1|Rear Window
2|Batman Begins
3|Moulin Rouge
4|A Bridge Too Far
5|The Blair Witch Project

Then, register the PHP SQLite3 class with the JavaScript context, and use it to do the same thing:

<html>
  <head></head>
  <body>
  <h2>My Movies</h2>

<?php
// create JavaScript context
$js = new JSContext();

// register print function in JavaScript context
function write($val) {
  echo $val;
}
$js->registerFunction('write');

// register SQLite3 class in JavaScript context
$js->registerClass('SQLite3');

// define script code
$script = <<<END
  var db = new SQLite3('mydb');
  var results = db.query('SELECT * FROM movies');
  write('<ul>');
  while (row = results.fetchArray()) {
    write('<li>' + row[1] + '</li>');
  }
  write('</ul>');
END;

// evaluate code and display result
$js->evaluateScript($script);
?>
  </body>

</html>

This example uses the registerClass() method to register PHP’s SQLite3 class, and the registerFunction() method to register a simple utility function for printing values. It then instantiates a new JavaScript SQLite3 object and uses it to open a handle to the SQLite3 database file created in the previous step. Once this object has been instantiated, it’s possible to use SQLite3 object methods to execute a query on the database and process the resulting data in a while() loop…again, all of this using JavaScript rather than PHP.

Here’s what the output looks like:

As these examples illustrate, PHP’s SpiderMonkey extension makes it easy to add a JavaScript interpreter to your PHP build. It’s particularly good for applications that need a limited, yet extensible, JavaScript implementation, as one of its key features is the ability to register and use custom PHP classes within the JavaScript context. While the extension is still not fully complete – according to the author’s blog, support for Iterators and better error reporting are on the anvil – it nevertheless provides a testbed for some fairly interesting experiments and discussions. Try it out sometime, and see what you think!

Copyright Melonfire, 2009. All rights reserved.

7 Responses to “Using JavaScript in PHP with PECL and SpiderMonkey”

  1. netmosfera Says:

    this is the final solution to templating in a sandbox

    use js to implement a template engine gives advantages to everyone: template designer and developers

    IT IS INCREDIBLY FAST, i’ve made some tests, no more need to cache or compile views

    unlike xslt, for example, js syntax it is not a pain for template designers ( that probably already know )

    i suggest to install and test it, and report bugs

    painless installation on debian/ubuntu:

    sudo apt-get install php5dev
    sudo apt-get install php-pear
    sudo apt-get install xulrunner-dev
    sudo pecl install spidermonkey-beta

    then add extension=spidermonkey.so to /etc/php/apache2/php.ini

    sudo apache2ctl restart

  2. metrofun Says:

    We are trying to use spidermonkey in our framework, but when using it with mod_rewrite apache hangs.
    Here is .htacceess

    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ js.php [QSA,L]

    Here is js.php

    <?php
    $js_script = ’5+5′;
    $js = new JSContext();
    $output = $js->evaluateScript($js_script);
    echo ‘js.php’;
    echo $output;
    ?>

    And every second time i update the page, it hangs

  3. metrofun Says:

    When i use RewriteEngine with your extension, it hangs every second time

  4. cvanek Says:

    We’ve been working on integrating SpiderMonkey into our product for a while now as a dev-team hobby project, this is perfect.

    So much more elegant than our own php extension.

    Thanks for the tutorial!

  5. vvaswani Says:

    Yes, I think you could do that. However, one caveat is that the extension sometimes "loses information" when converting PHP objects to JavaScript. So IMO, you’d need to be aware of those issues and build in appropriate safeguards when working with user-supplied JavaScript.

  6. adamcharnock Says:

    I was wondering if this has the potential to sandbox code provided by the client/user? For example, complex searches could be specified in JavaScript (similar to views in CouchDB) rather than in a long series of parameters.

    I think it could be an interesting way of users providing logic to a web application. Admittedly, it would require users to have a certain level of technical understanding, but I think plenty of apps will fit that profile.

    Adam Charnock
    porteightyeight.com

  7. samhennessy Says:

    I didn’t know you could do so much with SpiderMonkey like registering classes and objects. Thanks for he info.