SpiderMonkey: Extendiendo PHP

Hay muchas veces que nos encontramos con el problema de que queremos extender la funcionalidad de nuestro programa por extensiones, plugins, addons o como los queramos llamar. Estas extensiones tienen el problema de que pueden afectar al funcionamiento del resto del programa si lo ejecutamos directamente desde el core del programa que estemos haciendo, quedando el mismo en una situación inestable o nada funcional.

Para ello, desde hace mucho tiempo, se integran soluciones de lenguajes embebidos (como Lua, que ya vimos hace tiempo), que permiten ejecutarse en un entorno o contexto controlado, donde cualquier fallo no afecta directamente al núcleo del programa principal, solo al propio módulo en sí.

Leyendo un artículo bastante extenso de Zend Developer Zone, encontramos el cómo instalar la solución spidermonkey dentro de PHP para poder realizar código en JavaScript que pueda servir para extender el núcleo de la aplicación que hayamos realizado en PHP.

Instalación

La instalación es algo manual, ya que, al menos en Debian Lenny, no están disponibles las librerías necesarias para hacer que funcione. Los requisitos para poder echar a correr son:

La instalación de las librerías de Spider Monkey terminan siendo bastante manuales, por lo que las describiré a continuación paso a paso, tal y como viene en el artículo original:

tar -xzvf js-1.70.tar-gz
cd js/src
make -f Makefile.ref

mkdir -p /usr/local/include/js/ 
cp *.{h,tbl} /usr/local/include/js/ 
cd Linux_All_DBG.OBJ
cp *.h /usr/local/include/js/ 
cp js /usr/local/bin/ 
cp libjs.so /usr/local/lib/
ldconfig

IMPORTANTE: se debe de tener en ld.so.conf agregada la ruta de las librerías locales /usr/local/lib antes de hacer el último comando o no tendrá efecto (aunque no dé error).

Por último, instalamos desde PECL la librería:

pecl install spidermonkey-0.1.4

Con esto ya tendremos compilado e instalado el código, ya solo nos quedaría configurar la extensión spidermonkey.so dentro del php.ini para PHP pueda encontrar las funciones y objetos que le acabamos de instalar.

Probando, Probando… uno, dos, tres…

Pues nada, una vez tenemos la extensión configurada y funcionando, ya solo nos queda escribir un código para probar que todo funcione como tiene que funcionar. El código más simple que podemos probar es:

<?php
 
// crea un contexto JavaScript
$js = new JSContext();
 
// define variables PHP
$a = 10;
$b = 2;
 
// asigna las variables al contexto JavaScript
$js->assign('a', $a);
$js->assign('b', $b);
 
// define el código a ejecutar
$script = <<<END
  c = a + b;
END;
 
// evalua el script y muestra el resultado
echo "La suma de $a y $b es: " . $js->evaluateScript($script);

Lo cual nos resulta:

La suma de 10 y 2 es: 12

Complicándolo un poco más

Como todo en la vida, se puede hacer más aún. En principio, tenemos una forma de hacerle llegar al contexto de JavaScript las variables definidas en PHP, pero además, podemos:

Uso de funciones

Pasar una función de PHP para poder ejecutarla desde JavaScript:

<?php
// define la función en PHP
function getCircleArea($radius) {
  return pi() * $radius * $radius;
}
 
// crea el contexto JavaScript
$js = new JSContext();
 
// registra la función en el contexto JavaScript (como gca)
$js->registerFunction('getCircleArea', 'gca');
 
// define el código
$script = <<<END
  ret = 'El área del círculo con radio 5 es: ' + gca(5);
END;
 
// evalua el código y muestra el resultado
echo $js->evaluateScript($script);

Esto muestra:

El área del círculo con radio 5 es: 78.53981633974483

Uso de Clases

Supongamos que tenemos definida la clase Cow en PHP y queremos poder emplearla desde JavaScript. Lo que supone crear un nuevo objeto y poder emplearlo. La clase Cow la definimos como a continuación:

<?php
// definición de la clase
class Cow {
 
  private $_name;
  private $_milked;
 
  public function __construct() {
    $this->_milked = ;
  }
 
  // 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() . ' ha sido ordeñada hoy.';
    } else {
      return 'Hora de ordeñar a ' . $this->getName();
    }
 
  }
}

Con la definición de la clase, podríamos ejecutar el siguiente código:

<?php
// cargamos el fichero de la clase
include_once 'Cow.class.php';
 
// crea el contexto JavaScript
$js = new JSContext();
 
// registra la clase PHP en JavaScript
$js->registerClass('Cow');
 
// define el código
$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;
 
// evalua el código y muestra el resultado
echo $js->evaluateScript($script);

En este caso, el resultado sería, dependiendo de la hora del servidor:

Molly ha sido ordeñada hoy.

O bien:

Hora de ordeñar a Molly

Conclusiones

Considero que, cuando se realizan aplicaciones de servidor que requieran de un nivel de personalización alto, es muy útil poder darle la posibilidad al usuario, o programador en caso de que sea un framework, de extender la funcionalidad propia del entorno, sin afectar con ello al núcleo de la aplicación o servidor.

Spidermonkey, en este aspecto, tiene la ventaja de que cumple con creces las necesidades que puedan surgir en este aspecto, además de ser bastante simple de manejar y, en sí, el lenguaje JavaScript no resulta desconocido para los programadores de PHP, ya que la mayoría son en sí programadores web que, por una causa u otra, han necesitado en algún momento del uso del mismo.