SSブログ

PHP の array_reduce [PHP]

PHP に array_reduce という関数が用意されていたので喜び勇んで使ってみたんだけど実に興味深い仕様だったのでここに記しておきたい。

array_reduce は関数型言語でいう reduce か fold に相当する関数だ。例を挙げると、

<?php
  function conc($acc, $x) { return $acc . $x; }
  echo array_reduce(array("a", "b", "c"), "conc");
?>

この結果は abc となる。
別にコールバック関数名を文字列で与えるのが気持ち悪いとかいう話ではない。

次に、array_reduce は第3引数に初期値を与えることができて、それを与えるとコールバック関数が最初に呼ばれるときの第1引数になる(fold になる)。

ではこれはどうなるだろうか。

<?php
  function conc($acc, $x) { return $acc . $x; }
  echo array_reduce(array("b", "c"), "conc", "a");
?>

これは 0bc になる。

・・・ 0bc?

実は array_reduce の関数プロトタイプをよく見ると理由が分かる。

mixed array_reduce ( array $input, callback $function [, int $initial] )

第3引数の初期値は何故か型が整数に限られるのだ。だから上記の "a" は暗黙に整数に変換されて 0 となっているようだ。一体何を思ってこんな仕様にしたのだろうか。

この問題は以下のような workaround で回避できる。

<?php
  function conc($acc, $x) { return ($acc === 0 ? "a" : $acc) . $x; }
  echo array_reduce(array("b", "c"), "conc", 0);
?>

結論: PHP では素直に for/foreach を使って命令的に書きましょう。


CakePHP 始めた [PHP]

ちょっと Web アプリで作りたいものを思いついて CakePHP [1] (Ruby on Rails の考えかたに影響を受けたらしい PHP 用のフレームワーク)を始めてみたんだけどかなり楽しい。今まで漠然とフレームワークというものには自分は馴染めないんじゃないかと思ってたんだけどそうでもなかった。大体以下のような要素が好感に結びついていると思われる。

1. アップロードするだけですぐに使えた

CakePHP はパッケージを解凍してその辺の PHP と DB をサポートしているレンタルサーバにアップロードすればすぐ使える(DB 設定のみ必要)。これはフレームワークというのから連想されるのとはまったく違う手軽さだったので大変感動した。これに匹敵する手軽さのフレームワークって他にあるのだろうか。

2. 目的をもって使っている

フレームワークというのはそれ自体で遊ぶことを考えると遊びどころないものなのかもしれないと思うのだけど、今回はこういうの作りたいというのが先にあってそれから手段として CakePHP を選んだ。目的がフレームワークの使用ではないというのがよい出会い感に結びついている気がする。

3. 私が PHP の書き方にこだわりが無い

これが OCaml だったら10行書くのにもそれがベストな書き方かどうかとか、フレームワークに強制される書き方とのギャップにしばし悩んだりしそうなんだけど、PHP に関しては特に自分の中にこだわりが無いというのが逆によい方向に作用しているのではないかと思った。フレームワーク嫌いの人は自分のこだわりが薄い言語のフレームワークを選ぶとむしろよいのではないか。

ところで CakePHP 本というのがあるのかどうか探してみたのだけど専用の本というのはなかった。PHP フレームワークをたくさん取り上げる中に CakePHP のことも書いてあるというのはあったけど、本屋でぱらぱらめくった限りでは特に濃い感じではなかったので購入はしなかった。英語の本でも特に CakePHP 本っていうのはまだ出てないみたいだ。

[1] http://www.cakephp.org/


PHP でサーバソケットプログラミング (2) [PHP]

前回のプログラムの問題点

前回 [1] の sock_try2.php は同じ期間に1つの接続しか受け入れない設計でした。
クライアントから以下のように実行してみるとそれが分かります。

% set f [socket localhost 9999]
sock1544
% set g [socket localhost 9999]
sock1532
% puts $f f; flush $f
% puts $g g; flush $g
% puts $f f; flush $f
% puts $g g; flush $g
% puts $f f; flush $f
% puts $g g; flush $g
% close $f
% close $g

これを実行するとサーバ側は以下のようになります。

php sock_try2.php
f
f
f
g
g
g

