php

Entries

SPL: RecursiveIterator

Date
2007-10-29 (Mon)
Category
php

PHP5 からは、Standard PHP Library (SPL) 関数 というのが標準でついてきます。標準 PHP ライブラリとかいったら、すぐに使える便利なライブラリなどを想像してしまう僕はおバカさんかな…標準 Interface / Design Pattern 集とでも云ってくれたほうが、僕にはわかりやすいかったかも。実際の所、ほとんどのコードは自分で実装しなければならない。ただ例えば、今回扱うような foreach の挙動をクラスの側から操作できる、というのはなかなか魅力的だし、今までの PHP にはなかった事ですね。

でも使っている人が少ないのか、コードサンプルがあまり見当たりません。今回実装した RecursiveIterator など、一度ハマりどころがわかれば全然簡単なものの、わからないときはさっぱりわからないもの。いわゆる Design Pattern 集に出てくるような内容なので、晒すのもすこし気後れしますが、まぁ何かの足しにはなるでしょう。というわけで以下コード。

コード

FooNode.php

<?php

class FooNode implements RecursiveIterator { public $name; protected $parent = null; protected $children = array(); protected $cursor = 0;
public function __construct ($n = null) { $this->name = (String)$n; }
public function __toString () { $prefix = null; if (!is_null($this->parent)) { $prefix = (String)$this->parent . '/'; } return $prefix . $this->name; }
public function setParent (FooNode $v) { $this->parent = $v; }
public function add (FooNode $v) { $this->children[ $v->name ] = $v; $v->setParent($this); return $v; }
//Iterator public function current () { if ($this->key() !== false) { return $this->children[$this->key()]; } else { return null; } }
public function key () { $keys = array_keys($this->children); $length = count($keys); if (($length === 0) || ($length <= $this->cursor)) { return false; } return $keys[$this->cursor]; }
public function next () { if (count($this->children) > 0) { $this->cursor = $this->cursor + 1; } }
public function rewind () { $this->cursor = 0; }
public function valid () { $result = false; if ($this->current() instanceof FooNode) { $result = true; } return $result; }
// RecursiveIterator public function getChildren () { return $this; }
public function hasChildren () { //trigger_error($this->name . " hasChildren?"); return !empty($this->children); }
// static function public static function traversal (FooNode $f, $d = 0) { $count = 0; foreach ($f as $node) { //$prefix = str_repeat("", $d); //echo $prefix; echo $node; echo "\n";
if ($node->hasChildren()) { $count += FooNode::traversal($node->getChildren(), ($d + 1)); } ++$count; } return $count; }
}

main.php

<?php

require_once('FooNode.php');
$a = new FooNode('root');
$b = $a->add(new FooNode('hoge')); $b->add(new FooNode(0)); $b->add(new FooNode(1)); $b->add(new FooNode(2));
$c = $a->add(new FooNode('dims')); $d = $a->add(new FooNode('dinges'));
$e = $c->add(new FooNode(3)); $e->add(new FooNode('a')); $e->add(new FooNode('b')); $e->add(new FooNode('c')); $c->add(new FooNode(4)); $c->add(new FooNode(5));
$f = $d->add(new FooNode(6)); $f->add(new FooNode('d')); $f->add(new FooNode('e')); $f->add(new FooNode('f')); $d->add(new FooNode(7)); $d->add(new FooNode(8));
$c = FooNode::traversal($a);
echo "$c times traversed.\n";

使い方

同じディレクトリに main.php と FooNode.php というファイルを用意して、コマンドラインから php main.php とタイプしてみてください。

簡単な解説

FooNode::traversal() というのが、Recursive Iteration をするメソッドです。僕は getChildren と hasChildren が返す値について少々悩みました。hasChildren は Loop の外側から見ると、子供の子供があるかどうかを確認するメソッドですが、この実装では(RecusiveIterator ではなく)Iterator がオブジェクトの子要素を iterate するようになっているため、hasChildren が呼び出されるのは既に Loop が回っている先。だから直近の子供を確認しているのですね。

ただまぁこの traversal メソッド内の if 文で hasChildren なのはちょっとダサい気がするので、外部 iterator を使った実装に変えた方がいいかもなぁ。でも実際には db が絡むからこれでのいいのかもしれない。

あと Iterator::next() や Iterator::current() が 配列関数の next()reset() とは少し挙動が違います。僕が前に Iterator の実装をした時は同じようにしていたのですが、今回 http://www.phpguru.org/downloads/Tree/Tree-2.0.0.tgz/Tree-2.0.0/ を見つけてこのようにしました。

今よく Manual を見直すと void RecursiveDirectoryIterator::next (void)void RecursiveDirectoryIterator::rewind(void) とあるので、僕が間違っていたんですね…

また時間を見つけて SPL についても書いてみたいと思っています。

Dreamhost で php.ini を設定する方法 続き

Date
2007-06-03 (Sun)
Category
php

以前のポストで Dreamhost 上で、カスタマイズした php.ini を使う方法を紹介しましたが、セキュリティ上すこし気になる事があったので、アップデートします。

前回の設定のままだと、php.ini が公開されてしまうんですね。まぁ php.ini にパスワードを書く人は居ないと思うので、それほど大きな問題になるとは思えないんですが、Directory Structure を全世界に公開するのはあまり気持ちのいいものではないので、隠しましょう。

設定をしたホストのディレクトリにて、以下の内容の .htaccess ファイルを作成してください。

Options -Indexes
<Files php.ini>
  deny from all
</Files>

結果として、.htaccess ファイルを二つ書くことになって、それはとってもイケてないんですが…Dreamhost で普通の web hosting だと、いわゆる shared host なので、自分では変えられない設定がいくらでもあります。Location, LocaitonMatch, Directory, DirectoryMatch などがルートの設定( httpd.conf の中)でブロックされてるんですよね。あとは、.htaccess は deny from all になっているようです。

Dreamhost で php.ini を設定する方法

Date
2007-04-28 (Sat)
Category
php

追記事項があります。

