遊星ゲームズ
FrontPage | RSS


プログラム
 プログラム

 作ったプログラムを置いてみる場所。
 たしかPHPの専門家だったと思うけど好きじゃないので(ぉ)、人を馬鹿にした書きなぐりPHPが多くなりそう。
 いやほんとに謙遜でも冗談でもなく書きなぐりなので気をつけてください。

2009/07/16 03:36

PHP フレームワーク作りのポイント
 プログラム

 PHPにはいろんなフレームワークがあって、その中のどれが一般的!というのはたぶん、あまり決まっていない。たくさんあるし、どれも長所と短所がある。選ぶのが難しい。
「別にフレームワークなんかいらない」というのも一つの選択肢だし(そしてそれがけっこう採用されている)、フレームワーク自作!というのもいいだろう。

 個人的な現状での結論は、フルスクラッチだ。なぜなら、既存のモノを選ぶと高級すぎるから。できるだけ機能の少ないフレームワークをまず作る。
 PHPで仕事をする以上、PHPの技術者が集まってるはずで。しかし標準的なフレームワークは決まっていないから、みんな共通で知っているのはネイティブPHPだけなんである。そんな状況で、フレームワーク独自の記法とか、大量のライブラリのAPIをみんなに読んでもらうとか、そんなことは現実的じゃない。
 誰もが知っているフレームワークとかが、あるなら考えるけど。Zend Frameworkとかがそういうものになってくのかもしれないけど。あれはそんなに悪くなさそうとは思うんだけど、なんか好きじゃない(笑)。

 個人的な意見ですよあくまで。経験も浅いし。
 経験十年!なんて人たちの意見と食い違っていることもあると思いますが。そのときは、どれが説得力あるか判断するのは読者諸氏です。


 フレームワークを選ぶ/改造するときは、次にあげるような基準で考えるといい。と思う。または、自作するときは次のような機能を作る。

 まず一番大切なこと。

  • グローバルスコープを使わない
     あまりにあたりまえのことだ。変数だろうと関数だろうと、グローバルにはなにも置いてはならない。置いていいのはクラス定義だけ。
     定数も同じだ。define文はできる限り使わないようにする。
     グローバルをひとつでも使うと、そこから汚染が拡がっていくことになる。できる限り早い段階で撲滅しておきたい。
     選んだフレームワークやライブラリがグローバルを使っているならしかたないけど。それ以外では、絶対に使わないようにしなければいけない。
     ただ、これをフレームワークの選定基準にしてしまうと、けっこうな数が落第になってしまうわけだが(笑)。
  • ネイティブPHPを活かす
     フレームワークを使う以上、本当なら、できるだけフレームワークが用意したモノを使うべきだ。と思う。
     でもたとえば「Util::dateFormat()」という関数があったとき。それはPHP標準のdate()関数じゃダメなのか?
     そもそも日付の整形処理を書くときに、このUtil::dateFormat()を、APIリファレンスの中から発見できるのだろうか? 開発メンバー全員が?
     はっきりいってムリ。
     本当なら、たとえまったく同じ機能だとしても、ラップしてあることには意味がある。本当なら、採用したフレームワークが用意したモノをできるだけ使うべきなんだけど。現実的にムリなんだからしかたない。
     フレームワークにはたいてい、いろんなライブラリが付属している。なぜなら、そうしておけば「これもできます!」といえるから。
     客寄せのためなんである。
     というか一般に「あれもこれもできます!」系のものでないと、選ばれないんである。残念なことに。
     だから、有名な奴はたいてい、過剰だ。
     わたし自身はわりと、リファレンスを必死で読むほうだ。でもしょうじきなところ、いつも徒労感がある。いっそのこと、それらを使わないことにしてしまっていい。
  • 配列を使わない
    amazon フレームワークには、たとえばO-Rマッピング用のライブラリとかがついているかもしれない。でも、それを使う前にちょっと待とう。find()メソッドが、2次元の連想配列を返していないだろうか?
     そうなっていたら、その部分は自作したほうがいい。連想配列ではなく、データを格納するためにはオブジェクトを使うべきだ。
     たとえ全部publicメンバだったとしても、オブジェクト指向には、クラスで宣言していないメンバを追加したくないインセンティブが働く。いっぽう連想配列は、あとから勝手にいろいろ追加されてしまう。
    「基本型への固執」(『リファクタリング』)は本当に、最悪の病理だ。汚染はあっという間に拡がる。PHPは特に。
     アクティブレコードにするかどうかはなんでもいいけど、とにかく連想配列は撲滅しておいたほうがいい。
  • 高級なファクトリー機構は使わない
     フレームワークが、オブジェクトを生成するためのファクトリー機構を用意しているかもしれない。
     名前を文字列で渡すと対応するオブジェクトが作られるとか(……まあ、こんなアホな仕組みをなぜ作っちゃうのか知らないけど)。
     実際のところ、よほどよくできたメンバーが集まっていない限り、そういうのは結果的に無視されることになる。みんなわりと、newと書きたがる。
     本当にnewと書かせたくないのなら、newできない仕組みを組みこまなければならないだろう。

 あとは、たぶん自作じゃないと組みこまれていないけど必要なからくりを作る。

  • 本番環境用と開発環境用、二つの設定ファイル置き場(ディレクトリ)を作る
     本番環境用の設定行をコメントアウトしてその下に開発環境用の設定を書くとか、なんであえてそんなリスクを犯すの?という話だ。
     はじめから分けておき、$_SERVERかなにかを見て、自動的にディレクトリを差し替える仕組みを作っておく。
  • 設定ファイルは細かく分ける
     何百行もある設定ファイルとか。管理できると思うほうがどうかしてる。
     そういうのはすぐに「使わなくなったけど怖くて消せない設定」が溜まっていくことになる。
     DB用ならDB用と、できる限り細かく分割する。そうすれば、影響範囲が特定しやすいから、あとで不要になったときに消せる。いややっぱり怖くて消せないかもしれないけど、可能性があるだけマシ。
     もちろん、グローバルスコープも定数も使わない。static変数またはクラス定数だけを書いたクラスになるだろう。ほんとにconst1行しか書いてないクラスとかでいいから、細かく細かく分けるべき。いくら細かくしても、あとでどうせ勝手に追加されて大きくなっていくんだし。
     .iniファイルとかXMLとかでもいいけど、あまり高級なものをつかうと、不具合が出たときに疑う対象が増えてしまう。かえって手間になる。コアな部分ほどシンプルな実装を採用したほうがいいと思う。
     高級なフレームワークを敬遠する理由は、そういうところにもある。
  • テストコード置き場を作る
     リリース用のファイルとテストコードが同じ場所に混在しているとか、それこそ管理できるはずがない。
     リリースのときには消さなきゃならないわけだから、プロジェクトリーダーにとっては、そういうのも管理対象に含まれているんである。
     というわけで、テストコード専用のディレクトリをはじめから作っておく。
     とはいえ、置き場を作ったとしても、そこを使ってくれるのは理解のある少数だけかもしれない。それがしょうじきなところだ。だけどそれでも、ないよりはマシなのだ。
  • バックアップのとりかたを考えておく
     ソースのバックアップはできるだけ毎日とる。
     これはあとで戻せるようにするためじゃなくて「いつでも戻せるから」と宣言するため。ソース中にコメントアウトを残すとか、そういう愚かな行為をさせないためにバックアップをとるんである。
     SVNを使うとか、いろいろやれることはあると思う。

 ようするに、開発メンバーはつねに予想以上にフリーダムだ。でも、それでもプロジェクトリーダーは管理しなければならない。開発前に手間を減らす工夫をしておかなければ大変なことになる。
 有名なフレームワークをダウンロードしてきて使うのは、もちろんいい。けど、それで手間が増えてしまうくらいなら、簡単なものを自作したほうがいい。
 けっこう、できの悪いフレームワークがあったりするし。
 機能を限定したライブラリじゃなくてフレームワークの開発ばかりが盛んなところが、PHPの現状をよくあらわしている。つまり、けっきょくどう使っていいのかが決まっていないんである。
 いわゆるアーキテクトたちにとっては、腕の見せどころ!といえなくもない。
 じつは、そこらのJavaだの.NETだのの開発よりもよほど、高い技術力を持ったアーキテクトの存在を必要としてる。
 しかし、PHPはどうもバカにされる言語なので。技術力に見あう報酬は約束できませんがねー。


