Entry: PHP勉強会に行ってきた
PHP勉強会に行ってきた
土曜日にPHP勉強会に行ってきた。
勉強会系は初参加。ああ、こんな面白い世界もあるのかというカルチャーショックを受けました。仕事と無関係に、寝ても覚めても開発してそうな人たちがそこにいました。
もくもくと開発をして最後に成果発表会。プレゼンとか大の苦手なのでちょっと緊張しましたが、みなさん暖かく見守ってくれた。
今回は、tailコマンドでログを監視しながら開発する際に、PHPの構造体を表組で出力するライブラリを作りました。別のデバッグ出力用ライブラリで生成されたHTMLスニペットをw3mに丸投げすることで簡単に表組っぽいテキストを生成するという手抜きアイディアで好評いただきました。
まだ未完成ですが、完成したらOpenPearにアップしたいと思います。
Entry: ブックマーク
Entry: IEでチェックした
IEでチェックした
Macに転向してIEでのチェックができなくなったので、仕方なくウェブベースのレンダリングサービス「netrenderer」を使ってIEでのデバッグ。
酷かった。レイアウト崩れまくりでした。そうか、IE6はCSSの子セレクタ非対応だったっけ。あと、position:absoluteの要素の親要素にwidthを指定しないとぶっ飛ぶバグの対策。ここまでやるのに1時間もかかった。あと、ネットでDLしたアイコンが透過pngなので背景がおかしい。これも追々直さなければ。やっぱりwindows環境ないと無理だなあ。
Bootcampのベータが期限切れになって新規インストールできないっぽいので、Leoperd買わないとならない。金ないからしばらく先だなあ。
Entry: PHPデバッグ手法2
PHPデバッグ手法2
先日、HTMLをプレーンテキストに変換する方法を探してるということを書きましたが、w3mというテキストブラウザを同僚に教えてもらったので解決しました。定番のテキストブラウザのようですが、「テキストブラウザ」というジャンル自体を始めて知ったのです。勉強不足。そして、w3mは僕のdebianには入ってませんでしたのでapt-getしましたが、会社の、ここでは絶対使わないだろうっていうサーバーでも何故か必ず入ってるようなので、入ってることを前提にしてもいい世の中かも知れません。ちなみに、僕のサーバーにはlessコマンドが入ってないってことを同僚に呆れられました。
で、w3m。ブラウザなんですが、-dumpオプションをつけることで出力を文字列で受け取ることもできました。また、通常はURLをオプションで渡すようですが、標準入力があればそれをソースとすることもできました。なので、PHPからは、
$html = print_a($var, 'return:1'); $text = `echo "$html" | w3m -dump -I EUC-JP -O EUC-JP -T text/html`;
とすると、変数の構造を表組みのまま、プレーンテキストで得られます。これをログに書き出せば、tailコマンドで監視できます。ちなみに、文字コードをEUCにしているのは、UTF-8だと「East Asian Ambiguous」が原因でレイアウトが崩れるためです。
そんな訳で、全体的には下記のような感じでデバッグ環境が整いました。PHP歴2週間なんで、突っ込みどころは満載です。
ErrorHandle.php
デバッグ用のログを書き出すクラス。エラーエントリをスタックしておいて、後でまとめてログ出力。また、エラーエントリを配列で取り出せるので、ユーザー向けのエラー管理にも使えます。
<?php
class ErrorHandle {
var $filename;
var $logid;
var $entry;
// -----------------------------------------
// Constractor
// -----------------------------------------
function ErrorHandle($args) {
$this->filename = $args['filename'];
$this->logid = $args['logid'];
}
// -----------------------------------------
// Stack entry
// -----------------------------------------
function stack($args) {
// default postion
$caller = debug_backtrace();
if (isset($caller[1]['class']) and isset($caller[1]['function'])) {
$method = sprintf('%s::%s()', $caller[1]['class'], $caller[1]['function']);
$at = sprintf('%s line %d, during %s process',
$caller[0]['file'], $caller[0]['line'], $method);
}
else {
$at = sprintf('%s line %d', $caller[0]['file'], $caller[0]['line']);
}
$args = array_merge(
array('str' => NULL, 'level' => 0, 'at' => $at, 'multiline' => 0), $args);
if ($args['str']) {
// at strings
$at_str = ($args['at']) ? ' at '. $args['at'] : '';
$num = $this->count();
$outstr = $args['str'];
if (! $args['multiline']) {
$outstr = preg_replace('/[¥n¥s]+/', ' ', $args['str']);
}
$this->entry[$num]['str'] = $outstr. $at_str;
$this->entry[$num]['level'] = $args['level'];
}
}
// -----------------------------------------
// Number of error entry
// -----------------------------------------
function count($args = array('level' => NULL)) {
$args = array_merge(array('level' => NULL), $args);
$count = 0;
if (count($this->entry) > 0) {
foreach ($this->entry as $hash) {
if ($args['level'] and $hash['level'] != $args['level']) {
continue;
}
$count++;
}
}
return $count;
}
// -----------------------------------------
// Output the logs and exit
// -----------------------------------------
function end($args = array()) {
// default postion
$caller = debug_backtrace();
if (isset($caller[1]['class']) and isset($caller[1]['function'])) {
$method = sprintf('%s->%s', $caller[1]['class'], $caller[1]['function']);
$at = sprintf('%s line %d, during %s process',
$caller[0]['file'], $caller[0]['line'], $method);
}
else {
$at = sprintf('%s line %d', $caller[0]['file'], $caller[0]['line']);
}
$args = array_merge(
array('str' => NULL, 'level' => 0, 'at' => $at), $args);
$this->stack($args);
$this->log();
exit;
}
// -----------------------------------------
// Returns error array
// -----------------------------------------
function getArray($args = array('level' => NULL)) {
$str_array = array();
foreach ($this->entry as $hash) {
if ($args['level'] and $hash['level'] != $args['level']) {
continue;
}
array_push($str_array, $hash['str']);
}
return $str_array;
}
// -----------------------------------------
// Output to log file
// -----------------------------------------
function log($args = array('level' => NULL)) {
$total_count = $this->count($args);
if ($total_count < 1) {
return $this;
}
$num = 0;
if (! $fhandle = fopen($this->filename, 'a')) {
die("Log File ". $this->filename. " open failed");
}
foreach ($this->entry as $hash) {
if ($args['level'] and $hash['level'] != $args['level']) {
continue;
}
$level = " LV". $hash['level'];
if (preg_match('/¥n/', $hash['str'])) {
$hash['str'] = "¥n". $hash['str'];
}
$result = sprintf("[%s%s] %s", $this->logid, $level, $hash['str']);
if ($total_count > 1) {
$result = " -> $result";
}
$status = '';
if ($num == 0) {
$date = date('D M d H:i:s Y', time());
$status = sprintf("[%s] [client %s]", $date, $_SERVER['REMOTE_ADDR']);
if ($total_count > 1) {
$status .= "¥n";
}
}
$outstr = implode(' ', array($status, $result));
fputs($fhandle, $outstr. "¥n");
$num++;
}
fclose($fhandle);
return true;
}
}
?>
debugPrint.php
debuglib.phpのprint_aメソッドの結果をテキストで取り出す。print_a($var)と書く代わりにdebugPrint::structure($var)と書く。ここでw3mが登場。w3mが見つからない場合はprint_rの結果を出力。この辺のunixコマンドの使い方も突っ込みどころです。クラスにした意味は特にない。
<?php require_once 'debuglib.php'; class debugPrint { function structure($var, $locale = 'UTF-8') { $html = print_a($var, 'return:1'); $html = preg_replace('/¥n|¥t/', '', $html); $html = preg_replace('/<table /', '<table border="1" ', $html); $html = preg_replace('/<style .*<¥/style>/', '', $html); if ($text = debugPrint::_html2text($html, $locale)) { return $text; } return print_r($var, TRUE); } function _html2text($html, $locale) { exec("which w3m", $command, $status); // w3m command not found if ($status != 0) { return; } $html = mb_convert_encoding($html, "EUC-JP", $locale); $return = `echo "$html" | $command[0] -dump -I EUC-JP -O EUC-JP -T text/html`; $return = mb_convert_encoding($return, $locale, "EUC-JP"); return $return; } } ?>
で、使い方。
<? set_include_path(get_include_path() . PATH_SEPARATOR . './lib'); require_once 'ErrorHandle.php'; require_once 'debugPrint.php'; $Errhandle = new ErrorHandle(array('filename' => './log/error.log', 'logid' => 'hoge')); $test_data = array( "company" => "自由民主党", "name" => "小泉純一郎", "birth_month" => 1, "birth_day" => 8, "hobby" => array("opera","X JAPAN","Elvis Aaron Presley") ); $Errhandle->stack(array('str' => debugPrint::structure($test_data), 'multiline' => 1)); $Errhandle->log(); ?>
結果。

Entry: PHPデバッグ手法
PHPデバッグ手法
- PHPのデバッグに便利な関数 - 基本編
こんなのやってらんねー。 - PHPのデバッグに便利な関数 - 応用編
おお、テーブルかっけー。 - Google Answers: PHP HTML to Text Conversion
この人が同様のライブラリを探しているが、よい解答なし。 - PHP Class: HTML to Plain Text Conversion
あった!けど、テーブルがうまいことコンバートされないぞ。<ul>は表示できた。
PHPのデバッグ手法を考える。なんかGUIなデバッガもあるようですが、それらがどんな仕組みなのかは今度調べるとして、とりあえず、ブラウザに変数を書き出すようなデバッグの代替案を考える。
PHPに限らず、ウェブアプリの開発ではブラウザ出力でデバッグするのが当然のようです。僕のここ数年の趣味の開発では、デバッグ出力はひたすらファイルに書き出して、tailコマンドで監視するようなスタイルです。サービスイン後も継続して監視できるのでよいです。このモジュールを早速、PHPに移植。
PHPには結構、ブラウザデバッグのライブラリが豊富で、構造体な変数を、小ギレイなテーブルで表示してくれたりするんですね。これ、tailで見たいな、などと思う。変数表示のライブラリはいくつも見つかったので、あとはHTMLをプレーンテキストに変換するライブラリがあれば解決。
要は、ブラウザのレンダリングを等幅フォントの文字だけでエミュレートしてほしいわけです。今日、半日くらい探したら一個だけ見つかったけど、テーブルはまともに表示されませんでした。HTMLはもちろん、CSSまで認識しちゃったり、ウィンドウサイズまで変えられるようなのをギャグで作っちゃってる人がどっかにいると思ったんだけどなあ。誰か知りませんか。Perlでもいい。自分で作るとなると骨が折れるし、それなら、そもそもHTMLをかます必要もないし。
Entry: ブックマーク
Entry: Angry Chair
Angry Chair
最近、Angry Chairというソフトを常駐させています。
以前、携帯サイトのデバッグ時にUAを詐称するためによく使ってたのですが、久々にひっぱり出してきたらなかなか便利です。ブラウザで自サーバにアクセスするときだけUAに目印を付加するとか。これで、会社で仕事サボって自サバにアクセスしてるときに、同僚が同じIPでアクセスしてきても区別ができるのだ。なんてことを書いたら警戒して誰も来なくなるか。
あとReferrerを詐称するとか。Referrerって、何となく絶対的な存在だという印象をもっていました。リンク元をサーバに通知するという、一見、ユーザに何のメリットもない、場合によってはデメリットでしかない機能を、すべてのブラウザが実直に実装しているのには、何か想像を絶する大人の事情があるのだろうから、非通知や詐称なんてもってのほかだ、というオボロげな発想です。しかし、最近、NTT DoCoMoの端末は全てReferrerが通知されてないということに気付き、思い込みが解けたので試しに詐称してみたのでした。何のことはない、HTTPをカジるまでもなく、詐称できることぐらい容易に判断できただろうに。
ところで、RFC2068には
利用者はRefererフィールドが送られるかどうかの選択ができることを強く推奨する
ってあるけど、IEやFirefoxには設定項目が見当たらないぞ。はて。やっぱり大人の事情?
Entry: Firefoxでスクリプトのデバッグ
Firefoxでスクリプトのデバッグ
Firefoxをインストールしてみた。目的はJavascriptのデバッグ。Firebugというアドオンを入れるとこんなことができるらしい、ということで入れてみて、大いに感動。ブラウザ上で、表示中のページソースや、外部のscriptファイルのソースへも簡単アクセス!HTMLソースのDIVタグなどにフォーカスすると、ブラウザ描画上でブロック要素をハイライトしてくれる。ページ作る時に、* {border:1px solid #f00}なんてして、レイアウトを逐一確認する必要がなくなるのかも。その他にも、ページの読み込み時に、そのページに付随する画像やCSSなどの全ファイルの読み込み所要時間をガントチャートみたいに表示してくれ、それぞれのファイルについて、リクエストヘッダとレスポンスヘッダまで確認できる。
そして、もちろん、当初の目的のSCRIPTデバッグも申し分ない。ブレークポイントを設定したり、変数の値を確認するのもお手の物。alertなんて使ってデバッグしていた頃が懐かしい。で、とりあえず、ホームリンクのフラッシュ効果が、Firefoxではうまく動作してないらしいことがわかった。どうやら原因は、style.colorの値がIEと違ってrgb(0,0,0)なんて書式になってるせいらしい。
(2007.4.11追記)
という訳で、ブログタイトルが無駄に光るスクリプトを、Firefoxでも正常動作するよう修正。これはもうちょういマシな書き方ありそうだなあ。
var flash_tid; function flash(n, target_rgb, start_color, target_id, count) { var color = new Array(); var target = new Array(); var margin = new Array(); var i; if (count == null) { count=0; clearTimeout(flash_tid); } var target_obj = document.getElementById(target_id); if (start_color == null) { if (target_obj.style.color.indexOf('rgb') != -1) { color = target_obj.style.color .match(/rgb\((\d+),\s?(\d+),\s?(\d+)\)/, "i"); } else if (target_obj.style.color.indexOf('#') != -1) { color[1] = parseInt(target_obj.style.color.substr(1, 2), 16); //現在の色を検出 color[2] = parseInt(target_obj.style.color.substr(3, 2), 16); color[3] = parseInt(target_obj.style.color.substr(5, 2), 16); } } else { color[1] = parseInt(start_color.substr(1, 2), 16); //指定の開始色があれば設定 color[2] = parseInt(start_color.substr(3, 2), 16); color[3] = parseInt(start_color.substr(5, 2), 16); } target[1] = parseInt(target_rgb.substr(1, 2), 16); //指定の終了色を設定 target[2] = parseInt(target_rgb.substr(3, 2), 16); target[3] = parseInt(target_rgb.substr(5, 2), 16); if (count < n){ for (i = 1; i < 4; i++){ margin[i] = Math.round((color[i] - target[i]) / (n - count)); //1段階の差異を算出 color[i] = color[i] - margin[i]; if ((color[i] = color[i].toString(16)).length == 1) { color[i] = "0" + color[i]; } } target_obj.style.color="#" + color[1] + color[2] + color[3]; //色を出力 flash_tid = setTimeout( "flash(%1,'%2',%3,'%4',%5)" .replace('%1', n - 1) .replace('%2', target_rgb) .replace('%3', null) .replace('%4', target_id) .replace('%5', count + 1), 60 ); } else { target_obj.style.color = target_rgb; //終了色を出力(誤差調整用) } }
Entry: ブーツィーコリンズ
ブーツィーコリンズ
師走です。最近は遅刻をしないで定時にあがろうキャンペーンを実施中。夜もちゃんと2時までには寝る。
ブログシステムの改修は牛歩のあゆみなれど、着実に進行中。Perlのオブジェクト指向はいびつだとよく言われてるようですが、Perlだからこそオブジェクト指向だ、という側面も見えてきました。文字列も数値も一緒くたにされてしまうPerlなので、構造体を厳格に定義しとくと間違いが起きづらくてよい。
そして、近頃はとても買い物依存症な日々。主にお洋服なんか買っちゃったりするんですが、まあ、僕ってば、極端に慎重派なので、4~5店舗をチェックするという工程を、3回くらい繰り返してから買ったりするんです。お金はあまり使わないのですが、時と言う名の金を浪費している気がしてならない今日この頃。最近のターゲットはブーツ!最近だんだんイデタチがお兄さん風になってきたのが自分で気に入らないのですが、ブーツなんか履いたら尚更な予感。
というわけで、12月最初の日記。最低でも月に一回は書かないと、トップのカレンダーのデバッグにならないのだ。
Entry: なんかもう、オブジェクト指向2
なんかもう、オブジェクト指向2
テンプレート関数を呼び側からオブジェクトに食わせてみる。この方向でやってみようか。デバッグはあまりしていません。
=sample package main; print "Content-type: text/plain; charset=utf-8\n\n"; my $template = Template->new(); $template->set_delimiter(left => '<', right => '>'); $template->set_var(data => 'This is a sample data'); $template->set_func(length => \&tpl_func_sample1); $template->set_func(first_word => \&tpl_func_sample2); print $template->parse(str => q!data : <$data>;!. "\n"); print $template->parse(str => q!length : <&length($data)>!. "\n"); print $template->parse(str => q!first word : <&first_word($data)>!. "\n"); sub tpl_func_sample1 {return length($_[0]);} sub tpl_func_sample2 {return (split(/\b/, $_[0], 2))[0];} # --output-- # data : This is a sample data # length : 21 # first word : This =cut package Template; use strict; use warnings; use utf8; binmode(STDERR,":utf8"); use open IO => ":utf8"; ### ----------------------------------------- ### コンストラクタ ### ----------------------------------------- sub new { my $class = shift; my $self = {mother => undef, @_}; bless $self, $class; $self->{func} = {}; $self->{var} = {}; ### マザーインスタンスがあればデリミタ継承 if (defined $self->{mother}) { $self->set_delimiter(left => $self->{mother}->{left_delimiter}, right => $self->{mother}->{right_delimiter}); } else { $self->set_delimiter(); } return $self; } ### ----------------------------------------- ### テンプレート変数の定義 ### ----------------------------------------- sub set_var { my $self = shift; my %args = (@_); while ((my $key, my $value) = each %args) { $self->{var}->{$key} = $value; } } ### ----------------------------------------- ### テンプレート関数の定義 ### ----------------------------------------- sub set_func { my $self = shift; my %args = (@_); while ((my $key, my $value) = each %args) { $self->{func}->{$key} = $value; } } ### ----------------------------------------- ### テンプレート変数の問い合わせ ### ----------------------------------------- sub var { my $self = shift; my %args = (name => undef, @_); if (defined $args{name}) { return $self->{var}->{$args{name}} if (defined $self->{var}->{$args{name}}); return $self->{mother}->var(name => $args{name}) if (defined $self->{mother}); } return undef; } ### ----------------------------------------- ### テンプレート関数の問い合わせ ### ----------------------------------------- sub func { my $self = shift; my %args = (name => undef, @_); if (defined $args{name}) { return $self->{func}->{$args{name}} if (defined $self->{func}->{$args{name}}); return $self->{mother}->func(name => $args{name}) if (defined $self->{mother}); } return undef; } ### ----------------------------------------- ### テンプレートパース ### ----------------------------------------- sub parse { my $self = shift; my %args = (str => undef, file => undef, @_); my $tpl_str; ### テンプレートの選定 if (defined $args{str}) { $tpl_str = $args{str}; } elsif (defined $args{file}) { $tpl_str = $self->get_str_from_file(name => $args{file}); } else { die 'No template string found'; } my @scraps = split(/$self->{left_delimiter}(.+?)$self->{right_delimiter}/s, $tpl_str, 2); ### 手続き指定があったら if ((scalar @scraps) == 3) { my $output = $scraps[0]; my $parse_str = $scraps[1]; ### テンプレート変数を解釈 $parse_str =~ s/(?<!\\)\$([A-Za-z0-9:_]+)/\$self->var(name => '$1')/g; ### テンプレート関数を解釈 $parse_str =~ s/(?<!\\)\&([A-Za-z0-9:_]+)/\$self->func(name => '$1')->/g; ### エスケープを解釈 $parse_str =~ s/\\(\$|\&)(?=[A-Za-z0-9:_])/$1/g; ### テンプレート関数内で親テンプレートとして参照 local $Template::self = $self; ### パース結果を取得 my $result = eval $parse_str; $output .= $result if (defined $result); ### エラーがあればエラーを返す if ($@) { ($@ =~ /as a subroutine/) and die "Cannot parse a function in template line($scraps[1])"; die "$@ This error was in eval($scraps[1])"; } ### 残りの文字列を評価 $output .= $self->parse(str => $scraps[2]); return $output; } return $tpl_str; } ### ----------------------------------------- ### テンプレート文字列をファイルから取得 ### ----------------------------------------- sub get_str_from_file { my $self = shift; my %args = (name => '', @_); if (open(tpl_handle, $args{name})) { my $output = join('', <tpl_handle>); close(tpl_handle); return $output; } die 'template file not found'; } ### ----------------------------------------- ### テンプレート関数デリミタの初期化 ### ----------------------------------------- sub set_delimiter { my $self = shift; my %args = (left => '{%', right => '%}', @_); $self->{left_delimiter} = ($args{left} or '{%'); $self->{right_delimiter} = ($args{right} or '%}');; } return 1;
Subscribe to my RSS feed