クライアントからは2つの接続から交互に入力したつもりなのにサーバでは最初の接続が閉じてから2つ目の接続を処理しています。これは socket_try2.php が最初の接続で socket_accept して以降はその接続から読み切るまで次の socket_accept を実行しないためです。

複数接続を可能にする2つの方法

複数の接続に対応する方法の一つは fork を使うというものです。
これは socket_accept で新しい接続を受け入れたらその接続についての処理は子プロセスに引継ぎ、親プロセスはすぐに待ち受けに戻るというやりかたです。

しかし Windows 版の PHP では fork を使うことができないようなのでこれはあきらめます。

もう一つの方法は単一のプロセスで複数のソケットを管理し、かわりばんこに処理を行う方法です。かわりばんこに処理をすると言ってもデータが来ないソケットに対して socket_read を実行するとそこでブロックされてしまうのでプログラムは「管理しているソケットのうちどれがデータ読みこみ可能か」を知る必要があります。これを知るための関数が socket_select です。

socket_select はソケットの配列を受け取り、そのうちのどれかに変化があるまでブロックします。どれかに変化があったら配列を変化のあったソケットのみの配列に書き換えます。戻り値は変化のあったソケットの数です。

$num_changed_sockets = socket_select($read, $write = NULL, $except = NULL, NULL);

第1引数に読み込みを監視するソケットの配列を渡します。
第2引数が書き込みの監視、第3引数が例外の監視ですが、これらは今回は注目しないので NULL としています。
第4引数はタイムアウト時間ですが、基本的に NULL を指定していつまでも待つようにします。

これを使って複数接続可能版のサーバを書いて見ます。

複数接続可能版のサーバ
<?php // sock_try3.php

// 配列から要素を取り除く関数
function remove_elem(&$arr, $elem) {
  foreach ($arr as $k => $v) {
    if ($v == $elem) {
      unset($arr[$k]);
    }
  }
}

$port = 9999;
$sock = socket_create_listen($port);

// 待ち受けソケット+現在繋ぎに来ているソケットの配列
$socks = array($sock);

while (true) {
  $read = $socks; // socket_select は配列を破壊するので一旦別の変数に移す
  $num_changed_sockets = socket_select($read, $write = NULL, $except = NULL, NULL);

  foreach ($read as $changed) {
    if ($changed == $sock) {
      // 待ち受けソケットに変化=新しい接続が来た
      $clientsock = socket_accept($sock);
      $socks[] = $clientsock; // ソケット配列に追加
    } else {
      $buf = socket_read($changed, 1024);
      if ($buf == "") {
        // 読み込みが終わったらクローズしてソケット配列から削除
        socket_close($changed);
        remove_elem($socks, $changed);
      } else {
        echo $buf;
      }
    }
  }
}

socket_close($sock);

?>

プログラムが現在管理しているソケットは $socks 配列に入ります。これには最初は待ち受けソケットだけが入っています。

ここに入っているソケット配列を socket_select にかけるのですが、前述したようにこの関数は渡された配列を破壊的に書き換えるので一旦 $read という配列に移して渡しています。
これが終わったあとは $read に「変化のあったソケット」が入っています。

変化のあったそれぞれのソケットについて処理を行いますが「変化のあったソケットが待ち受けソケットかどうか」で処理の分岐を行います。
待ち受けソケットに変化があったというのは新しい接続が来たということを意味しているので、その場合は socket_accept を実行して得られた新しいソケットをソケット配列に追加します。
それ以外の場合はそのソケットからデータを読み込めるということなので socket_read を実施します。
読み込みが終わったらソケットを閉じてソケット配列から該当ソケットを除去します。配列から要素を取り除くために remove_elem 関数も定義してみました。

では先ほどと同様に複数接続を実験してみましょう。

% set f [socket localhost 9999]
sock1540
% set g [socket localhost 9999]
sock1532
% puts $f f; flush $f
% puts $g g; flush $g
% puts $f f; flush $f
% puts $g g; flush $g
% puts $f f; flush $f
% puts $g g; flush $g
% close $f
% close $g

今度はサーバ側はこうなります。

F:\>php sock_try3.php
f
g
f
g
f
g