2009/06/20 10:05

PHP最小のテンプレートエンジン
 プログラム

 PHPはもともとがテンプレートエンジンみたいなモノで、別にわざわざテンプレートエンジンを使う必要なんかない気もする。
 でもやっぱり使うと便利なときもある(かえって不便なときもある)。
 じゃあテンプレートエンジンに必要な機能ってなんだろう。

  • 変数をアサインする
  • テンプレートファイルを読み込んで表示する
  • テンプレート中で、アサインした変数を(簡単に)呼び出せる
  • if文とfor文

 まあそういうことだよな。
 テンプレートエンジンを使わない場合、単純なincludeとかでテンプレートファイルを呼び出せばいい。もちろん、テンプレートを分けないよりは何千倍もいい。
 PHPそのものなら、if文でもなんでもつかえる。テンプレートエンジンの機能はすでに実現されているわけで、つまりincludeがあればテンプレートエンジンは必要ないんである。
 テンプレートエンジン記法のほうが読みやすい、PHPネイティブだとif文の{}とかがすごく見づらい、という意見もあるかもしれない。これには解決法があって。

<? if($hoge) { ?>
<?= $hoge ?>
<? } ?>

 なんてのはかなり見づらくなるわけだけど、でもこれを

<? if($hoge) : ?>
<?= $hoge ?>
<? endif ?>

 こう書きかえれば、だいぶ読みやすくなる。じっさい、このためにこんな記法が残ってる。
 こんなのでも、JSPのうんたらタグとかよりは書きやすい気がするし(わたしが慣れてないだけ)。というか、テンプレートエンジンごとの独自記法なんていちいち憶えられないし、これでいいような気もする。

 ただ一点、問題がある。
 この方法だと、グローバル変数を使うことになってしまうんである。グローバル変数はもちろん大問題だ(世界中のすべての人にこう思ってほしい……)。
 テンプレートエンジンを使う場合と使わない場合、なにが違うかといえばこれだ。
 表示用変数の名前を制限するとか、コーディング規約的なやりかたで縛るという方法はあるだろう。規約を守る体制があるなら、それでもいいだろう。つまり、規約は守られないので、なにか強制力のある仕組みが必要なのである。

 というわけで、PHPのテンプレートエンジンに必要な機能があるとすれば。
