PHP 用デバッガ

PHP: Advanced PHP Debugger(PHP) (php.net)

(PHP 公式ドキュメントより引用)
APD は進化した PHP デバッガです。PHP コードのプロファイリングや デバッグの機能を提供すること、また完全なスタックトレースを出力する 機能を提供することを目的として作成されています。

 
インストール手順:

$ pecl install apd
 
$ cat >> /path/to/php.ini ← エディタ等で以下の内容を php.ini に追加
 
zend_extension = /path/to/apd.so ← APD モジュールのフルパス(zend_extention 指定の場合は相対パスは不可)
apd.dumpdir = /tmp/apd_log ← APD での解析結果の出力先フォルダ
apd.statement_tracing = 0 ← 行単位トレースをする場合 1 にする。これを有効にすると出力が冗長になります。

Windows 用のコンパイル済み PECL モジュールは http://snaps.php.net/win32/ からダウンロードできます(ファイル名 pecl からはじまるもの)。
 
 
利用方法:
デバッグをしたいスクリプトの一番始めに

apd_set_pprof_trace();

を呼ぶと PHP デバッガが利用できます。
 
PHP デバッガによる結果は php.ini の apd.dumpdir で指定したディレクトリに

pprof.<プロセスID>.<通し番号>

のような形式で保存されます。
 
そのまま読めなくはないですが、読みやすい形に整形するために pprofp コマンドを利用することができます(APD に付属しています)。
 
— かかった処理時間の多い関数順に並びかえて出力する

pprofp -R [ファイル名]

— コールツリー(関数の実行順序)を調べる

pprofp -t [ファイル名]

— (上の二つ)+(ビルトイン関数は含めない)+(コールツリーの横に経過時間を表示)

pprofp -Rtci [ファイル名]

 
その他のオプションについては「APD の pprofp 引数一覧(pprofp -h)」を参照してください。
  
Windows 用の pprofp についてはAPD のソースに含まれるものが使えるのですが、そのままの形では使いにくいため、実行用のバッチファイルを作成したものを置いておきます。
 
Windows 対応 pprofp をダウンロード
(APD 1.0.1 ソース付属のものから作成)
 
– pprofp
– pprofp.bat
– pprof2calltree
– pprof2calltree.bat
の 4 ファイルが入っています。
この4ファイルをそのまま php.exe と同じ場所など適当にパスが通る場所に置いてご利用ください。
 
参考:
APD(公式 PHP マニュアル)
cles::blog

PHP で OpenSSL

PHP で OpenSSL 関数を使ってみたので、定番のハローワールドを置いておきます。


OpenSSL 関数で暗号化、復号化を行うには、
暗号化、復号化に必要な鍵のペアを PEM 形式で生成する必要があります。
1. パスワード付き秘密鍵 private_password.pem を生成

$ openssl genrsa private_password.pem -des3 1024

2. (1)からパスワードのない秘密鍵 private.pem を生成

$ openssl rsa -in private_password.pem -out private.pem

3. (1)を削除

$ rm private_password.pem

4. (2)から公開鍵 public.pem を生成

$ openssl rsa -in private.pem -pubout -out public.pem

 


openssl_public_encrypt(), openssl_private
…公開鍵で暗号化し、秘密鍵で復号化します。
つまり、誰からでも暗号化して送れるが、内容を読めるのは秘密鍵を持っている人だけになるため、通信経路での傍受が不可能になり、内容が外部に洩れる心配がないので機密性が高くなります。
ただし誰でも暗号化できるので全く別の内容へのかいざんは可能であり、必ずしも信頼性はありません。
 

// 暗号化に使う公開鍵
$public_key = file_get_contents(“public.pem”);
 
// 暗号化する文字列
$message = “Hello, world!!”;
 
// 暗号化データの結果格納先
$encrypted = “”;
 
// 暗号化実行
if(!openssl_public_encrypt($message, $encrypted, $public_key)){
    echo “暗号化に失敗\n”;
    die;
}
 
// 暗号化結果を表示(そのままでは表示できないのでBASE64エンコードする)
echo “暗号化データ: \n “.base64_encode($encrypted) . “\n”;
 
$message = null;
// ———————
// 復号化
// ———————
// 暗号化に使った公開鍵に対応する秘密鍵
$private_key = file_get_contents(“private.pem”);
 