Dreamhost には Wiki があって、たいていの情報はそこに集まっています。php.ini に関しては、以前にコメント欄で教えてもらって以来、ずっと使っていたんですが、今回もう一度自分で設定する機会があったので、まとめ直しておきます。前述の Wiki には、2つ項目があってだいたい同じような事が書いてあります。それらの訂正なども含めて。あと あなたの知らない PHP 5つの秘密。 なんて記事を訳した手前、PHP4 に関しては、スルーしておきます。

wiki.dreamhost.com : Custom PHP.ini
wiki.dreamhost.com : PHP.ini

手順

自分の設定を書いた php.ini を使いたいドメインの document root に cgi-bin というディレクトリを作ります。以下では dreamhost というログインネームで bash を使っていて、dreamhost.com を運用していると仮定します。

$ pwd
/home/dreamhost
$ cd dreamhost.com
$ mkdir cgi-bin

次に php の cgi バイナリと php.ini ファイルをコピーします。

$ cp /dh/cgi-system/php5.cgi $HOME/dreamhost.com/cgi-bin/php5.cgi
$ cp /etc/php5/cgi/php.ini $HOME/dreamhost.com/cgi-bin/php.ini

ここで php.ini を更新しましょう。FastCGI 経由で起動されるとはいえ、php のスクリプトが起動されるたびに読み込まれてるはず、なので、短い方がいいと思います(が、どうなのだろう?また今度調べましょう)。僕はコメントなどは全部削除して、5KB くらいにしました。

ここで、.htaccess ファイルを作成して、document root 以下にある .php ファイルでは、全てこの cgi-bin 以下にある php バイナリを経由するようにします。

$ pwd
/home/dreamhost/dreamhost.com
$ vi .htaccess

.htaccess

AddHandler php-cgi .php
Action php-cgi /cgi-bin/php5.cgi

ここで5分くらいハマりました。僕はものぐさなので、cp するとき php5.cgi の名前を php.cgi に変えませんでした(Terminal では tab キーで directory 名の補完をしてしまうので)。上記の wiki にある .htaccess をコピペしたら

Not Found
The requested URL /cgi-bin/php.cgi/php/script.php was not found on this server.

と、404 がでてしまいます。なぜかというと、Action の deploy 先が php5.cgi ではなく php.cgi だったからです。というわけで、上記の .htaccess では php5.cgi としておきました。

次に permission の設定を正しくします。

$ chmod 644 $HOME/dreamhost.com/.htaccess
$ chmod 755 $HOME/dreamhost.com/cgi-bin
$ chmod 700 $HOME/dreamhost.com/cgi-bin/php5.cgi
$ chmod 600 $HOME/dreamhost.com/cgi-bin/php.ini

上記 wiki よりだいぶ厳しい permission ですが、dreamhost では CGI がユーザ権限で起動しているので(というか CGI がユーザ権限で起動しているのか)動きます。

<?php
echo `whoami`;
?>

とかすれば、どのユーザで実行されているかわかるはず。

最後に、php のバイナリを dreamhost で使われている最新に追随させるためのスクリプトを書きましょう。

$ pwd
/home/dreamhost/dreamhost.com
$ cd ..
$ mkdir bin
$ vi bin/php-copy

php-copy

#!/bin/sh
CGIFILE="$HOME/dreamhost.com/cgi-bin/php.cgi"
rsync -a /dh/cgi-system/php5.cgi "$CGIFILE"

wiki.dreamhost.com : PHP.ini では .ini をいちいち正規表現で処理していますが、それはアフォらしいので省きました。っつぅか、php.ini はそんなに書き換えないでしょう。あとは 上記ファイルを cron で回します。

$ crontab -e
@weekly /home/dreamhost/bin/php-copy

あえてもう一個ハマりどころを上げるとすると、僕は error を別ファイルに書き出して、display_errors は off にする人なので、error_log に指定したファイルを作っておく事でしょうか。permission の所に書いた通り、php はユーザ権限で起動しているので、chmod 600 php-error.log でいいはずです。それとその log は document root より上に置くべきということも。

追記 on June 3, 2007:ここに挙げた設定に加えて、Dreamhost で php.ini を設定する方法 続き に書いた内容ももれなくやっておいた方が、セキュリティ上好ましいでしょう。

How-to: Linux talks to Microsoft SQL Server by PHP Data Objects (PDO) その2

Date
2007-04-18 (Wed)
Category
php

How-to: Linux talks to Microsoft SQL Server by PHP Data Objects (PDO) その2

前回に引き続き、Linux 上の PHP から、PDO を使って、Micrsoft SQL server に接続する方法を解説します。

unixODBC と FreeTDS の設定

Relationship between conf files

自分でやっているときは、異常にワケがわからなくて、いらだっていた設定ファイルの関係なんですが、わかってみて、絵にしてみたら、全然複雑じゃないですね。実際 FreeTDS のユーザーガイドをしっかり読めば…少なくとも 3. Install FreeTDS4. Preparing ODBC を読んでおけばどうなっているかは理解できるはずです。実際の設定内容は以下の通り。

odbcinst.ini

[FreeTDS]
Driver = /usr/local/lib/libtdsodbc.so

odbc.ini

[ODBC Data Sources]
MY_DSN_NAME = MSSQL

[MY_DSN_NAME]
Driver = /usr/local/lib/libtdsodbc.so
Description = MSSQL
Trace = No
Servername = MY_HOST_NAME
Database = MY_DB_NAME

[Default]
Driver = /usr/local/lib/libtdsodbc.so

freetds.conf

[MY_HOST_NAME]
host = MY_HOST_IP
port = MY_HOST_PORT_NO
tds version = 7.0

これらの設定は、unixODBC パッケージに付属の odbcinst コマンドで確認できます。

# odbcinst -q -d
[FreeTDS]
# odbcinst -q -s [MY_DSN_NAME] [Default]

ここで、まずは FreeTDS パッケージに付属の tsql コマンドを使って、FreeTDS の接続がうまく行くか試してみましょう。

# tsql -S MY_HOST_NAME -U USERNAME -P PASSWORD
locale is "en_US.UTF-8"
locale charset is "UTF-8"
1>

