2010年2月 4日 (木)

SpiderMonkeyを使ってPHPでサーバーサイドJavaScript

はじめまして。2009年に新卒で入社しました天野祐介です。amachang を期待された方はゴメンナサイ!

先日 SpiderMonkey を利用して PHP から JavaScript を実行する方法を調べる機会がありましたので、ご紹介します。

SpiderMonkey とは

SpiderMonkey は  C で実装された Mozilla の JavaScript エンジンです。 これを PHP から実行する拡張を利用すると、 PHP コード内で JavaScript が実行できます。

SpiderMonkey extension のインストール

こちらhttp://devzone.zend.com/article/4704に記載されている方法で CentOS にインストールしてみました。
PHP 5.3.0 以上が必要です。

$ wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.7.0.tar.gz
$ 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/
$ /sbin/ldconfig
$ cd ~
$ svn export https://ookoo.org/svn/pecl-spidermonkey/ ./sm
$ cd sm
$ phpize
$ ./configure
$ make
$ make install
Installing shared extensions:     /usr/local/lib/php/extensions/no-debug-non-zts-20090626/

インストール先を確認して、 php.ini に以下の行を追加します。

extension = "/usr/local/lib/php/extensions/no-debug-non-zts-20090626/spidermonkey.so"

それでは動かしてみましょう!

Hello, World

<?php
$js = new JSContext();

// jsでPHPの関数を使えるようにする
$js->registerFunction('var_dump');

// jsコードの定義
$script = <<< END
    var hello = 'Hello, World!!';
    var_dump(hello);
END;

// jsコードを評価
$js->evaluateScript($script);

実行結果

Hello, World!!

JavaScript から var_dump() を実行してコンソールに出力しています。

JSでPHPのクラス, 関数を使う

var_dump() 以外にも、 JavaScript から PHP のクラスや関数を使ってみます。

myclass.php

<?php
class hoge
{
    public $foo = array('abc', 'def');
    public static $bar = array('dog' => 'wanwan',
                               'cat' => 'nya-!');

    function pow($x)
    {
        return $x * $x;
    }

    static function hello($name)
    {
        return 'Hello, ' . $name . '!!';
    }
}

call.php

<?php
require_once 'myclass.php';

$hoge = new hoge();
$js = new JSContext();

// PHPの関数, クラスを使えるようにする
$js->registerFunction('var_dump');
$js->registerClass('hoge', 'hoge');

// PHPの変数を使えるようにする
$js->assign('hogedog', hoge::$bar['dog']);

// define script
$script = <<< END
    var hoge = new hoge();
    var_dump(hogedog, hoge.foo, hoge.pow(5));
    var_dump(hoge.hello('amano'));
END;

$js->evaluateScript($script);

実行結果

# php -f call.php
string(6) "wanwan"
object(stdClass)#4 (2) {
  ["0"]=>
  string(3) "abc"
  ["1"]=>
  string(3) "def"
}
int(25)
string(14) "Hello, amano!!"

配列もちゃんと出力できました。

JSで定義したオブジェクト, 関数を使う

次は、 JavaScript のオブジェクトを PHP から触ってみましょう。

jsobj.php

<?php
$js = new JSContext();

$js->registerFunction('var_dump');

// タイムゾーンを設定
date_default_timezone_set('Asia/Tokyo');
// 現在の時(24時間)を取得
$hour = date('H');

// assign
$js->assign('time', $hour);

// define script
$script1 = <<< END
    var menu = {morning:'ばなな', lunch:'sandwich', dinner:'steak'};
    var_dump(menu);
END;

$script2 = <<< END
    var menu = {morning:'ばなな', lunch:'sandwich', dinner:'steak'};
    var gohan = {
        okazu : function(time) {
            if (time > 5 && time < 10) {
                return menu['morning'];
            } else if (time > 10 && time < 14) {
                return menu['lunch'];
            } else {
                return menu['dinner'];
            }
        }
    };

    gohan.okazu(time);
END;

$js->evaluateScript($script1);
printf("今日のおかずは %s よ!\n", $js->evaluateScript($script2));

実行結果

# php -f jsobj.php
object(stdClass)#2 (3) {
  ["morning"]=>
  string(9) "ばなな"
  ["lunch"]=>
  string(8) "sandwich"
  ["dinner"]=>
  string(5) "steak"
}
今日のおかずは steak よ!

evaluateScript() メソッドは最後に評価した値を返します。

外部 js ファイルを使う

以下の js ファイルを読み込んで実行してみます。

function.js

var F = {
    getYearMonth : function () {
        var date = new Date();
        return date.getFullYear() + '/' + (date.getMonth().valueOf() + 1);
    }
}

main.php

<?php
$js = new JSContext();

$library = file_get_contents('function.js');

$main = <<< END
    F.getYearMonth();
END;

$js->evaluateScript($library);
var_dump( $js->evaluateScript($main) );

実行結果

string(6) "2010/2"