ちゃんと複数接続をハンドリングできました。

なお実際のプログラムで使うにはちゃんと socket_* 関数のエラーチェックも必要です。

[1] http://blog.so-net.ne.jp/rainyday/2007-02-17


PHP でサーバソケットプログラミング (1) [PHP]

低レベルのサーバサイドソケットプログラミングってあんまり知らなかったのでちょこっと調べてまとめてみた。

対象言語: PHP5
この記事のゴール: クライアントから入力されたデータをそのまま標準出力に表示する

PHP5 でソケットを使うための準備

ソケットを使うには php.ini の以下の行のコメントアウトを外す必要があります。

変更前:

;extension=php_sockets.dll

変更後:

extension=php_sockets.dll

この設定をしないとソケット関数を使用したときに関数が未定義であるというエラーが発生します。

PHP Fatal error:  Call to undefined function socket_create() in ...
ソケットを作る

サーバ側ソケットを作るには socket_create_listen を使います。

$port = 9999;
$sock = socket_create_listen($port);

このコードでは9999番ポートを待ち受けとするサーバソケットを作成します。ポート番号は1024より上であれば任意です。
作成されたソケットは $sock に入ります。

socket_create_listen は実は若干高レベルなAPIで、細かい制御をするには socket_create, socket_bind, socket_listen を組み合わせて使います。socket_create_listen はこの3つをまとめて実施しています。

クライアントの接続を待つ

作成されたソケットを引数に socket_accept を呼び出すとクライアントからの接続待ちが開始します。

$clientsock = socket_accept($sock);

この行が実行されるとプログラムは接続が来るまで待ち続けてここで停止しています。
接続があるとそのクライアントとの通信用の新しいソケットが $clientsock に入り、次の行に進みます。
クライアントからのデータはこのソケットを通じて取得することが出来ます。

クライアントからのデータを読み込む

上記で得られた $clientsock からデータを読みこむには socket_read 関数を使います。

$buf = socket_read($clientsock, 1024);

第2引数の意味は最大で1024バイトのデータを取得するということです。

ソケットを閉じる

ソケットの後始末をするには socket_close 関数を使います。

socket_close($clientsock);
socket_close($sock);
ここまでのまとめと実験

ここまでのコードをプログラムにまとめて実行してみます。

<?php // sock_try1.php

$port = 9999;
$sock = socket_create_listen($port);
$clientsock = socket_accept($sock);
$buf = socket_read($clientsock, 1024);
echo $buf;
socket_close($clientsock);
socket_close($sock);

?>

クライアントはなんでもいいのですが、Tcl のインタラクティブシェルを使うことにします。

% set s [socket localhost 9999]
sock1808
% puts $s "Hello World!"
% flush $s
%

サーバの方を見ると…

F:\>php sock_try1.php
Hello World!

F:\>

おお!表示されてますね!(←「JavaでHello World」の人風に)

もっとサーバらしくする

しかしクライアントが1度フラッシュしただけで読み込みを終えたり、そもそも1回の接続だけでサーバが終了するというのもおかしな話です。

<?php // sock_try2.php

$port = 9999;
$sock = socket_create_listen($port);

while (true) {
  $clientsock = socket_accept($sock);

  while (true) {
    $buf = socket_read($clientsock, 1024);
    if ($buf == "") { break; }
    echo $buf;
  }
  socket_close($clientsock);
}

socket_close($sock);

?>

ここでは2つのループを追加しました。
内側のループは1つの接続からの全入力を終えるまで繰り返し socket_read を実行します。socket_read は読み込むものが無くなると空文字列を返すのでそれをループから抜ける判断に使います。
外側のループは内側のループの終了後に再び接続待ち状態に戻るためのものです。
サーバを止めるには Ctrl+C を打ちます。

外側のループは終了条件がないので実は最後の socket_close($sock); にはたどり着きません。本当はシグナルハンドラなどで終了処理をすべきなのですが Windows 版 PHP では使えないようなので差し当たりあきらめました。

これを起動して再びクライアントから接続を行います。

% set s [socket localhost 9999]
sock1760
% puts $s "Hello World!"
% flush $s
% puts $s "Hello Again!"
% flush $s
% close $s
% set s [socket localhost 9999]
sock1760
% puts $s "Howdy!"
% flush $s
% close $s