// 復号化結果格納先
$result = “”;
 
// 復号化実行
if(!openssl_private_decrypt($encrypted, $result, $private_key)){
    echo “復号化に失敗\n”;
    die;
}
 
// 結果出力
echo “復号化結果: \n ” . $result . “\n”
     “BASE64:\n” . base64_encode($result).”\n”; // 比較用

結果:

暗号化データ:
FmPaJ9jOmjl/mK249NqUPzYjPUHsmFkSuNyp0SMlgW1c9RRzmKXBjcRhXH/5fwTxItAcE8/
AlZkjYo27XvGR7a3hdXxgaXbk4VORdRRk8LJXKB+xlVzF48OlHJ5lKUrXUdfr2BFtx53/IH
431mBlYWDhf+KyW608eKAA0T34qRY=
復号化結果:
Hello, world!!
BASE64:
SGVsbG8sIHdvcmxkZmc=

 


openssl_private_encrypt(), openssl_public_decrypt() の例:
…秘密鍵で暗号化し、公開鍵で復号化します。
つまり、鍵の所有者しか暗号化できないので中身のかいざんも、経路での差し替えも不可能になり、内容についての信頼性が高くなります。
ただし誰でも読めるため、内容の機密性はありません。
 
秘密鍵、公開鍵の使い方を逆にする以外は上の例と全く同じです。

// 暗号化に使う秘密鍵
$private_key = file_get_contents(“private.pem”);
 
// 暗号化する文字列
$message = “Hello, world!!”;
 
// 暗号化データの結果格納先
$encrypted = “”;
 
// 暗号化実行
if(!openssl_private_encrypt($message, $encrypted, $private_key)){
    echo “暗号化に失敗\n”;
    die;
}
 
// 暗号化結果を表示(そのままでは表示できないのでBASE64エンコードする)
echo “暗号化データ: \n “.base64_encode($encrypted) . “\n”;
 
$message = null;
// ———————
// 復号化
// ———————
// 暗号化に使った秘密鍵から作られた公開鍵
$public_key = file_get_contents(“public.pem”);
 
// 復号化結果格納先
$result = “”;
 
// 復号化実行
if(!openssl_public_decrypt($encrypted, $result, $public_key)){
    echo “復号化に失敗\n”;
    die;
}
 
// 結果出力
echo “復号化結果: \n ” . $result . “\n”
     “BASE64:\n” . base64_encode($result).”\n”; // 比較用

結果:

暗号化データ:
CII4EW6LQBq0faVJxshUfk9VCdFsE66DnQPVdsLbvfq7tNCMzAIZbZA3GBxHu4cG2TqvOnn
xN9C9S9IgBIbulfj4c90IkrYtHQGt3RHAJBYiAgfmzCKbMr2cPJhZwkMUkjczXeaOD1htFJ
TL9e2RKsrWBVFIaiG1JgFk2Jyd928=
復号化結果:
Hello, world!!
BASE64:
SGVsbG8sIHdvcmxkZmc=


openssl_open(), openssl_seal() の例:
…同報メールのように、別々の公開鍵を持つ複数の相手に対して同じデータを暗号化して送る場合に使います
 
– 送信側(送信先1の公開鍵 user1_public.pem と送信先2の公開鍵 user2_public.pem を持っているとする. 自分の鍵は必要としない)

// 送り先の公開鍵一覧。
$pubkeys = array(
        file_get_contents(“user1_public.pem”),
        file_get_contents(“user2_public.pem”),
   );
 
// 送る内容
$real_message = “Hello, world!!”;
 
// 暗号化データの結果格納先
$encrypted = “”;
// 復号化に必要なエンベロープキーの格納先。$pubkeys の数だけ格納される。
$ekeys = array();
 
if(!openssl_seal($real_message, $encrypted, $ekeys, $pubkeys)){
    echo “暗号化に失敗しました\n”;
    die;
}
 
