2008-01-01 (火)
■ PukiWiki 用プラグイン geshi.inc.php, highlighter.inc.php Ver.1.2 を公開
あけましておめでとうございます。 今年もよろしくお願いします。
お年玉代わりに、PukiWiki の構文ハイライト用プラグインの新バージョンを公開します。 前バージョン からの変更点は以下の通りです。
- geshi.inc.php
- 外部スタイルシートを利用する機能を追加
- キャッシュの保存場所を cache/geshi ディレクトリに変更
- highlighter.inc.php
- スタイルシートを自動的に読み込むように改良
- キャッシュの保存場所を cache/highlighter ディレクトリに変更
- PEAR::Text_Highlighter 0.7.0 未満でも start オプションが効くように
動作チェックをしていたら、 PEAR::Text_Highlighter 0.7.0 だと表示がおかしくなることに気が付きました。 以下のバグが原因です。
1つ前のバージョンの PEAR::Text_Highlighter 0.6.9 だと正しく動作するので、 そちらを使うのが無難なようです。
(2008/01/02 追記)
PEAR::Text_Highlighter 0.7.1 がリリースされたので試しました。 上記のバグは直っていましたが、Version 0.6.9 までとは違う HTML を出力するようになっていたので、 思い切って Version 0.7.1 以降限定のプラグインに作り替えて、差し替えておきました。
2008-01-04 (金)
■ PHP をインタラクティブシェルとして使う
php -a ってやるとインタラクティブシェルとして使えるんだね。知らなかったよ。 --with-readline オプションを有効にした PHP 5.1 以上限定らしいけれど、こんな風に使える。
$ php -a
Interactive shell
php > for ($i = 0; $i < 5; $i++)
php > echo $i . "\n";
0
1
2
3
4
php > for ($i = 0; $i < 5; $i++) {
php { echo $i . "\n";
php { }
0
1
2
3
4
php > exit
$
phsh とか irhaco といった PHP で実装したシェルもあるらしいけれど、 PHP の組み込み関数の動作を確認したい場合なんかは、php -a も手軽でいいかも。
2008-01-08 (火)
■ IP アドレスを数値に変換してデータベースへ保存する場合の注意点
PHP や MySQL には、IP アドレスをドット表記の文字列と数値との間で相互に変換する関数がある。 ところが実は、PHP の ip2long() 関数は signed int な値を返すのに対し、 MySQL の INET_ATON(), INET_NTOA() 関数は unsigned int な値を前提にしている。 PHP 5.2.5 と MySQL 4.1 (4.1.11a-4sarge2) で試してみると、それぞれ次のような結果になった。
php > echo ip2long('192.168.0.1');
-1062731775
php > echo long2ip(-1062731775);
192.168.0.1
php > echo long2ip(3232235521);
192.168.0.1
php >
mysql> select INET_ATON('192.168.0.1');
+--------------------------+
| INET_ATON('192.168.0.1') |
+--------------------------+
| 3232235521 |
+--------------------------+
1 row in set (0.00 sec)
mysql> select INET_NTOA(-1062731775);
+------------------------+
| INET_NTOA(-1062731775) |
+------------------------+
| NULL |
+------------------------+
1 row in set (0.00 sec)
mysql> select INET_NTOA(3232235521);
+-----------------------+
| INET_NTOA(3232235521) |
+-----------------------+
| 192.168.0.1 |
+-----------------------+
1 row in set (0.00 sec)
mysql>
そのため、例えば ip2long() と INET_NTOA() を混ぜて使おうとすると、 signed と unsigned の変換が必要になったりして、ややこしいことになる。 PHP 限定、もしくは MySQL 限定ということでよければ、
- データベースのカラムは INT で定義しておき、PHP 側で ip2long(), long2ip() 関数を使って変換
- データベースのカラムは UNSIGNED INT で定義しておき、MySQL 側で INET_ATON(), INET_NTOA() 関数を使って変換
のどちらかの方法を採るのが良いと思う。
2008-01-11 (金)
■ csshover.htc を使うと 〜onhover といったクラスが裏で追加される
話の要点としては、 csshover.htc を使うとクラス名がいつのまにか変わったりするので注意しましょう、ということ。 といっても、これだけでは何のことやらさっぱり分からないと思うので、 今回はまったことを具体的に書いておく。
<div id="treemenu"> ...... <li class="collapsed"> ...... </li> ...... </div>
のような HTML を書き、treemenu のところに onclick を設定していた。 その後で、treemenu 内のどの要素がクリックされたのかを、
if (element.className == "collapsed") {
......
}
のように判別して処理していた。 ところが、スタイルシートに次のような設定を追加した途端、IE で動かなくなってしまった。
li.collapsed:hover {
background-color: #cdf;
}
クリックした時の element.className の値を調べてみると、
collapsed collapsedonhover
のように、元々の collapsed というクラスに加え、collapsedonhover というクラスがなぜか追加されている。 ところが、IE 以外のブラウザで試してみると、普通に collapsed という値だけが返って来た。
ここに来てようやく、csshover.htc を組み込んでいるのが原因じゃないかと思い至ったので、 試しに csshover.htc を外してみたところ、案の定動くようになった。 しかし csshover.htc は組み込んでおきたいので、結局、クラス名を判定している箇所を、
if (element.className.indexOf("collapsed") == 0) {
......
}
のように、前方一致で比較するように書き換えることで対処した。
と、まぁ、csshover.htc は有用なのだけれど、 結構こうやってだましだまし使わないといけない気がする。 最近公開された IE7.js を使う方がいいのかなぁ?
2008-01-15 (火)
■ 性別コードの規格 (ISO 5218, JIS X 0303)
性別のデータを保存しておく時に、0 と 1 で表せばいいかと考えていたが、 「不明」を示す値もあった方が良いことに気が付いたので調べてみた。 こういうのも規格があるんだね。
- ISO 5218 - Wikipedia, the free encyclopedia
- 0 = Not known
- 1 = Male
- 2 = Female
- 9 = Not applicable
- JIS X 0303
- 1 = 男
- 2 = 女
ただし、JIS X 0303 は廃止された らしく、 また ISO 5218 に同じ内容が含まれているので、ISO 5218 に合わせておけばいいか。
2008-01-17 (木)
■ メールアドレスの最大文字数
メールアドレスは最大何文字まで許されるのか?
RFC 2821 に書かれている規定はどうも分かりにくかったが、 おそらく上のページに書かれている通り 256 文字弱なんだと思う。 forward-path の最大長が 256 文字で、forward-path はメールアドレス+αだから、 データベースに保存する場合は varchar(255) で確保しておけば足りることになる。
でも例えば、255 文字ぴったりのメールアドレスを登録されたりすると、forward-path が 256 文字を超えてしまう。 だから、記入してもらう時には、もう少し制限をきつくしておく方が良いのだろう、多分。
2008-01-19 (土)
■ $_SERVER['REQUEST_TIME'] の使い道
PHP 5.1 以上限定だが、 time() より $_SERVER['REQUEST_TIME'] の方が速いと聞いていたので検証してみた。 ついでに、date() 関数の引数に $_SERVER['REQUEST_TIME'] を指定するのもやってみた。 PHP 5.2.5 で、10000000 回 (date() 関数の方は 1000000 回) 実行するのにかかった時間を測定している。
| 1st | 2nd | 3rd | |
|---|---|---|---|
| time() | 9.638 | 9.749 | 9.868 |
| $_SERVER['REQUEST_TIME'] | 4.877 | 4.881 | 4.869 |
| 1st | 2nd | 3rd | |
|---|---|---|---|
| date('Ymd') | 8.411 | 8.247 | 8.325 |
| date('Ymd', $_SERVER['REQUEST_TIME']) | 6.965 | 7.033 | 7.057 |
確かに $_SERVER['REQUEST_TIME'] を使った方が速い。date() 関数の場合もわずかだが速くなる。 といっても、time() を1回実行するのに 1/1000000 秒くらいしかかかっていないので、 別にどちらでもいい気がする。
ただ、time() は関数が実行された時刻なのに対し、 $_SERVER['REQUEST_TIME'] はリクエスト開始時刻なので、 スクリプト実行中に $_SERVER['REQUEST_TIME'] を何回呼んでも同じ値が得られる、 という点で使えるかもしれない。
一応、ベンチマークに使ったコードを載せておく。
(2008/01/21 追記)
elf さんから指摘 を受けたので、あらかじめ定数 REQUEST_TIME に保存しておいた場合の結果も含めて載せておきます。 ついでに空ループ 10000000 回に要する時間も。
| 1st | 2nd | 3rd | |
|---|---|---|---|
| time() | 9.638 | 9.749 | 9.868 |
| $_SERVER['REQUEST_TIME'] | 4.877 | 4.881 | 4.869 |
| REQUEST_TIME | 3.767 | 3.612 | 3.545 |
| (空ループ) | 1.708 | 1.762 | 1.728 |
また後できちんと書きます。
<?php
function microtime_float()
{
list($usec, $sec) = explode(' ', microtime());
return ((float)$sec + (float)$usec);
}
$time1_start = microtime_float();
for ($i = 0; $i < 10000000; ++$i) {
$time = time();
}
$time1_end = microtime_float();
$time2_start = microtime_float();
for ($i = 0; $i < 10000000; ++$i) {
$time = $_SERVER['REQUEST_TIME'];
}
$time2_end = microtime_float();
define('REQUEST_TIME', $_SERVER['REQUEST_TIME']);
$time3_start = microtime_float();
for ($i = 0; $i < 10000000; ++$i) {
$time = REQUEST_TIME;
}
$time3_end = microtime_float();
$time4_start = microtime_float();
for ($i = 0; $i < 10000000; ++$i) {
// empty loop
}
$time4_end = microtime_float();
echo $time1_end - $time1_start . "\n";
echo $time2_end - $time2_start . "\n";
echo $time3_end - $time3_start . "\n";
echo $time4_end - $time4_start . "\n";
<?php
function microtime_float()
{
list($usec, $sec) = explode(' ', microtime());
return ((float)$sec + (float)$usec);
}
$time1_start = microtime_float();
for ($i = 0; $i < 1000000; ++$i) {
$date = date('Ymd');
}
$time1_end = microtime_float();
$time2_start = microtime_float();
for ($i = 0; $i < 1000000; ++$i) {
$date = date('Ymd', $_SERVER['REQUEST_TIME']);
}
$time2_end = microtime_float();
echo $time1_end - $time1_start . "\n";
echo $time2_end - $time2_start . "\n";
2008-01-22 (火)
■ $_SERVER['REQUEST_TIME'] の使い道 補足
先日の記事は、 「$_SERVER['REQUEST_TIME'] って、あまり無理に使う必要ないんじゃない?」 という趣旨のつもりだったのだが、 elf さんからツッコミ を受けてしまったので若干補足。
スクリプトで$_SERVERを上書きするのは気持ち悪いので個人的には(もし関連する内容を使うなら)こうするだろうけど.
if (array_keys('REQUEST_TIME', $_SERVER) != true) { define('REQUEST_TIME', time()); } else { define('REQUEST_TIME', $_SERVER['REQUEST_TIME']); } // 以後定数「REQUEST_TIME」を使う
ベンチマークの結果からざっと計算すると、 time(), $_SERVER['REQUEST_TIME'], REQUEST_TIME の参照に要する時間は、8:3:2 くらい。 何回もこの値を使うなら、定数 REQUEST_TIME に保存しておく価値はある。 PHP のバージョンによらず使えるのもお得。
ただし、個人的にはこういう書き方はあまり好きじゃない。 REQUEST_TIME という定数がどこでどう定義されているのか、 (推測はできるけれど)いちいち確認しなければならなくなる。 それだったら、多少遅くても $_SERVER['REQUEST_TIME'] と直に書いておく方が、 意味が明確になると思う。
そういう意味では、
そこまでわかっていて何故こういう記述ができないのだろう.
if (array_keys('REQUEST_TIME', $_SERVER) != true) { $_SERVER['REQUEST_TIME'] = time(); }
こちらの書き方の方が好みかな。 PHP_Compat っぽくて。
(追記)
今、気付いたが、array_keys() は変だ。array_key_exists() か isset() でないと。 いや、むしろこうか?
if (version_compare(PHP_VERSION, '5.1.0', '<')) {
$_SERVER['REQUEST_TIME'] = time();
}
2008-01-25 (金)
■ Piece_Right の Length バリデータを配列&マルチバイト文字に対応させる
必要に迫られたので対応させてみた。
<?php
require_once 'Piece/Right/Validator/Common.php';
class Piece_Right_Validator_Length extends Piece_Right_Validator_Common
{
var $_isArrayable = true;
function validate($value)
{
if (is_array($value)) {
foreach ($value as $val) {
if ($this->validate($val) === false) {
return false;
}
}
return true;
}
if ($this->_getRule('multibyte')) {
$length = mb_strlen($value);
} else {
$length = strlen($value);
}
$min = $this->_getRule('min');
if (!is_null($min)) {
if ($length < $min) {
$this->_setMessage('min');
return false;
}
}
$max = $this->_getRule('max');
if (!is_null($max)) {
if ($length > $max) {
$this->_setMessage('max');
return false;
}
}
return true;
}
function _initialize()
{
$this->_addRule('min');
$this->_addRule('max');
$this->_addRule('multibyte', false);
}
}
?>
ただし、
- マルチバイト版は別のバリデータに分けた方が良い?
- PHP 5 以上になってしまうが iconv_strlen() の方が良い?
(Zend Framework のバリデータを見たら、一応 iconv_strlen() を使っていた) - multibyte オプションのデフォルトは false で良い?
- multibyte オプションでなく encoding オプションでエンコーディングを指定する方が良い?
といった辺りは、あまり考えていない。 でも、エンコーディングの変換は必要ならフィルタの方で行う、と考えれば、 この実装でもいいのかな。
2008-01-30 (水)
■ Zend Framework 1.5.0 Preview Release がリリース
Zend Framework 1.5.0 のプレビューリリース版が出ていた。 正式版が出るまでに、まだ修正がかなり入ると思うが、主な変更点は以下のようになっている。
- New Zend_Form component with support for AJAX-enabled form elements
- New action and view helpers for automating and facilitating AJAX requests and alternate response formats
- Infocard, OpenID, and LDAP authentication adapters
- Support for complex Lucene searches, including fuzzy, date-range, and wildcard queries
- Support for Lucene 2.1 index file format
- Partial, Placeholder, Action, and Header view helpers for advanced view composition and rendering
- New Zend_Layout component for automating and facilitating site layouts
- UTF-8 support for PDF documents
- New Technorati, SlideShare, and Remember the Milk web services
Zend_Form やヘルパーの辺りにかなり手が入っているようなので、 時間を見つけて試してみたい。