サーバ側の実行結果はこちら。

F:\>php sock_try2.php
Hello World!
Hello Again!
Howdy!

クライアントがクローズするまで読み込み続けること、クローズをした後に再接続できることがわかります。

次回は複数の接続を同時に受け入れられるようにする方法を(調べて)書きます。


Nanoserv のバグ [PHP]

CodeReadingWikiの単体動作版を鋭意開発中なのだけど、HTTPデーモンとして使っている nanoserv にバグらしきものがあって座礁してしまった。
現象としては大きいファイルをダウンロードしたりリロードが狭い間隔で繰り返されたりするときにいくつか警告が出た後1823行目でエラーになる。

PHP Notice:  fwrite(): send of 8192 bytes failed with errno=10035 ブロック不可の
ソケット操作をすぐに完了できませんでした。
 in F:\php-5.2.0\PEAR\nanoserv\nanoserv.php on line 270
PHP Notice:  Undefined property:  NS_Socket::$socket in F:\php-5.2.0\PEAR\nanose
rv\nanoserv.php on line 1804
PHP Notice:  Trying to get property of non-object in F:\php-5.2.0\PEAR\nanoserv\
nanoserv.php on line 1804
PHP Notice:  Undefined property:  NS_Socket::$socket in F:\php-5.2.0\PEAR\nanose
rv\nanoserv.php on line 1822
PHP Notice:  Undefined property:  NS_Socket::$socket in F:\php-5.2.0\PEAR\nanose
rv\nanoserv.php on line 1823
PHP Fatal error:  Call to a member function Eof() on a non-object in F:\php-5.2.
0\PEAR\nanoserv\nanoserv.php on line 1823

どうもこの手前部分で $handler 変数に NS_Connection_Handler クラスのオブジェクトが入ってないといけないところに NS_Socket クラスのオブジェクトが入っているのがよくないらしいというところまでは分かった。

もうちょっと調べてみたら開発者のフォーラムに報告してみるかなあ。


文字列から関数やクラスを得る [PHP]

PHPのちょっと面白い言語機能として、変数の中に関数名やクラス名を入れておくと $ をつけて該当する関数やクラスに代替できるというものがある。

<?php

function fun () {
  print("fun() called\n");
}
class Klass {
  public function __construct() {
    print("Klass created\n");
  }
}

$funname = "fun";
$classname = "Klass";

$funname(); // -> fun() called
new $classname; // -> Klass created

?>

もっとも $"fun"() とか new $"Klass" とか書くことはできない。

これの使い道として思いつくのは、例えば abstract class DbAgent { ... } とそれを継承した class MySQLAgent extends DbAgent{ ... }class SQLiteAgent extends DbAgent{ ... } があったとして、MySQLを使う場合には設定ファイルに "MySQLAgent" と書き、SQLite を使う場合には "SQLiteAgent" と書く。一方でプログラム側では設定値を $dba 変数に入れておいて new $dba とする、みたいなことができる。なかなか便利。

Java の Apache log4j ライブラリでもログの切り替えポリシーに対応するクラス名を log4j.properties に書いていたりするけど、ちょっとソースを追ってみたらあれは ClassLoader でクラスを作って Class クラスのオブジェクトの newInstance メソッドを呼んで…みたいなあまり見慣れないことをやっていた。


PHP の new 演算子とメソッドチェイン [PHP]

オブジェクトに対して右に右にメソッドを書き連ねていく書き方はよくメソッドチェインと呼ばれている。また生成したオブジェクトを変数に結びつけずにすぐにメソッドを呼んで使い捨てるという書き方がコードの簡潔さの上で好ましい事もある。

Ruby だとこういう感じだ。

irb(main):013:0> class Foo
irb(main):014:1>   def method() print "hello!\n" end
irb(main):015:1> end
=> nil
irb(main):016:0> Foo.new.method()
hello!
=> nil

Python でもできる。

>>> class Foo:
...   def method(self):
...     print "hello!"
...
>>> Foo().method()
hello!

C++ ですらできる(でもメモリリークします)。

#include <iostream>