// 出力. エンベロープキーと暗号化データについてはバイトコードなので、表示のために BASE64 エンコードしています。
for($i = 0; $i < count($pubkeys); $i++){
    echo “送り先番号: $i\n” .
         “使った公開鍵: \n” .
         $pubkeys[$i] .”\n” .
         “対応するエンベロープキー: \n” .
         base64_encode($ekeys[$i]) .”\n” .
         “暗号化されたデータ: \n” .
         base64_decode($encrypted);
}

 
受信側(送信側からエンベロープキー $ekey と暗号化データ $encrypted を受け取っているとする):

// 送信側が暗号化に使った公開鍵(user1_public.pem)に対応する秘密鍵
$private_key = file_get_contents(“user1_private.pem”);
 
// 結果格納先。復号化した文字列が入る
$message = “”;
 
if(!openssl_open($encrypted, $message, $ekey, $private_key)){
    echo “復号化に失敗しました\n”;
    die;
}
 
// 結果表示
echo $message . “\n”;

 
送信側結果は次のようになります。


送り先番号: 0
使った公開鍵:


-BEGIN PUBLIC KEY—–
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzZFcKoXbuWzGvlv++7Y05/jBh
nYC+PM67v3H2I5pYV8tUz/6GqsG5afFgLJYoWPEd3KYXHb46B0GxEOydOTuTLEcv
+gVr6v4khgOgjxOcM1hlv+EvAThm1lIY1Y5lbTv+MEUyrGm7SDk8iUaHF6OBcQp3
NzkO6j3+YrsC8znXPwIDAQAB


-END PUBLIC KEY—–
対応するエンベロープキー:
lu4ON1xW2hrrS1jXfav6mjWD32+Iuz1Jk20vhDRmSsfEp3vXuij7Z0R/ClhpgE9MxDJADiU
LS/CCUPuiZ3wUDErccfUFGGgcBKFsoXOgcX/vSfhi8yCYu3BOD+h5fOMmg/ZVe6NMjCVq50
D8yce5gPoypg2LHijse+0R1bPil8o=
暗号化されたデータ:
0qf1eWjjaHkiqRFKijam


送り先番号: 1
使った公開鍵:


-BEGIN PUBLIC KEY—–
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzZFcKoXbuWzGvlv++7Y05/jBh
nYC+PM67v3H2I5pYV8tUz/6GqsG5afFgLJYoWPEd3KYXHb46B0GxEOydOTuTLEcv
+gVr6v4khgOgjxOcM1hlv+EvAThm1lIY1Y5lbTv+MEUyrGm7SDk8iUaHF6OBcQp3
NzkO6j3+YrsC8znXPwIDAQAB


-END PUBLIC KEY—–
対応するエンベロープキー:
Hpv4sE4M4RGnbEAPMqFKxOOPDJG0hpWBoIXCe8xUaIzx/dskUQ/a+T4jYyw+RRGh11wlzZt
CJOjzkP3pbafc0ymElAzS8RFAne7bVuCJh44UF55thAUo0nZVo1/2Wz9HuTDL23AYwc+jyh
U/k24bpGh81VJl5I7IkItoDeiS70Y=
暗号化されたデータ:
0qf1eWjjaHkiqRFKijam

受信側結果:

Hello, world!!

 
参考:
OpenSSL 関数(php.net)
 PHP マニュアルの OpenSSL 関数についての項目。
OpenSSL 公式サイトの日本語訳サイト
更新は不定期のようですが、openssl のマニュアルが参考になります。
公式はこちらです。

PHP の OpenSSL 関数で DSA(DSS) を使うとエラー

PHP で OpenSSL 関数を使って DSA 公開鍵での暗号化を行おうとしたところ

Warning: openssl_public_encrypt(): key type not supported in this PHP build!

のようにエラーがでてしまった。
 
PHP のソースを追ってみたところ、どうやら PHP では現在のところ DSA の秘密鍵/公開鍵についてはサポートされていないようです。
 
php-5.2.1/ext/openssl/openssl.c:

PHP_FUNCTION(openssl_public_encrypt)
{
// ** 省略 **
        switch (pkey->type) {
                case EVP_PKEY_RSA:
                case EVP_PKEY_RSA2:
                        successful = (RSA_public_encrypt(data_len,
                                                (unsigned char *)data,
                                                cryptedbuf,
                                                pkey->pkey.rsa,
                                                padding) == cryptedlen);
                        break;
                default:
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, “key type no
t supported in this PHP build!”);

        }
// ** 以下略 **