1> 以降は、改行を押すたびに 2> 3> と数が増えますが、それ以上何の反応もないです。1> も出ない/エラーが出る、ということは、サーバと接続できていません。サーバーの IP がリーチャブルかとか、Port があってるかとか試してみてください。IP と Port に関しては telnet コマンドで

#telnet MY_HOST_IP MY_HOST_PORT_NO
Trying MY_HOST_IP...
Connected to MY_HOST_IP.
Escape character is '^]'.

とか出れば、リーチャブルなのでしょう。あとは TDS のバージョンを変えてみるとか。

tsql がうまく言ったら今度はいよいよ実際に SQL を投げてみましょう。unixODBC パッケージに isql というコマンドがあり、これは SQL のインタラクティヴシェルです。

# isql -v MY_DSN_NAME USERNAME PASSWORD
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL>

SQL 文を発行してみて、内容を確認してみてください。もし connected と出ない場合は、おそらく DSN の設定がおかしいです。isql を -v で起動すると verbose モードで、いろいろエラーが出ていると思うので、内容をググって見てください。7. Troubleshooting の内容も役に立つでしょう。

PDO のインストール

PDO ではクラスメソッドが統一されていて、どのデータベースを使っていようと、同じ操作感を提供する、というのが売りです。で、違いはインスタンス作成時の DSN の呼び方で、実際にどのクラスを使うのかを分けます。僕は正直ってここにハマっていました。ODBC を使っているので、てっきり ODBC だと思い込み、ひたすら PDO_ODBC をビルド/インストールしては動かない、なんでだろう?を繰り返していました。6. How to get what works with it working:: PHP にも ODBC って書いてあるし(db-lib とも書いてありますけど、まぁ焦っているとわからないものです)。

大きな声で言っておきます。PHP で FreeTDS を扱えるのは PDO_DBLIB です。っつぅかね、マニュアルにもでかでかと XCI. Microsoft SQL Server および Sybase 関数 (PDO_DBLIB) って書いてあるしね…やっぱ RT*M だね。。。

でももし PDO が shared module じゃなくて、static link だったら、この PDO の設定を変えるたびに PHP 全体を再ビルドしなくちゃいけなかったわけで、shared module にしておいてよかったよかった。やり方自体は全然簡単で、PHP 本体の ./configure の時に option で --disable-pdo を一言追加。後は pecl コマンドを使って、pecl install PDO_DRIVER とかする。

今回は設定を一応確認しながらやりたかったので、pecl のやってる事を手動でやってみました。

  1. PECL サイトにて PDO_DBLIB パッケージのアーカイヴをダウンロード
  2. アーカイヴを展開して ( tar -zxvf PDO_DBLIB-1.0.tgz ) PDO_DBLIG のディレクトリに移動
  3. phpize コマンドを実行
  4. ./configure コマンドを実行
  5. make && make install
  6. すると no-debug-XXXXXXXXX とかいうディレクトリ名で pdo_dblib.so とかいうファイルが出来ているはずなので、extension_dir でなければ、extension_dir に移動させる。
  7. php.ini の中にある Dynamic Extensions 項に extension=php_dblib.so を一行追加。extension_dir からの相対パスになるのも注意
  8. Apache を再起動して、p h p i n f o () を確認する

僕が確認している事項

  • PDO で FreeTDS を使用する DSN suffix (DSN接頭辞) は今の所 dblib です。マニュアル:PDO_DBLIB DSN には以下のようにありますが、今の所実装とは異なります。
    PDO_DBLIB が FreeTDS ライブラリに対してリンクされている場合は DSN 接頭辞は sybase: です。 Microsoft SQL Server ライブラリに対してリンクされている場合は mssql:、それ以外の DB-lib に対してリンクされている場合は dblib: となります。
  • そして DSN 名による接続はできません。以下のように必ず host:port を使う必要があります。
    $PDO = new PDO('dblib:host=IP_ADDRESS:PORT_NO;dbname=DB_NAME','USERNAME','PASSWORD');
    上記マニュアルページのコメント欄にありますが、Windows システムでは IP_ADDRESS:PORT_NO という指定は出来ず IP_ADDRESS,PORT_NO となるらしいですが、僕は試してません。
  • datetime 型 の値が結構変わってしまうかもしれません。おそらく FreeTDS の locales.conf がやってる部分だと思うのだけれど、まだよくわかってません。

まとめ

以上で PHP の PDO を使って FreeTDS 経由で、MSSQL サーバーに接続する事が出来ました。PDO のマニュアルもしっかり整備されてないし、実装が変わる可能性もいっぱいありそうですが、FreeTDS 自体の提供する接続は結構安定しているし、使えそうだと思います。

あとは PDO_ODBC だと思い込んで、コンパイルして PHP が無言で Segment Fault を頻発したときは血の気が引きましたが、要するにマニュアルとソースを嫁!という普通の事を再確認しました。

How-to: Linux talks to Microsoft SQL Server by PHP Data Objects (PDO) その1

Date
2007-04-17 (Tue)
Category
php

Linux で走る PHP のアプリケーションサーバーから Microsoft SQL Server (以下 MSSQL ) に接続したい機会って、あまりないんでしょうか? Web で探しても余り出てこなかったので、メモしておきます。

PHP で今や PHP Data Objects (以下 PDO) を使わない Database 開発はあり得ない、とまでは言わないけど、新しく始めるのに PDO を使わない手はないと思います。ので、今回は背景の説明で力つきましたが、次回にて Linux 上で走る PHP から PDO を使って MSSQL に接続する方法を解説します。加えて PDO を共有モジュールとして組み込んでおけば、こういう時にデータベースドライバを組み込めばいいだけで簡単に始められるので、pecl コマンドを使った PDO のコンパイルも簡単に説明します。

必要なもの

MSSQL サーバー (DB)
TCP 接続を受け付ける設定
ODBC 接続の UID と PWD
Linux サーバー (Application Server)
コンパイルできる環境
PHP5
unixODBC
FreeTDS

まずは使うソフトウェアの説明。以下の図をご覧あれ。

Database Software Chains