思いのほか簡単に動きました!ユーティリティ関数を外部 js の中に詰め込んでおけば、 PHP と JavaScript で共有できそうですね。

PHP に値を渡した時の挙動

JavaScript から PHP にオブジェクトを渡した時の型の変化をもう少し詳しく見てみましょう。

Array

<?php
$js = new JSContext();
$js->registerFunction('var_dump');

$script = <<< END    var array_func = function() {
        var array = new Array(5);
        array[0] = 1;
        array[4] = 5;
        var_dump(array.length);
        return array;
    }
    array_func();
END;

$result = get_object_vars($js->evaluateScript($script));
var_dump( $result );
var_dump( count($result) );

実行結果

int(5)
array(2) {
  [0]=>
  int(1)
  [4]=>
  int(5)
}
int(2)

返り値の array は StdClass に包まれていたので、 get_object_vars() で取り出しています。

JavaScript は array[1]〜array[3] に "undefined" が入り長さは 5 ですが、PHP では長さ 2 となります。
PHP で存在しないインデックス(array[1]〜array[3])にアクセスすると、 NULL が返ります。

Function

<?php
$js = new JSContext();

$script = <<< END
    var func = function() {
        var x = function(n){return n;}
        return x;
    }
    func();
END;

$result = $js->evaluateScript($script);
var_dump($result);

実行結果

object(stdClass)#2 (1) {
  ["prototype"]=>
  object(stdClass)#3 (0) {
  }
}

Function は空のオブジェクトになりました。

実数

Math オブジェクトや Number オブジェクトに含まれる実数値を PHP に渡して出力してみました。
コードは割愛します。

Math.PI

js  -> 3.141592653589793
php -> 3.1415926535898

Number.MAX_VALUE

js  -> 1.7976931348623157e+308
php -> 1.7976931348623e+308

Number.MIN_VALUE

js  -> 5e-324
php -> 4.9406564584125e-324

浮動小数点数は異なる精度で出力されてしまいました。 PHP に実数を渡す場合は、 BC Math 関数http://www.php.net/manual/ja/ref.bc.phpなどを使う必要がありそうです。
ちなみに、JavaScriptの未定義値 "undefined" をPHPに渡したら NULL になりました。

連想配列

<?php
$js = new JSContext();
$js->registerFunction('var_dump');

$script = <<< END
    var func = function() {
    var hash = {num:1, str:'aiueo', array:[1,2], func:function(x){return x}};
    return hash;
    }
    func();
END;

$result = $js->evaluateScript($script);
var_dump( get_object_vars($result) );

実行結果

array(4) {
  ["num"]=>
  int(1)
  ["str"]=>
  string(5) "aiueo"
  ["array"]=>
  object(stdClass)#3 (2) {
    ["0"]=>
    int(1)
    ["1"]=>
    int(2)
  }
  ["func"]=>
  object(stdClass)#4 (1) {
    ["prototype"]=>
    object(stdClass)#5 (0) {
    }
  }
}

PHP でも連想配列として扱うことができました。 関数オブジェクトはやはり空のオブジェクトになってしまいますが。。。

PHP に値を渡したときの挙動まとめ

  • JavaScriptが 数値, 文字列, 論理値, null, undefined, NaN 以外の値を返す場合、PHPには StdClass オブジェクトとして渡される。
  • 配列や連想配列は get_object_vars() で取り出せる
  • 関数オブジェクトは PHP に渡せない

エラー処理

JavaScript コード内でのエラーの扱いを見てみます。

<?php
$js = new JSContext();
$js->registerFunction('var_dump');
$script = <<< END
    try {
        hoge();  // 未定義関数
    }
    catch(e) {
        var_dump(e);
    }
END;

$js->evaluateScript($script);

実行結果

object(stdClass)#2 (4) {
  ["message"]=>
  string(19) "hoge is not defined"
  ["fileName"]=>
  string(0) ""
  ["lineNumber"]=>
  int(1)
  ["stack"]=>
  string(4) "@:1
"
}
セグメンテーション違反です

var_dump() との折り合いが悪いのかセグメンテーション違反が発生してしまいましたが、エラーオブジェクトのプロパティは正しく参照できます。
e.linenumber は 0 から始まり、エラーの発生した行が格納されています。

e.linenumber でエラー行が分かるのは便利ですが、 ひとつの PHP コード内で 複数回 evalueateScript() を実行したときに、どの evaluateScript() での行番号か特定できないという問題があります。
この問題は後述のパッチを適用することで回避可能です。パッチを適用すると、 evaluateScript() の第2引数に e.fileName に出力される名前を設定できるようになります。

まとめ

いかがでしたでしょうか。PHP と JavaScript でお互いの関数やオブジェクトを繰り返し実行すると結構バギーな動きをするので安全とは言い難いですが、 共通の処理を JavaScript のコードにまとめられたり JavaScript からサーバーサイドのオブジェクトが操作できるのは魅力的だと思います。JavaScript の活躍する場所はクライアントサイドだけじゃないということで、何かのご参考になれば幸いです。