のように、
openssl_open(), openssl_seal(), openssl_private_decrypt(), openssl_private_encrypt(), openssl_public_encrypt(), openssl_public_decrypt()
など、暗号化/復号化を行う関数では RSA か RSA2 の時の処理しか実装されていないようです。
 
参考:
OpenSSL 関数(php.net)

PHP の autoload を使ってみる

PHP5 からは、オブジェクト指向言語らしくクラスのオートローディングが可能になりました。
具体的には、明示的に require_once “foo.php” としなくても、

function __autoload($class){
    include_once $class . “.php”;
}

としておけば、

$foo = new foo;

とした時に include_path から自動的に foo.php をインクルードしてくれる,というものです。
 
ただ、class_exists()はデフォルトで__autoload()を呼び出すらしいので __autoload() の実装は以下のような感じにするのがよさそうです。

function __autoload($class){
   // 検索する拡張子一覧
   $exts = array(“php”, “inc”, “class.inc”, “class.php”);
   // 検索するディレクトリ一覧( “./” や “../” で始めると,その指定については include_path を見なくなるので注意 | PHP マニュアル include() 参照)
   $dirs = array(“”, “include/”, “classes/”);
   // include_path を検索し、存在すればそのファイルをインクルードする。
   // 存在しなければ何もしない。
   foreach($dirs as $dir){
     foreach($exts as $ext){
       $file = $dir . $class . “.” . $ext;
       // echo “Debug: trying $file \n”;
       if($fp = @fopen($file, “r”, 1)){
         @fclose($fp);
         require_once $file;
         return;
       }
       // PEAR Style
       $file = $dir . str_replace(“_”, “/”, $class) . “.” . $ext;
       // echo “Debug: trying $file \n”;
       if($fp = @fopen($file, “r”, 1)){
         @fclose($fp);
         require_once $file;
         return;
       }
     }
   }
}

XCache を使ってみた

前の記事に書いた、XCache を使ってみました。
 
例1:

if(!xcache_isset(“time”)){
    xcache_set(“time”, “現在の時刻: “.date(“Y-m-d H:i:s”), 30); // 30 秒間キャッシュ. 数値を省略すると、おそらく php.ini の設定依存
}
 
echo xcache_get(“time”);

結果例:

現在の時刻: 2007-03-14 11:30:56

30秒未満でアクセスすると、キャッシュを参照するため常に同じ結果を返します。
30秒以上経過後にアクセスすると、新たに現在時刻をキャッシュ、表示します。
 
例2:

echo “count: ” . xcache_inc(“count”);

結果:

count: 1

数値は 1, 2, 3… と、アクセスする度に増加していきます。
 
手軽に扱え、管理ページもあるということでなかなか便利そうです。
 
ちなみに、php.ini の設定は大体以下のようにしました(xcache.admin.user, xcahe.admin.pass は要変更)。

[xcache-common]
zend_extension = /usr/local/lib/php/20060613/xcache.so
; extension = xcache.so
; extension = php_xcache.dll

[xcache.admin]
xcache.admin.user = “admin”
xcache.admin.pass = “fded9601747e160c1dac9b7eb0ce4af9” ; md5($your_password)

[xcache]
xcache.shm_scheme = “mmap”
xcache.size = 4M
; set to cpu count (cat /proc/cpuinfo |grep -c processor)
xcache.count = 1
xcache.slots = 8K
xcache.ttl = 600
xcache.var_ttl = 600
xcache.var_maxttl = 3600
xcache.var_size = 4M
; interval of gc scanning expired items, 0=no scan, other values is in seconds
xcache.gc_interval = 300
 
xcache.test = Off
xcache.readonly_protection = Off
xcache.mmap_path = “/dev/zero”
;xcache.mmap_path = “/tmp/xcache”
 
xcache.coredump_directory = “”
 
; per request settings
xcache.cacher = On
xcache.stat = On
xcache.optimizer = On
  
[xcache.coverager]
xcache.coverager = Off
 
cache.coveragedump_directory = “”
;xcache.coveragedump_directory = “/tmp/pcov/”

 
参考:
XCache API

PHP5 とキャッシュまわり

PHP5 でメモリキャッシュをしようと思うと、選択肢は APC, eAccelerator, XCache のいずれかになるようです。
 