Database と通信するソフトウェアは、これらパーツの組み合わせだと思います。一番 Typical なのは、一つのマシンに MySQL と Apache と PHP が一緒に走っていて、開発機だったら Client である Browser も走っているかもしれません。そこでの処理の流れは Browser ⇔ Apache ⇔ PHP ⇔ Database となります。また PHP と Database (は MySQL だと仮定して、そ) の間だけ注目すると PHP ⇔ MySQL 関数 ⇔ MySQL ライブラリ ⇔ unix socket コネクション ⇔ MySQL Server というつながりになります。これが Application と Database が別れたマシンだと、Connection Protocol の部分から、例えば TCP/IP 上を流れる、ということです。

今回、使いたい

Application:
PHP on Linux (CentOS)
Database:
MSSQL on Windows Server 2003

だと、Connection Protocol は ODBC で、Driver は FreeTDS というのを使います。(もちろん他にも選択肢はあります)

unixODBCFreeTDS のインストール

CentOS なので yum を使って unixODBC と unixODBC-devel をインストールしました。正直に言うとソースからのインストールも試みたのですが、Qt ライブラリがないというエラーで ./configure から先に進みませんでした。Web サイトによると Qt は設定用の GUI に必要で、なければ無視して先に進むとあったのですが… 一応 "--witout-x --without-qt-dir --without-qt-libraries --without-qt-programs"とかは試したんですけど。unixODBC-devel は PDO をコンパイルする時に必要になります。

副次的に rpm -qs unixODBC というコマンドで、何処にどのファイルがインストールされたか一目瞭然で、これが後で結構役にたちました。

FreeTDS は自分でビルドしました。./configure --with-opeenssl とだけして、後はそのまま make && make install で /usr/local 以下にインストールされました。

猛烈に眠くなったので、また明日。How-to: Linux talks to Microsoft SQL Server by PHP Data Objects (PDO) その2へ続く。。。

ISO Week Notaiton in PHP, or double standard of PHP

Date
2007-03-06 (Tue)
Category
php

ISO Week Notation to Epoch time

ISO 8601:2004 で定義されている 週 を単位にした表記法を Epoch に変換する関数を作りました。Wikipedia の項目を参照して作りました。もし間違いがあれば指摘してください。

もし 2007-W10-2T08:51Z という風にすると、2007年の 第10週目 の 2日目(火曜日)の8時51分、ということになります。

これを制作するにあたって、PHP のトリビアを一つ。

PHP の2重舌外交政策

  • まず大前提として、ISO 8601:2004 では、週連番として、1 〜 7 を 月曜日〜日曜日に割り当てています。(ISO による要約)
  • それに習ったのかどうかは知りませんが、 C 言語でも、週連番として、1 〜 7 を 月曜日〜日曜日に割り当てています (PDF: C99 の仕様書) 。
  • strftime という文字列を解析して Epoch を返す関数では、 Solaris を名指しして、ISO 準拠でないシステムへの批判ともとれる警告文を掲載しています。
  • しかし
  • idate 関数、あるいは date 関数を見てみると解りますが、PHP の内部では 日曜〜土曜を、0〜6とする実装になっています。さてどうしたものか。
  • タイトルは煽りです。

なおこの関数を制作するにあたり、tzzz: ISO 8601 week number to date (timestamp) in PHP が大変参考になりました。というよりロジックはほとんど拝借しています。ありがとうございます。

Design Patter by PHP[4]: Factory Method

Date
2006-10-31 (Tue)
Category
Design Pattern | php

第四回目は Factory Method です。コードは毎回 Subversion のレポジトリ

http://svn.nydd.org/dpxphp/

にあります。

今回の気づき

  • PHP って packaging 機構はないのかなぁ。。PEAR にあるにはあるが、ちょとめんどい。そろそろ class auto load の仕組みをそろそろ覚えるべきかも。

実行結果は

$ php main.php
The card for Takashi will be made.
The card for John will be made.
The card for Carren will be made.
The card for Takashi will be used.
The card for John will be used.
The card for Carren will be used.

でした。

preg_* に関する覚え書き

Date
2006-10-22 (Sun)
Category
php

正直言って、しょうもないことですが、ちょっとハマったので書き記しておきます。

PHP の Perl 互換正規表現 (PCRE) を使った関数群、で行頭記号 ( ^ ) を使ってもなんかマッチしないなぁ、とちょと悩みました。よくドキュメントを読みましょう。。

パターン修飾子 -- 正規表現パターンに使用可能な修飾子

現在使用可能な PCRE 修飾子の一覧を以下に示します。 括弧の中の名前は、これらの修飾子に関する PCRE 内部の名前です。 修飾子中での空白文字および改行は無視されます。他の文字はエラーになります。
〜〜 snip 〜〜
m (PCRE_MULTILINE) デフォルトで、PCRE は、検索対象文字列を(実際には複数行からなる 場合でも)単一の行からなるとして処理します。 「行頭」メタ文字 (^) は、対象文字列の最初にしかマッチしません。 一方、「行末」メタ文字 ($) は、文字列の最後、または (D 修飾子が設定されていない場合) 最後にある改行記号の前のみにしかマッチしません。 この動作は Perl と同じです。

だ、そうです。つまり、いわゆる空白行をマッチしたいときは…

preg_match("/^$/m", $haystuck, $match);

とかする必要があります。

Design Patter by PHP[3]: Template Method

Date
2006-10-18 (Wed)
Category
Design Pattern | Tech | php

日々是鍛錬。

第三回目は Template Method です。コードは前回に引き続き、Subversion のレポジトリ

http://svn.nydd.org/dpxphp/

をどうぞ。

