I’ve been working for the last month on my first PHP extension since 2 years ago, a nice way to learn the new stuff Zend had in store for PHP 5.3. This extension is named SpiderMonkey, and embed the original engine made by Mozilla and available on most Linux distributions.
SpiderMonkey
SpiderMonkey is the Javascript engine used by Mozilla’s main products. It provide a nice C API for executing and interacting with Javascript. I base myself on the 1.7.0 version, which is the latest stable version. 1.8.0 is supposed to come soon but is still under development.
Usage
The API is straight-forward, a Javascript Runtime is first created, it’s a container for all “global” variables and Javascript Contexts. Contexts is the global scope where your program runs, all scripts executed on the same contexts will share the same global variables.
This part changed in rev. 38, see below
$rt = new JSRuntime();
/* Create a single context, most application will only use
* one context while a server ( like the pinetd httpd server )
* would create a context by request */
$ctx = $rt->createContext();
Since rev. 38, you don’t need to instanciate the JSRuntime anymore
* one context while a server ( like the pinetd httpd server )
* would create a context by request */
$ctx = new JSContext();
Once the context created, you’ll want to do two things:
- Assign values from PHP to JS
- Register functions from PHP to JS
For now, variables assigned from PHP are converted to a new Javascript variable and changing them in JS will not affect the original PHP value, while it’s supposed to be allowed later ( using a different function that will assign the variable as a reference and not a copy ), it’s already enough to provide $_GET, $_POST, $_FILES and the likes to JS. The only case where this change is for objects and resources, which keeps pointers to themselves thus allowing you to export a $dom variable containing a DOMDocument and acting on it by the mean of the object methods ( the object properties are not available ).
Exported functions are declared in the global scope.
Here is an exemple with mysqli:
$mysqli = new mysqli("host", "user", "pass", "db");
/* Allow Javascript to call sprintf under the name sprintf */
$ctx->registerFunction('sprintf', 'sprintf');
/* Provide our db connexion to Javascript */
$ctx->assign('db', $mysqli);
Then in Javascript you’d do:
* i'll stick with it in this sample */
res = db.query(sprintf('SELECT * FROM my_table WHERE field = %s', db.escape_string(dynamic_data)))
if (res) {
while (line = res.fetch_assoc())
{
printf("%s\n", line.field2);
}
}
Because this is based on PHP 5.3, you could also use closures in the assign function, allowing things like:
Allowing you to create a function foo() that echo “bar” when called.
Future
A lot of new features are already being implemented, here is a non-exhaustive list:
- Allowing to pass all variables as reference, allowing the Javascript to modify the PHP value
- Allow to register “classes” prototypes, like:
$ctx->registerClass('DOMDocument');
Then you could just do “var a = DOMDocument();” in Javascript to instanciate the object.
- Autoregister a prototype on some types of resources. A Stream resource could then be assigned in JS, and you could call stream.write, stream.read, etc…
How-to test ?
The whole source code is available on a public read-only SVN: https://ookoo.org/svn/pecl-spidermonkey. Be careful, you need PHP 5.3.0 beta 1 to compile it ( it will not even compile on PHP 5.2 ). There may be some issues when dealing with many objects so don’t use it for production purpose.
cd pecl-spidermonkey
phpize
./configure
make
./test.sh
#1 by Ross Scrivener on February 19, 2009 - 11:26 pm
OK, this is pretty mental, why would you want to be able to do this?
#2 by Christophe Robin on February 20, 2009 - 12:30 am
One of the idea I had was providing a powerful language to our integration team that they would already know, while preventing them to access too much things. PHP was out of question and writing a small language was a pain. SpiderMonkey is well tested and provide Javascript, a language most website integrators know well. And I’m pretty sure other people will found a usage to this
#3 by Tuan Anh on February 20, 2009 - 11:04 am
It’s really cool. Thank you for greate information.
#4 by Luiz on April 16, 2009 - 8:55 pm
Hi Christophe,
Please, I have a mobile site (to be run from a mobile phone) and I would like to use your library to execute JavaScript code, based on some remote JavaScript library, server-sidely and return the result of this (a URL) to the celullar.
Is it possible with your library?
#5 by Paul Dorn on April 29, 2009 - 6:39 pm
This is great. I am building a new management tool to replace our aging flagship and needed a way to allow my users to write scriptlets for the server side from the user interface. This allows me to put their scripts in a VERY limited javascript context with a few API objects. This way they can safely alter the server behavior without leaking privileges.
THANK YOU!
#6 by Christophe Robin on April 30, 2009 - 3:25 pm
Be careful, most javascript URLs depends on objects made available by the browser, like DOM, so it may not work. But if the library is pretty independent and work only with basic stuff or the Math lib, it should be okay.
#7 by scotts on October 7, 2009 - 11:56 pm
This is incredible.. exactly what I was looking for to create a mini-language, and so easy to add the custom objects I want.
One thing I found after a couple hours of tinkering with it: I don’t see a way to register a function that’s a part of class. With other PHP functions that take callbacks as parameters, when you want to callback to an object’s method, you put an array in place of the name of the function with the object as the first parameter and the name of the method as the second, e.g.:
$js->registerFunction( array( $this, ‘print’ ), ‘print’ );
(this currently gives a segmentation fault, I guess because it’s expecting a string, not an array, and there’s no error checking present)
Another nice feature would be to get the details of a javascript error back to PHP (type of error, line number ,etc) so it can be returned by PHP in a sanitized way.
Just a couple of features to think about for a future release. But this is really great and already extremely useful. Thanks for the work!