class Foo {
public:
  void method()
  {
    std::cout << "hello!\n";
  }
};

int main(int, char*[])
{
  new Foo()->method();
  return 0;
}

それが PHP では…

<?php

  class Foo {
    public function method() {
      print("hello!");
    }
  }

  new Foo->method();

?>
F:\>php tryme.php
PHP Parse error:  parse error, unexpected T_OBJECT_OPERATOR in F:\tryme.php on l
ine 9

できない! こういう仕様なのかな。括弧で囲んでもだめみたいなので下みたいに生成用のメソッドを静的メソッドで作るのがいいのかもしれない。

<?php

  class Foo {
    public static function create() {
      return new Foo;
    }
    public function method() {
      print("hello!\n");
    }
  }

  Foo::create()->method();

?>

Nanoserv - a sockets daemon toolkit for PHP 5.1+ [PHP]

先日の記事 [1] で「Ruby の WEBrick みたいなのの PHP 版」が欲しいと書きましたが、記事中で紹介した Nanoweb と同じ人が開発している Nanoserv [2] というのがそれだったようです。以下はサンプルから引用。

#!/usr/local/bin/php
<?

require "nanoserv/nanoserv.php";
require "nanoserv/handlers/NS_HTTP_Service_Handler.php";

class dumb_httpd extends NS_HTTP_Service_Handler {

    public function on_Request($url) {

        return "You asked for url : <b>$url</b>\n";

    }

}

Nanoserv::New_Listener("tcp://0.0.0.0:800", "dumb_httpd")->Activate();
Nanoserv::Run();

?>

そうそう、こういうが欲しかったのです。

[1] http://blog.so-net.ne.jp/rainyday/2006-11-19-1
[2] http://nanoserv.si.kz/


独習PHP [PHP]

いい加減 PHP をウェブで検索しながら場当たり的に書くのもよそうと思って適当な書籍を物色。PHP のような言語は勘でかけてしまう分なかなか本腰入れて勉強する気にならないものですが、ここ数日で PHP 熱があがってきたのでこれを逃すまいという気もしたので。

できるだけクックブック的なものでなくて言語仕様を解説しているのがいいと思って書店で検討した結果、「独習PHP」にしました。PHP には JavaScript でいう Rhino Book みたいな超定番はないのかな。

で、関数名と定数名では大文字小文字を区別しないというのは初めて知りました。(でも変数名では区別する!)
定義済み定数に実行中の関数名/クラス名/メソッド名があるのが面白いなあとか。

あと誤植を発見しました(初版第2刷)。p.53 の $_FILES['file']['error'] の説明が「オリジナルのファイル名」となっているのは「エラーコード」の間違いですね。


Nanoweb - an HTTP server written in PHP [PHP]

CodeReadingWiki はウェブアプリケーションとして作ったので使うにはウェブサーバが必要だ。ブラウザベースのアプリケーションだからまあ妥当な話ではあるのだけど、プロバイダやレンタルサーバを使う場合はともかくとして、ローカルでちょっと動かしてみたいというときに Apache なり何なりを入れるというのはとんでもなく面倒な話になる。フォルダごとどこかにおいてアイコンをクリックすれば起動というのが理想だ。

そこでウェブアプリとしても使えつつ、単独で起動も可能なように作ることは可能だろうかと考えた。

CodeReadingWiki は PHP で作り始めてしまったので(今のささやかな行数なら引き返せないこともないけど)、PHP のみで実装された HTTP サーバライブラリみたいなものがあればよい。ということで探してみたら Nanoweb [1] というのがあった。

「an HTTP server written in PHP」。まさにそういうことなんだけど、これはむしろ頑張りすぎで、かなり Apache 的なものになっていて Apache 的な設定ファイルがあって、あちこちのファイルパス設定をインストール場所にあわせて書き換えないといけない(そんな部分まで Apache の真似をしなくていいのに)。

これだと「フォルダごとどこかにおいてアイコンをクリックすれば起動」という理想から離れてしまうので、むしろ Python の BaseHTTPServer とか Ruby の WEBrick のようなライブラリ的に使えるものがよいんだよなあ。

[1] http://nanoweb.si.kz/


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。