今回の気づき

  • 毎回思っていたけれど、PHP では名前空間の制約が厳しく、標準ライブラリに存在する関数と同じ名前の関数を定義できない。前2回分では、SPL で宣言されているクラス名と当たっていて、今回は標準関数の print だった。特に関数はなんとかしてもらいたい所ではある。だって内側から呼ぶのも $this つけなきゃ行けないわけだし、ambiguity はなさそうだけれども…
  • println ってなかったっけ??
  • 字数を数えるのにちょっと苦労した。以下 StringDisplay.php より
    $this->width = mb_strlen($this->str);
          if ( $this->width != strlen($this->str) ) {
             $this->width = $this->width * 2;
          }
    要するに、mb_strlen と strlen で数えてみて、一致してなかったらマルチバイト文字列と判断して、そうだったら、とりあえず2倍にしてある。ホントは private $is_mb とかつけて、printLine() を工夫すべきか、な。
    • 少し正確に話すと、unicode では(ASCII 互換以外の文字列、いわゆるダブルバイトと呼ばれているもの)1文字3byte消費しているの(だよね?)で、『あ』の1文字で strlen では 3 という返り値、mb_strlen では 1 という返り値を得る。ま〜正確ではあるけど、使いやすくはないのかな〜
    • 追記 on March 2007: Unicode 、と一緒くたに語っていますが、これは正確じゃない。例えば(日本語の wikipedia によればほとんど使われていないらしいが) UTF-1 は 8-bit (≒ 1byte) コード。ここで語ろうとしている UTF-8 はマルチバイトで可変長 ( 1〜4 byte ) 。『あ』が 3byte だったのは“たまたま”。この実装は間違いですな。

実行結果は

$ php main.php 
<<HHHHH>>
+-------------+
|Hello, World.|
|Hello, World.|
|Hello, World.|
|Hello, World.|
|Hello, World.|
+-------------+
+------------+
|こんにちは。|
|こんにちは。|
|こんにちは。|
|こんにちは。|
|こんにちは。|
+------------+

でした。

Design Patter by PHP[2]: Adapter

Date
2006-10-16 (Mon)
Category
Design Pattern | Tech | php

前回の イテレータ パターン からはだいぶ時間が空いてしまいました。本を友人に貸していたからなんですが、まぁ言い訳はこのくらいにして。

未だ Chpater 2: Adapter Pattern です。今回からは(昨日から運用開始した)Subversion を使ってレポジトリを公開〜。まだ非常に稚拙なコードですが、興味のある人は、

http://svn.nydd.org/dpxphp/

をどうぞ。

今回の気づき

  • Java の super() は PHP で parent:: となる。
    • PHP4 では class 名と同じ function がコンストラクタに、いわゆる Java と同じ形式だけだったのに、PHP5 から Object に __construct() という特殊関数ができて、誰が使うんだろーなー、と正直思っていたけれど、なるほど確かに parent::__construct() を呼ぶのに、統一的かつ合理的にするには __construct() を使うべきだと思いました。ちなみにマニュアルにはこう書いてあります。
      For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, it will search for the old-style constructor function, by the name of the class. Effectively, it means that the only case that would have compatibility issues is if the class had a method named __construct() which was used for different semantics.
      実際実験してみましたが、__construct() を class 宣言の中に含めなくても parent::__construct() で呼び出すことができています。
  • PHP とは関係なく、svnX の使い勝手の問題なんですが、Working Copies ウィンドウから開けるところで、Update ボタンを押すと、"Are you sure you want to update this working copy to the latest revision?" って聞いてくれるのだけれども、Yes を押すと、どれが変わるとかは言ってくれないで、黙ってアップデートされてるのがすこし気になった。

CLI と SAPI, あるいは Google Code Search について

Date
2006-10-09 (Mon)
Category
php

PHP は Web 専用のプログラミング言語だと誤解されがちですが、実際には、CLI (Command Line Interface) と云って、shell から直接起動できるバイナリも普通は一緒についてくるので、普段の shell script とか、ワンライナーみたいなことをやろうと思えばできます。

僕は結構 cron から立ち上げる php スクリプトを書いたりすることもあるんですが、例えば shell 起動と Web 経由では View 部分を変えるけど、Model/Controller は一緒、みたいなのは技術的には可能ですよね。さてどうしよう?というのが今回の疑問でした。

結論から言うと、

string php_sapi_name ( void )

という関数がありました。戻り値の string は起動した interface を保持しています。CLI 起動だと "cli" とか mod_php 起動なら "apache" で、cgi 起動なら "cgi"。サイトの例にあるように、文字列判別をすればそのまま使えそうです。

これをどうやって見つけたか?この間発表になったばかりの Google Code Search を使ってみました。英語でコードを検索する時、例えば PHP だとひたすら cvs.php.net とか bugs.php.net とかが検索結果上位に来ていてあんまり役に立ってなかったけど(こういう時日本語は便利)、code search なら一発でした。 CLI SAPI branch lang:php 英語の思いついた素直なキーワードですぐ出てきた。これは便利かもしれません。

全く印象の余談ですが、Google Code Search、ちょっと速くなったような?今まで safari で検索結果の code を見ようとすると javascript object かしらん?表示は完了していても何か別項目をひたすらロードし続けていたけれど、今はちゃんとロード完了するし、何となく code 表示が機敏になったような。

PHPTALでQuoteを出力する

Date
2006-08-10 (Thu)
Category
php

PHPTAL を調べています。もうすぐ公開できると思う project でも使っています。PHPTAL だと、しっかり組んだ (X)HTML をテンプレート化するのがスゴく楽。コード書かない人でも、logic が理解できる人ならつかえると思う。Symfony でも一時期 View の所で使われたみたいだし、啓蒙する人が居れば結構流行りそうだけどな〜。っていうか、GUI 被せればいいのかも。次の project でやってみようかしらん。

さて。今回はちょっと tricky ではあるんですが、javascript の文 (statement)、つまり <script> 〜〜 </script> の波線の部分を PHPTAL で出力したろ、というのが今回の課題です。普通に文を出すのはいくらでも方法があるのですが、javascript で string を表現したい時はどうしても quote を使わないといけません(多分)。