「グローバルでない、表示専用のスコープを提供すること」
 なんじゃないか。
 そういったことをふまえて、ネイティブPHPの機能を最大限に活用したテンプレートエンジンを書いてみよう……。

class EasiestTemplate
{
public function show($____file)
{
extract((array)$this);
include $____file;
}
}

 8行で実現!

$template = new EasiestTemplate();
$template->hoge = 'piyo';
$template->show('foo.tpl.php');

 これでも、なんかそれっぽい書きかたにはなる。
(PHPは、定義していないpublic変数を外から作られても文句をいわない)

 うーん、ほんとにこれでいいなあ……。
 余計なことしないし、パフォーマンスの問題もないし、なにより学習の必要がない。これが理想的なテンプレートエンジンだ! という人がいてもいいと思う。


てらしま -2009/06/22 01:51
 よくきく「PHPなんてただのテンプレートエンジンで、言語じゃない」っていうのはぜったい信じちゃダメと思う。そんなこといってる人は本物の技術を知らないと思う。上のはそういう意味じゃないです。


2008/12/28 20:18

掲示板のコメントスパム対策
 プログラム

 以前も書いたけど、そのページは事故で消えましたorz
 数ヶ月たつけど荒らしにはあってないので、方法を紹介だけします。

 まず、スパムは人間ではありません。ロボットです。なので、ちゃんと理解して対策をとれば防ぐことができます。まだまだロボットでは人間さまにかなわないのです。
 スパムがロボットであるという前提に立って考えてみます。ロボットはどうやって、コメントフォームを捜すのでしょうか?
 Googleのクローラーと同じ。リンクを辿ってやってくるのです。そして、まあ単純に考えて、formタグを捜すのでしょう。
(あくまで推測)
 というわけで、ロボットに対抗するために考えられる方法は↓。

  • フォームを、ロボットには見えないようにする

 とそんなことを考えて、このサイトでとってる方法。
 フォームタグをHTML中に書きません。Javascriptのdocument.writeで書きます。

 PHPでやる場合のソース↓。

<div id="hoge">ふぉーむ</div>
<script type="text/javascript">
	document.getElementById("hoge").innerHTML = "<?
ob_start();
?>
フォームをここに書く
<form ……
	……
</form>
<?php
$h = ob_get_contents();
ob_end_clean();
$h = preg_replace_callback(
	'/[<"\'>&aiueo\n\r\\\\]/' , 'ordChar' , $h);