パッチ情報

こちらでラボの星野さんが作成されたパッチを公開しています。
http://developer.cybozu.co.jp/oss/2010/01/spidermonkey-ph.html
windows build のサポート,evaluateScript() の引数の追加,報告されたバグの改修の3点です。

参考にさせていただいたページ

2010年1月13日 (水)

新年明けましておめでとうございます

新年明けましておめでとうございます。
昨年は百年に一度の大不況、政権交代、円高、デフレと企業はもとより個人にとってもつらい年となりました。

しかし、私たちはそんな大不況を吹き飛ばしたい思いから、家族、友人、ビジネスのコミュニケーションを活性化させる無料グループウェア、「サイボウズ Liveを公開しました。現在、招待制で運用を開始しており、一般登録制に向けて準備中です。
こちらhttp://blog.cybozulive.com/のブログにて情報を発信しております。

今年はOffice、ガルーン2のバージョンアップだけでなく、マイクロソフト社との業務提携によるSharepoint向けのグループウェアを発売致します。こちらは従来のグループウェアの新バージョンではなく、全く新しい製品ラインナップになります。
こちらhttp://group.cybozu.jp/news/09092801.htmlがプレスリリースになります。

また、サイボウズとサイボウズラボとのコラボレーションによる次世代グループウェアR&Dも昨年より開始しています。ご期待ください。

その他にもワクワクする製品を今年いくつかリリース致します。
本年もサイボウズとサイボウズラボを宜しくお願いします。

2009年12月 4日 (金)

Cybozu Toolbar

サイボウズ開発部でアルバイトをしている郷原浩之です。

サイボウズ開発部では3人のアルバイトと一緒に、サイボウズOfficeへの直感的なアクセスを可能にするためのブラウザツールバーを作っております。

http://kantan.cybozu.co.jp/office7/option/client/toolbar/

現在4ヶ月目になりますが、開始当初に比べてより使いやすい機能が幾つか追加できたかと思います。ドキュメントなどが少なく、またCOMやATLの知識が乏しいところからのスタートだったので、当初はかなり苦労しました。

サイボウズではサイボウズガルーン(グループウェアソフト)を自社で稼動させていますが、そこに疑問点を書いておくと、上級者の方がコメントをくださり、とても心強いです。現在は、サイボウズツールバーのFireFoxへの移植を行っておりますが、javascript等でのアドバイスについて、サイボウズガルーンに書き込んでいただき助かっております。

2009年10月 5日 (月)

第2回技術発表会を行ないました

Dscf6235

こんにちは、サイボウズ篠原です。
夏も終わりそろそろ肌寒くなってきましたね。
9月の頭の話になりますが、社内で技術発表会を開催しました。

この催しは開発部の技術交流を目的としており、
各拠点とグループ会社であるサイボウズラボとサイボウズ総研に
参加いただいています。

この技術発表会、今回で2度目となり、
開発、運用のノウハウ、そして新プロジェクト、と
目白押しの内容。
メンバーにとってよい刺激になっています。

残念ながら対外的に出せない情報が多いですが
タイトルだけちょっと公開しようと思います。


Dscf6229_2

本社開発部
Gaia (応用発展系アプリ)
Mercury (モバイル系調査)
グループウェアネットサービス

Dscf6231

サイボウズラボ
AzDz by Windows Azure
Fast IMAP Proxy
Key-Value Storage Mio
A Clever Way to Scale-out a Web Application
Application Development on SharePoint
Lab's VM Environment
Bamboo Project Report


Dscf6234

サイボウズ総研
Garoon2 EC2
Garoon2 IP Phone Connection
Garoon2 Exchange Connection


Dscf6226

情報システム部
内部統制
オフショア
ストレージ研究
バックアップシステム
顧客情報システム
Silverlight

松山開発部の活動
上海開発部の紹介

次回の技術発表会は3月を予定しています。

もしかすると対外的に出せる情報も増えるかもしれませんし
新しいプロジェクトがはじまっているかもしれません。
お楽しみに。

2009年9月25日 (金)

ものづくり日本大賞受賞

サイボウズ松山の矢野です。

いまさら感の否めないご報告となりますが、
去る 平成21年7月某日、件の賞を頂戴いたしました。

第18回の高専プロコンへ出展した作品が評価されたとのことで、
当時の開発メンバーが一同に集い、あたかも学生時代に戻ったかのようでした。

実感の湧かぬまま表彰式を迎え、終えたので、未だに夢か幻のような気分であります。
首相官邸にて賞を賜ったのですが、瀟洒な雰囲気の中に身を置くにあたり、場違いな印象を受けるばかりでした。
官邸の天井は高かったです。

第92代首相の力強い握手と励ましのお言葉を忘れません。
この熱い想いを胸に、製品開発に全力を注いでゆきたいと思います。

関連URL:
[第3回ものづくり日本大賞]
[Cydn - 地方でプログラマは育つのか]