でも、PHPTAL は HTML 要素の attribute を制御構造に使うという性質上、single quote (') と double quote(") は特別な意味を持っていて、普通に出力すると実体参照(entity reference、例えば &quote;みたいなやつ)に変換されてしまいます。無理に出そうとすると Error を吐きます(当たり前)。困った所で Forum を検索して解決法を発見。

html entities with phptal

<title tal:content="structure title" />

"structure" keyword tells PHPTAL that you've done all the escaping you want to do on the value. Nothing will be escaped, including <.

というわけで正解は structure というキーワードをつけると良い、でした。そしてコードは以下のようになります。ちょと長いがまぁ、tricky なことしてるので。PHPTAL 自体のせいではありませぬ。というか、むしろこういうのは別に、例えば JSON を挿入するとかでやった方が良いのかも。

<span tal:define='global dquote string:"' />
<span tal:define="global comma string:," />
var array = new Array( <span tal:repeat="each obj/array" tal:omit-tag=""> <span tal:content="structure dquote" tal:omit-tag="" />   <span tal:content="each/path" tal:omit-tag="" />   <span tal:content="structure dquote" tal:omit-tag="" />   <span tal:condition="not: repeat/each/end" tal:content="structure comma" tal:omit-tag="" /> </span> ); </script>

以下のコードは、PHPTAL がエラーを出します。っていうか当たり前ですが。。)

<span tal:content='"'/>

あなたの知らない PHP 5つの秘密。

Date
2006-07-16 (Sun)
Category
Links | Translation | Trivia | php

以下は、5 Things You Probably Didn't Know About PHP By Gregory Szorcのいい加減訳です。


?> はオプション

<?php
...
?>

たいだい上のように PHP のコードを書くと思うけれど、最後の ?> は実は書かなくてもいい。少なくとも PHP 5.1 では動く。問題点はあるけど、XML なんかの出力には、?> 以降に(意図的でない)空白行が入ると問題を引き起こす(こともある)ので、終了タグを書かない。

他言語を PHP 中に埋め込むことができる

Java, Perl, Python.NET なんかは PHP から実行できる。Java Inteface はとってもクール。

配列へアクセスする方法で、オブジェクトも扱える

Standard PHP Library (SPL) を使えば、オブジェクトをあたかも配列のように扱える。 例えば ArrayObject を使うと以下のようなことが出来る。

$object = new MyObject();
$object['name'] = 'Hello World';

あるいは

$object = new MyObject();
foreach ($object as $k=>$v) {
	echo "$k = $v\n";
}

など。

require() は require_once() より速い

(これは PHP 5.2 までの話だけれど、)require_once でなく require を使った方が、アプリケーションが大きくなればなるほど、相当速くなる。require_once は呼ばれるたびに余分な system call が走って重複がないことを確認するから。「じゃぁどうやって重複がないことを保証するわけ?」「まだ require_once なんか使ってんのがダセーの。__autoload をうまく使え。」もし SPL が入ってるなら、spl_autoload_* を使えば複数の autoload ができる。より詳しい内容は Zend Framework ML のこのスレを読もう。

PHP 5.1.0 未満を使ってる奴はアフォ

PHP 5.0.x ブランチはもうメンテされてない。PHP 4.x ブランチは死んでる。いかに理由を挙げるから、まだ移行してない奴はするように

  • たいていの PHP4 で書かれたコードは動く。動かないとか言ってたのは FUD。開発者は互換性確保のためによくやってるから、まだ PHP4 を動かしてる奴は PHP 4 から PHP 5 への移行: 下位互換性のない変更点 を見ながらコードを grep するように。
  • PDO 使え! SQL を別ハンドラにかく必要なくなるし、SQL Injection 対策もばっちりになる。
  • Standard PHP Library (SPL) は今まで PHP で考えられかったすごいこと。
  • いくつかの人気アプリが PHP5 への以降をすませた。例えば MediaWiki 1.7
  • PHP5 のオブジェクトモデルは他のオブジェクト指向言語によく似てる。例えば Java とか。PHP5 はアクセス修飾子もあるし、抽象クラス、インターフェイス、それにマジックメソッドもある。

以下感想。

僕は PHP の parse の処理を考えると、閉じタグがいらない理由はなんとなくわかるけど、場合によりけりだと思うなぁ。Class 定義の書いてあるファイルとか、人に見せるコードはちゃんと閉じたい。直接関係ないけれど、始まりは <?php でするのは大賛成です(できれば short-open-tag を php.ini で off にして)。

他言語埋め込みは、僕にはとりあえず必要性がないのでよくわかりません。

SPL の ArrayObject は、Greg 曰く database query result とか LDAP directory queries によく使って便利だとか。ちょうど昨日も書いたけど、SPL は面白そうなので今度触ってみよう。

PDO 使えってのも、いいんだけど、『PDO 使えば SQL Injection 対策になる』ってのは危ないね。ちゃんと使えば、SQL Injection を起こすコードを書きにくくなるってだけだし。

Iterator Pattern

Date
2006-07-16 (Sun)
Category
Design Pattern | php

もう今更ですが、PHP 再入門というか、Object 指向再入門してみようと思い立ちました。だいぶ前に買って一度 java でやったのですが、PHP でやってやろうと。

目次通りにやって行くつもりなので、初回はやや簡単ですが、Iterator です。

最初にちょっとだけ詰まったのが、関数の戻り値をどうやって参照で渡す方法でした。もちろん本書では Java でやってるので、値を渡す時は参照が基本だけれども、PHP はそうではないので明示してやる必要があります。マニュアル以下のページに書いてあります。

リファレンスを返す

簡単にまとめると

  • 変数への参照での代入
    $copy =& $originalObject
  • 関数の引数を参照で渡すことを明示
    function foo(&$var)
    {
    	$var++;
    }
  • 関数の戻り値を参照で返すことを明示
    function &foo( $param )
    {
    	/* ... コード ... */
    	return $bar;
    }

でした。

あと Iterator.php のコード中にでてくるインターフェイス名が YIterator となっているのは、PHP が内部的に Iterator というクラスを定義しているからです。マニュアルを見て発見したですが、CXLV. Standard PHP Library (SPL) 関数 というクラスライブラリが提供されているのですね…スゴいそろってるじゃん。。

SPL-StandardPHPLibrary Class List

foreach を使った時に、iterate されないクラスメンバとかはこれらを使うと設定できるみたいでした。今回のデザインパターン入門が終わったら見てみたいと思います。

Continue reading

JSON

Date
2006-05-06 (Sat)
Category
Web | javascript | php