ちなみに、BSD の ports ではそれぞれ www/pecl-APC, www/eaccelerator, www/xcache が該当パッケージです。
 
参考:
Do You PHP? – eAccelerator を導入してみる, XCache を導入してみる
ZAPAブロ〜グ2.0 – XCache のインストールとphp用アクセラレータ比較
XCache API

JSON で実現する快適な Ajax

AJAX で JavaScript でサーバからの出力を解釈する際に、最も用意にパースできる出力形式として、JSON(JavaScript Object Notation)形式があります。出力が XML ではなくなるため、厳密には Ajax(Asynchronous JavaScript + XML)ではなくなりますが、JSON を利用することで、JavaScript でのパース作業が格段に容易になる上、クロスドメイン呼び出しが可能になるというメリットがあります。
 
基本的な文法についてはこちらが参考になりますが、大体このようになります。

var person = {
      name: “John”, // name プロパティ
      age: 30,
      family: [ // 配列
        {name: “Jane”, age: 24}, // 配列内オブジェクト
        {name: “Richard”, age: 1}
      ]
   };
// 参照例
alert(person.name); // John が出力される
alert(person.family[0].name); // Jane が出力される

 
JavaScript で動的利用する場合、
JavaScript – JSONでデータを受信する方法2種類(s.h.log)
dojo toolkit の ScriptSrcIO(snippets from shinichitomita’s journal)
が参考になります。
これを PHP で生成する場合、PHP 5.2.0 以降デフォルトで組み込まれるようになったJSON関数(PECL:php-json)を利用します。日本語使用時の注意として、json_encode に渡す文字列の文字コードは UTF-8 にする必要があります。

  echo json_encode(array(“name”=>”John”, “age”=>30));

また、JSONP (JSON with Padding) という手法により、サーバ側からクライアントに対し非同期コールバック関数を実行させることができます。
リクエストURL:

http://foo.example.com/api/person?name=John&callback=myPersonHandler

リクエスト結果:

myPersonHandler({name: “John”, age:30});

埋め込む HTML:

<script type=”text/javascript” src=”http://foo.example.com/api/person?name=John&callback=myPersonHandler” charset=”UTF-8″><!–
    function myPersonHandler(result){
        alert(result.name + ” is ” + result.age + “years old”);
    }
//–></script>

こうすると、サーバからの結果の出力完了後に自動的に myPersonHandler が呼ばれるようになります。なお、myPersonHandler という関数名は、リクエストURL の callback パラメータを元にしているため、クライアント側で任意の関数に置き換えることが可能です。
 
参考:
PHP で JSON(Do You PHP?)
Collection & Copy – JSON 入門
JavaScript – dojo の ScriptSrcIO で動的クロスドメイン JSON 読み込み
JSONP とコールバック関数(戯れ言++ – 入門 JSON 3)

makexmldocomomap の出力を PEAR の Net_UserAgent_Mobile で使えるようにするパッチ

単純に makexmldocomomap を実行すると <terminal … modeil=”SH902I” … > のように出力されたのですが、PEAR では <SH902I … >という形式を期待していて、そのまま DoCoMoMap.xml に適用すると動作しなくなったため次のように書き換えました。(Net_UserAgent_Mobile 0.27.0 で確認 cpan のほうは失念)
 
$ diff /usr/local/bin/makexmldocomomap{.bak,}

 21,22c21,22
 < push @{$map{terminal}}, {
 < model => uc($model),
 —
 > push @{$map{uc($model)}}, {
 > width => $width,

また、出力時にiモード HTML シミュレータの情報が書かれていないため、エミュレータでも利用するためには

  <ISIM0301 color=”1″ depth=”262144″ height=”320″ width=”240″ />
  <ISIM70 color=”1″ depth=”4096″ height=”182″ width=”163″ />

としてエミュレータの端末情報を指定する必要があります。
 
メモ:
SO902iWP+ 端末が

<SO902IWP+ color=”1″ depth=”262144″ height=”256″ width=”240″ />

となり、XML 的にエラーで次要素以降が読まれなくなるのは何か対処法あるんでしょうか。。
# 現在一番下に移すことで回避中。もちろんこれでは SO902i WP+ 自体は対応されないでしょうね(いろいろ未確認)。