echo $h;
?>";
</script>
<?php
function ordChar($matches)
{
	return '"+String.fromCharCode(' . ord($matches[0]) . ')+"';
}

 HTML特殊文字と、\とあとてきとうに何文字かを

String.fromCharCode()
 に変換。これで、formという単語すらなくなります。ロボットでは、フォームがあることがわかりません。
 もっとも、ロボットがJavascriptを解釈しはじめたら別。そうなったら別の方法を考えるしかない。
 この方法の欠点は、すでにスパムサーバのDBに登録されてしまっているページでは効かないかもしれないというところ。まあ、そういうDBがあるのかどうかは知らないけど。

 もちろんほかにも方法はある。

  • 「半角英数のみで、URLが含まれる投稿」をNGとする
     以前ここで使ってた方法。効くけど、でもこれじゃあ、外国人を排除してしまう。あと、近ごろは韓国からのスパムとかも増えてるらしい。
  • サイト全体で投稿コメントに含まれるURLを集計し、頻度の高いものをNGとする
     はてなはこんな方法らしい。すごく大きなサイトでしかできない。
  • コメントフォームへのリンクにJavascriptを仕込む
     ただのaタグじゃなくて、onclickでページ遷移させる。理屈としてはこのサイトの方法と同じ。
  • コメントにログイン(またはワンタイムパスワード)が必要
     確実だろうけどユーザビリティを犠牲にしてる
  • IP制限
     こういうめんどくさい方法は本質的にダメだと思うけど……、まあ効くのは確かだろう。

2008/12/24 20:58

HTML特殊文字エスケープのやりかた
 日記  プログラム

 DB登録時にHTML特殊文字をエスケープして登録してるPHPシステム。コレ久しぶりに喰らった。すげー困る。

 というわけで改めて、正しいやり方を書く。

  • HTML特殊文字は表示の直前にhtmlspecialchars!
  • htmlspecialcharsには第2引数でENT_QUOTESをつけろ!
    (シングルクォートもエスケープ)
  • 変数を表示するときは、基本全部htmlspecialchars!

「表示の直前」である。htmlspecialcharsをしたら、その後はいっさいの加工をしてはいけない。「あたりまえじゃん」と思った人はずっと忘れないで。また、エスケープするタイミングはできるだけ一致させないと、あとでわけわからなくなる。「直前」が一番わかりやすい。
 ENT_QUOTESについては異論もあるらしい。けどまあこういうのは、「正しいか正しくないか」じゃなくて「できるかできないか」なのだ。ひとりで作ってるなら個別に正しい判断も可能だが、システムは不特定多数が触る。だから、わかりやすく全部つけときゃいい。
 そして、基本は「全部」だ。定数だろうとなんだろうと全部。どれをやってどれをやらないか、判断できる人もいるだろうが、不特定多数の全員ではない。わかりやすく全部、できるだけ同じ方法でエスケープ。

 それで支障があるのなら、設計が悪い。いまなおさないと、いつか問題が起こる。

 ……なんか、書いててレベル低い話だなあと思うけど。でもPHPってそういう世界だからさ……。
 PHPに関してはいつも思うが、間違ったこと書いてるサイトがあまりに多いという問題もある。これに関してはGoogleの罪かも。


2008/09/18 00:56

ajaxの作法?
 プログラム

 ajaxとか流行っちゃって、わりと気軽につかわれてたりするわけですが。これがけっこう、やばいことになりがちなのです。
 サーバに何回リクエストを投げるのかというのを意識しないで、onclickとか複数選択のselectタグのonchangeとか、危険なところにajaxを仕込んであったりする。けっこうよくある。
 そういうイベントは、下手をするとすごい回数発生してしまうのです。サーバにも同じ回数のリクエストが飛ぶわけなので、そのあたりは意識しなければいけない。
 サーバでは多くの場合、リクエストを受けるたびにSQL発行するはずだ。こういうコードは大変まずいのです。

 というわけで、こんな関数を用意しておくといいと思う。

 var ajaxTimer;
 function hogeOnChange()
 {
 	window.clearTimeout(ajaxTimer);
 	ajaxTimer = window.setTimeout("ajax()" , 1000);
 }

 1秒間イベントが来なかったら初めてajaxする。
 作法っていうか、考えてないとDBサーバが固まっちゃったりとか平気でするんで、ぜひとも気をつけたい。


プログラムを