しばらく開いてしまいました。来週、ITP の Spring Show にて、Makiko Saito 氏、Sonali Sridhar氏と共同で、taggit.in というプロジェクトを出展します。平日夜ですが、お時間のある方は是非。僕もいます。その中から、いろいろ書いておきたいことはあるのですが、詳しくは発表後

製作メモとして、今回は JSON の話。

今回のプロジェクトは、いろんなページから、taggit.in にアクセスして、データを引き出す、というのものなので、AJAX の XMLHttpRequest は domain 制限により使えません。という訳で、data は JSON にして受け渡しています。サーバ側では、Hawk 氏の JSONEncoder を使って、クライアント側では、本家にある詠み人知らずの JSON.js を活用させてもらっています。

そこで一つ問題があったのが、Single Quote (x27) の扱い。仕様上 Single quote は escape しなくていい感じですが、実際しないと困ります。例えば…

var container = JSON.parse('{"a":"hello","b":"My Uncle's house."}');

parse は String を受け取るから、Single Quote か Double Quote でくくる必要があるし、Double Quote は JSON で使われてるし…結局、少し JSONEncoder 改造しました。というか、スゴい簡単だった。69行目 前後に始まる “ $this->_transTable = array( ” に "\x27" => "\'" を一つ追加しただけ。array 全体を以下に。

$this->_transTable = array(
	"\x08" => '\b',
	"\x09" => '\t',
	"\x0A" => '\n',
	"\x0C" => '\f',
	"\x0D" => '\r',
	"\x22" => '\"',
	"\x27" => "\'",
	"\x2F" => '\/',
	"\x5C" => '\\\\'
);

何か僕の知らない深遠な理由があるのかな? Single Quote を Escape しないのには…

Mac OS X update と php

Date
2006-04-16 (Sun)
Category
Mac OS X | php

もうしばらく前ですが、Mac OS X 10.4.6 update を当てました。そうなるんじゃないかと思ってはいたのですが、削除したはずの php 4系がまた新たにインストールされていました。
以前のエントリに書いてある通り…

  • /usr/lib/php
  • /usr/include/php
  • /usr/bin/php*
  • /usr/bin/pear*

をどけます。ただこれだけでは、pear が動かないので、

ln -s /usr/local/lib/php /usr/lib/php

とする。メモ。

Dreamhost で PEAR ローカルコピーを作る

Date
2006-03-18 (Sat)
Category
php

前のエントリの続きです。)PEAR のローカルコピー、つまり展開先を指定して PEAR のファイルをとってくる方法を解説します。Dreamhost と唱っていますが、Terminal アクセス(ssh)のあるレンタルホストならどこでも同じように出来ると思います。

前々から、何処かで記事を書いた気がする、と思っていたけれど、この blog 内には見当たらないし。ま、実際書くほども無い位簡単ではある。しかし、今回、この サイトのある dreamhost で試してみたら…

> pear -s -c ~/.pearrc -d doc_dir=~/pear/docs -d ext_dir=~/pear/ext \
       -d php_dir=~/pear/lib -d data_dir=~/pear/data -d test_dir=~/pear/tests \
       -d cache_dir=~/pear/cache -d bin_dir=~/pear/bin
> pear -c ~/.pearrc install Archive_Tar
WARNING: channel "pear.php.net" has updated its protocols, use "channel-update pear.php.net" to update
Cannot install, php_dir for channel "pear.php.net" is not writeable by the current user

と出てうまく行かない…ググって見つけたのが此のサイト。

さくらサーバで PEAR を自由に使いたい場合

…此れ書いたの自分じゃん。。(微妙に間違ったことも書いてる気がするが、まぁいいや。)

どっちにしても、共有ホストにおける PEAR のローカルコピーのインストール をリンクしていて同じようにやれ、と書いてある。

それで僕がしたのは、まず

> pear -c ~/.pearrc channel-update pear.php.net
Retrieving channel.xml from remote server
Registry directory is not writeable by the current user

やっぱりダメらしい。

> wget http://pear.php.net/channel.xml

として、channel.xml をダウンロードして、~/pear/ に置いても無駄。さぁて。こう言うときは英語文献に当たるのがイイかも。というわけですぐ発見。

Installation of a local PEAR copy on a shared host

…要するに、日本語版の翻訳が古くて実情に合ってないわけです。やってみましょう。

> pear config-create /home/user/pear .pearrc
Configuration (channel pear.php.net):
=====================================
Auto-discover new Channels     auto_discover    
Default Channel                default_channel  pear.php.net
HTTP Proxy Server Address      http_proxy       
PEAR server [DEPRECATED]       master_server    
Default Channel Mirror         preferred_mirror 
Remote Configuration File      remote_config    
PEAR executables directory     bin_dir          ~/pear/pear
PEAR documentation directory   doc_dir          ~/pear/pear/docs
PHP extension directory        ext_dir          ~/pear/pear/ext
PEAR directory                 php_dir          ~/pear/pear/php
PEAR Installer cache directory cache_dir        ~/pear/pear/cache
PEAR data directory            data_dir         ~/pear/pear/data
PHP CLI/CGI binary             php_bin          
PEAR test directory            test_dir         ~/pear/pear/tests
Cache TimeToLive               cache_ttl        
Preferred Package State        preferred_state  
Unix file mask                 umask            
Debug Log Level                verbose          
PEAR password (for             password         
maintainers)
Signature Handling Program     sig_bin          
Signature Key Directory        sig_keydir       
Signature Key Id               sig_keyid        
Package Signature Type         sig_type         
PEAR username (for             username         
maintainers)
User Configuration File        Filename         ~/.pearrc
System Configuration File      Filename         #no#system#config#
Successfully created default configuration file "~/.pearrc"

うまく行ったっぽい。日本語版にも、英語版にもマニュアルに書いて無いですが、pear config-create して出るメッセージによると、bin_dir は ~/pear/pear に出来ている様なので .cshrc に以下を書き加えます。(Dreamhost は Linux ですが、tcsh がデフォルトシェルっぽい。もし bash を使ってるなら .bashrc に pear マニュアルに書いてある通りすること)参照: .cshrcのカスタマイズ

set path=($HOME/pear)

そして rehash して、

> mkdir pear
> chmod 766 pear
> pear install -o PEAR

とすると、PEAR, Archive_Tar, Console_Getopt が入ります。せっかくなので、XML_RPC も入れときましょうか。

> pear install XML_RPC

此れ以降、Terminal で pear とすると、このローカルコピーにファイルが追加されます。

あと dreamhost では php.ini を(自前ビルドしない限り)個別に用意するのはできないみたいなんで、以下を各スクリプトに追加?マジですか。。。

$old_ini = ini_get('include_path');
ini_set('include_path', $old_ini.':/PATH/TO/HOME/pear/pear/php');

ふぅむ。

<strike>実験用に別の PEAR ローカルコピーを作る</strike>

Date
2006-03-18 (Sat)
Category
php

例えば、普段運用に使ってる PEAR とは別に変な package を入れたいとか、Stable release と、CVS(の開発)版をわけたいとかそう言う時に、PEAR のローカルコピーを作るコトが出来ます。いつもどうすんだっけ?と思っていたので、とりあえずここにメモしておきます。

というか、全て此処に書いてある。

共有ホストにおける PEAR のローカルコピーのインストール

追記: ある種間違いが含まれていたので、別の記事に書きました。

mb_send_mail と SMTP の基礎知識

Date
2006-03-16 (Thu)
Category
php

最初に、実際の使用感とは異なるかもしれない low level プロトコルとして、SMTP を簡単に解説します。SMTP を用いるのに必要なのは以下の3情報です。

  • mail from:
  • rcpt to:
  • data

mail from とはその名の通り差出人情報で、rcpt to は receipt to の略。つまり受取人情報。(技術書などではこれらの二つをあわせて envelope と呼ぶらしい。)そして data がメールで伝えたい内容。

この2つの envelope (mail from と rcpt to)に入力していいのは、我々がメーラで目にするヘッダ情報(つまり“From: 鈴木 一郎 <ichiro@suzuki.com>”など)ではなく、素のemail アドレスだけ、というのは考えれば当たり前ですが、僕の直感とは少し異なっていました。

では我々がメーラで目にするヘッダ情報はどこにあるのでしょう?これは3つめの data のなかにあります。この data として送られる内容の、始まりから数えて最初の により前が message header と呼ばれる“我々がメーラで目にするヘッダ情報”。それ以降全ては message body となり、普段、メーラで見る内容です。

message header 内で日本語を扱う場合、慣習的にほぼ必ず、ISO-2022-JP で文字構造化(character set)してそれを Base64 で可視符号化(encoding method)します。(RFC による標準定義はもちろんあるが、慣習的にほぼ必ずとしたのは、他のオプションもがあるけれども、あまり使われていないようだから。)

詳しくは以下の RFC を参照のこと
RFC 2076: 一般的に使われる message header について
RFC 2047: message header 内での非ASCII 文字の使用について

(正確には SMTP がプロトコルで中身を規定したのが MIME ですね?此処ではプロトコルエンジニアになるのが目的ではないのでとりあえず割愛します。)

さて漸く本題の php に話を戻します。日本語のメールを送りたい時に一番使われるのが mb_send_mail() でしょう。これは上記の問題の多くを隠蔽してくれます。それゆえ挙動がたまに不可解。調べました。

mb_send_mail() は以下の5つの引数を受け取ります。最初の3つが必須であとの2つは optional。

bool mb_send_mail (
	string $to,
	string $subject,
	string $message [, 
	string $additional_headers [, 
	string $additional_parameter]] 
)

(以下で断り無くエンコードと言った場合は、「文字構造化(character set)してそれを Base64 で可視符号化(encoding method)」することを意味します。)

$to は受取人情報を示します。message header に入るべき内容を受け渡すと、関数側でemail address 文字列を自動的に抜き出して rcpt to として MTA (Mail Transfer Agent、例えば sendmail や postfix) に伝え、渡された内容を直接を message header に挿入します。ただしマニュアルページにあるように、エンコードはしません。ので、日本語を使いたい場合は、mb_encode_mimeheader() すべきです。

$subject は message header の件名(Subject)を示します。マニュアルに書いていませんが自動的にエンコードされます

$message は message body を示します。当然ですがエンコードされません

オプションの $additional_headers は、上述の以外の message header を(\n 区切りで)いくらでも挿入可能。通常メーラは複数のヘッダを見つけた場合、下にあるほうを優先するようなので、実は Subject を挿入し上書きするのも可能です。ただしここで入力した内容はエンコードされません

最後の $additional_parameter は MTA に引き渡す特別な option です。詳しくは terminal で man sendmail してほしいですが、よく使われるのは “-f foo@example.com” でしょうか。僕の例では、FreeBSD上、php を apache 経由で起動し mail の送信をした場合、MTA に伝わる mail from は unix username が引き渡されていました。つまり apache の起動 user ですから www です。それでは error message が存在しない www ユーザに帰って困るので、前述の "-f foo@example.com" を使いました。


本当はちゃんとコードも載せるつもりでしたが、眠いので省略します。リクエストがあれば。PEAR のメールクラスなんかも本当は調べたい。。

Update log: Chap. 2 --MySQL backup

Date
2005-11-13 (Sun)
Category
MovableType | Tech | Web | php

大した量ではないけれど、無くなったり、文字化けしたりしたら悲しいので、慎重に行きたい database の backup 。MySQL 自体もアップデートしたいので、これを機に db ファイル群全部再構築しようと言う魂胆。

結構良くある、と思うんだけど、MySQL は良く解ってないけど、MovableType のバックエンドでは動かしてて、そろそろ理解も深めてアップデート、なと人のために…なるかもしれません。

Continue reading

Update log: Chap. 1 --preface and MySQL on Mac OS X

Date
2005-11-13 (Sun)
Category
MovableType | Tech | Web | php

長らくほったらかしだった、本サーバーのアップデートを、久々の3連休にやってみようと思い立ち、非常に苦労した、その log です。完璧自分用備忘録。

まずすべきことを考えた。

MySQL Server アップデート
MySQL Database 再構築
Apache アップデート

この3つが大きな題目。

Continue reading

Return to Page Top