【PHP/CakePHP】phpadvent2010 Day21「ピュアPHPでも便利なCakePHPを使おう」

PHP Advent Calendar jp 2010 21日目担当のMASA-Pです。
CakePHP界隈ではいろいろとやらせていただいていますが、PHPコミュニティの方はなかなか日程等があわない関係もあって不参加状態ですので、もしかしたら初めましての方もいらっしゃるかもしれません。一応こんなものなんかを公開させていただいております。またこんな本とかこんな本なんかを執筆させていただだいておりますので、もし興味がありましたら是非よろしくお願いします。できれば買って!(笑)

さて、今回はPHP Tipsという事なのですが、非常にお恥ずかしい話ですが当方は既にCakePHPがないと生きていけない体になっております(笑)。当方としましてもそういう道連れ的な方を一人でも多く製造したい次第ですので(笑)、布教活動がてら「CakePHPに存在している超便利な機能をライブラリとして使ってしまおう!」ということで記事を書いてみたいと思います。実例としては前日のomoonさんの記事とネタが被っているかもしれませんが、まあ続編と思ってもらっても全然構いませんです(^^;。

さて、CakePHPのフレームワークの機能についてここで多くを語るのはやめるとして、CakePHP内には「便利クラス」なるものがいくつか存在します。
特に一押しなのが、今回取り上げさせていただく「Setクラス」です。このクラスは、配列の操作をより便利にする機能を持っています。PHPは標準でも他の言語と比べて配列を自在に操れる方だとは思いますが、かゆいところに手が届かないものもいくつかあります。しかしこのSetクラスを使うと、その辺が簡単に解決できたりする場合があります。
ちなみにどんな機能を持っているのかといいますと…

  • Set::merge()
    配列を再帰的に結合する
  • Set::filter()
    空の要素を取り除く
  • Set::pushDiff()
    配列の差分を付加する
  • Set::map()
    配列をオブジェクトにマッピングする
  • Set::numeric()
    配列内の要素が全て数字かをチェックする
  • Set::enum()
    指定するキーの値を見つける
  • Set::extract()
    配列から指定キーの値を抽出
  • Set::format()
    フォーマットに従って値を抽出・生成する
  • Set::matches()
    配列内の値が条件にマッチしているかを調べる
  • Set::insert()
    配列内に値を挿入する
  • Set::remove()
    配列から指定要素を削除
  • Set::check()
    指定パスの存在をチェック
  • Set::diff()
    配列の差分を抽出する
  • Set::isEqual()
    2つの配列の内容が一致しているかをチェックする
  • Set::contains()
    配列の値が内包されているかをチェックする
  • Set::countDim()
    配列の次元数を調べる
  • Set::normalize()
    配列内の値を正規化する
  • Set::combine()
    2つ以上のパスから新たな連想配列を生成する
  • Set::reverse()
    オブジェクトを連想配列に戻す
  • Set::flatten()
    連想配列の構造をフラット化する
  • Set::sort()
    パスで示した要素を対象にソートする
  • Set::apply()
    抽出結果をコールバック処理

上記メソッドについての詳細はここでは説明しきれませんので、こちらをご覧いただくか「ポケット詳解 CakePHP辞典」を参照してください。できれば買って!(笑) 今回はこの中のSet::extract()とSet::combine()を動作サンプルとして掲載します。

さて前置きはこのくらいにして、さっそくピュアPHPコード内でCakePHPのコードを使用する方法をご紹介しましょう。

1:CakePHPのコードを配置する

先でも後でも良いのですが、とりあえずCakePHPコードを配置してしまう事にします。
今回は、ドキュメントルート内に利用元のピュアPHPコードを展開し、「includes/」内に利用したいCakePHPコードのファイルのみをを入れます。ドキュメントルートとは別の一にCakeのパッケージを丸ごとインストールして、そこを参照したりしても良いかもしれません。
CakePHPはこちらからアーカイブを入手し、その中の「cake/libs/set.php」をincludesにコピーします。また、Setクラス内では同じくCakePHPライブラリである「Stringクラス」も呼び出していますので、「cake/libs/string.php」もコピーします。その他にもクラスを呼び出しているものがありますがそれは次のステップで。

2:ダミークラスを作成する

CakePHPのコードを使うための、超最低限の機能(?)を有するコードを作成します。
includes内に「cake.php」ファイルを作成します。内容は次のようにします。


<?php
define('DS', DIRECTORY_SEPARATOR);

//CakePHP内での基底クラスのダミー
//
class Object {
}

//CakePHPのAppクラスの代替メソッド
//
class App extends Object {

	//ファイル読み込み(App::import()の代替メソッド)
	//
	function import($type, $class){
		include(dirname(__FILE__).DS.strtolower($class).'.php');
	}
}

Objectクラスは、CakePHPのほとんどのクラスが継承している基底クラスで、ライブラリでも使用している場合があります。ダミークラスを作成して問題を起きにくくしておきます。
App::import()はCakePHP内で自在にincludeするための便利メソッドで、これもライブラリ内で用いられています。SetクラスでもStringクラスを呼び出すために使用しています。本来は$typeでクラスの種類(ControllerとかModelとか)を指定するのですが、今回の実装ではincludesディレクトリに全て突っ込んでしまうため、無視します。$classはupperキャメルケースで表現されていますのでアンダースコア形式に変換する必要がありますが、今回はインチキしてstrtolower()してしまっています。厳密にやるのなら「cake/libs/inflector.php」内のメソッドを用いるとできると思います。

3:目的のピュアPHPコードの作成

最後に目的のコードを記述します。今回は次のようなコードで実験する事にします。


<?php
//Includes
//
require_once('includes/cake.php');
require_once('includes/set.php');

//Example : CakePHPのSetクラスをピュアPHPでも便利に使ってみる!
//

//データ
//
$results = array(
	array(
		'User' => array(
			'id' => '1',
			'loginname' => 'saito',
			'password' => '0123',
		),
		'UserProfile' => array(
			'id' => '101',
			'name' => '斉藤',
			'type' => 'baseball',
		),
	),
	array(
		'User' => array(
			'id' => '5',
			'loginname' => 'honda',
			'password' => '0123',
		),
		'UserProfile' => array(
			'id' => '102',
			'name' => '本田△',
			'type' => 'soccer',
		),
	),
	array(
		'User' => array(
			'id' => '10',
			'loginname' => 'ishikawa',
			'password' => '0123',
		),
		'UserProfile' => array(
			'id' => '103',
			'name' => '石川',
			'type' => 'golf',
		),
	),
);

//処理1:名前だけを抜き出す
//
$arr = Set::extract('/UserProfile/name', $results);
var_dump($arr);
echo"<br />\n";

//処理2:User.id = 5の名前だけを抜き出す
//
$arr = Set::extract('/User[id=5]/../UserProfile/name', $results);
var_dump($arr);
echo"<br />\n";

//処理3:(User.id) => (UserProfile.name)の連想配列を入手する(select文生成などで利用)
//
$arr = Set::combine($results, '{n}.User.id', '{n}.UserProfile.name');
var_dump($arr);

実行結果は次のようになります。

array(3) {   [0]=>   string(6) "斉藤"   [1]=>   string(6) "本田△"   [2]=>   string(6) "石川" }
array(1) {   [0]=>   string(6) "本田△" }
array(3) {   [1]=>   string(6) "斉藤"   [5]=>   string(6) "本田△"   [10]=>   string(6) "石川" }

SetクラスはCakePHPユーザでもあまり使われていないかもしれないのですが、この便利さは魔力を秘めているというのか使い出したら止まりません(^^;。上記2つはデータベーステーブルから特定の値を抜き出したりする場合に威力を発揮すると思います。また、今回触れてはいませんが、Set::merge()やSet::sort()なんかもかなり便利です。array_merge()は1次元目の配列要素に対してのみマージが行われますが、これは多階層の配列に対して再帰的にマージが可能です。またsortについても、特定のパスの値に対してソートをかける事ができるので、例えばとりあえず取ってきた複数レコードのデータをid順に並べ直したりといったことが簡単にできます。
しかしSetクラスは少々CPU%を使う仕組みだと思うので、peclかなんかでモジュールを利用する形で作ったりすると良いのかもしれませんね。いや、できればPHPで標準化されて欲しいものばかり!(笑)

ぱっと見なのですが、Setクラスだけでなく次のクラスライブラリもたぶん同様の方法で使えると思います。

  • Inflectorクラス
    アルファベッド表記の名前を単数形←→複数形変換したりキャメルケース←→アンダースコアに変換したりできるクラス
  • Stringクラス
    UUIDを生成したり、テンプレート文字列に要素を挿入したりできる機能のあるクラス
  • NumberHelperクラス
    通貨など数値関連のフォーマッティングをしてくれるクラス。なおAppHelperクラスを上記のObjectクラスのようにダミー化する必要があります
  • TImeHelperクラス
    時間に関するフォーマッティングをしてくれるクラス。strtotime()等の機能をより便利に拡張します

他のクラスにも有用なものがありますが、ちょっと大がかりな工夫をする必要がありそうなので、興味があったらチャレンジしてみてください(Validationクラスくらいなら比較的簡単にけるかもしれない)。

アマゾンのサーバでエラーが起こっているかもしれません。一度ページを再読み込みしてみてください。

明日は kashioka さんの担当です。よろしくお願いします!
それではMerry Xmas!!

【プログラミング全般】githubでfork刺しっぱなしのリポジトリを更新する方法

時々、興味本位で他の方のプロジェクトにforkを刺したりするのですが、そのまま放置されていて知らない間に本家の方でバージョンが上がっていたりして、自分のリポジトリが古いままって事があります。gitの事を全く知らなかった頃、やり方が分からなくて結局removeしてしまったりしたのですが、やっと更新方法が分かったのでmemo書きです。

仮にhogeさんのfugaプロジェクトをgithub内でforkしたとしましょう。
そして、hogeさんがpushして、本家の方は最新版、ところが自分のfork刺したやつは古いまま、という状況です。
この場合、次のようにすることで最新版にすることが出来ます。

git pull git@github.com:hoge/fuga.git master
git push

まあ、よくよく考えてみればごく当たり前なのかもしれませんが(^^;;;。
というか、刺しっぱなしにしておかないで、改良してpull requestするようにしましょう!

【CakePHP】フレームワークにおける「秩序」とは何なのだろう?

Twitterで少し議論になって、これってちょっと大事だよなと思ったことがあったので、言葉足らずだった部分についてもちょっと補足したりして記事として残しておこうと思います。
なお、これはあくまでも私の一個人としての「考え」であって、正解というわけではないと思います。ただ「こういった考え方もある」という点だけ伝わったら嬉しいです。

で、まあよくあるフレームワークにおける「MVC」の話なんですが、例えば「コントローラやモデルの中でヘルパーとか使ってもいいんじゃないか?」という点について。つまり、MVCで役割を分割しているのに、その領域を乗り越えて機能を実現することについてどうなのか、ということです。
CakePHPでも、ヘルパーの機能で汎用的に使いたい(そして実際に使える)機能があったり、逆にヘルパー内からモデルとかを呼び出して情報を取り出したりすることが出来るっちゃー出来ます。実際にコアヘルパーでもそのようなことをしている部分もあったりします。
正直言いまして、Cakeはかなり自由度が高く、やろうと思えば何でもできると思います。ちょっと不自由な点があっても、最悪コアコードをちょろっとextendsして置き換えるか書き換えるだけで、大抵のことは出来る。PHPなので、PHP的な書き方をすればそのような期待したことは出来ちゃうんですよね。たぶんPHPフレームワークで出来ちゃう方の部類。言い換えれば「ゆるい」です(笑)。ClassRegistoryの仕組みが出来てからは特に!(笑)

で、MVCの話なんですが「MVCを侵害することは何事だ!」「いや、使える技を使わないのはどうなんだろう」と対極の意見が生まれることもまた当然で、どちらも主張は大変によく分かります。MVCで機能をしっかり分けてコーディングをすることも大切なことですが、それによってとても面倒で回りくどい、あるいは無駄なコードを書かなければならなくなるけどちょっと工夫するだけで出来ちゃう場合もある。納期を守るために時間短縮は大切だし、そのためのフレームワークでもある。色々と意見はあると思います。

私の考えをシンプルに答えるのなら「どちらもアリだし大切」。MVCを守ることは大切だし、使える技は使っていく…これもまた重要だと思います。
しかし…私の考えはもしかしたらここから先は人と違うかもしれません。

2008年10月、「CakePHPカンファレンス東京」という大きなイベントが開催され、そこで当時CakePHPのプロジェクトマネージャであったGarett氏がCakePHPの理念について、このようにスピーチされました。

「State of Nature(ありのままに出来る自由)」
「Social Contract(ルール作り)」

CakePHPは自由に実現できるツールである。しかしそれだけでは無秩序になってしまい効率が悪い。ある程度のルール作りをしてそれに乗ってもらうことで、フレームワークの機能とかそういう部分以外に考えを注力してもらいたい…そのような意味が込められていると私は感じました。
この考え方に私は大賛成です。それは「以前に自作のフレームワークを作って業務を回していた」ことと「(現在も)オープンソース(?)なプロジェクトを公開している」点の二つのバックボーンが自分にあるからかもしれません。

まあ自作のフレームワークといってもそれはみすぼらしいもので、データを取得するクラスと自作のテンプレートシステムをくっつけてソースを分離しただけのもので、MVCとは言えないかもしれませんが。基本的に、一つ作ったプロジェクトをテンプレートにして使い回すことで、かなりの時間稼ぎが出来ていました。ただ、プロジェクトがこう佳境になってくると、脇が甘くなってくるというか、MVCなんて言っていられなくなってくるんですよね。データ取り出す部分でHTMLなんかも加工しちゃったりとか、何でもあり状態に。まあそうやって時間に間に合わせちゃうわけです。

まあ、この頃はほぼ一人でやっていて、それで回っちゃっていたので問題視することはなかったんですが、部下が一人つきまして、このなんちゃってフレームワークを勉強してもらうことに。
まあ分からないですよね。自分だったらたぶん分からないです。人の書いた、しかもぐちゃぐちゃに行き来するコードを追うことなんて(笑)。
でも、見よう見まねで頑張って書いてくれて、機能の役割なんかも分かってきつつあったみたいで、じゃあお願いということで別のプロジェクトを任せてみたんですけど…でも、やっぱり無秩序から生まれたものは無秩序でして、彼の書いたコードの何割かは自分の理解出来ないものに(笑)。別に彼が悪いんじゃないと思います。そういうなんちゃってフレームワークを書いた自分に責任があると思います。

時間をかけて覚えてくれたものなのに、できあがってみれば書いた本人しか理解の出来ないものになってしまうわけでして、これじゃあいけない! ということでオープンソースフレームワークを導入するに至った経緯があります。ただ、がっちがっちで不自由だらけのものでは小回りがきかなくて業務に差し支えがある場合もあるので、ある程度の自由さも検討し、またたまたまPHP4問題も当時ありまして、結局CakePHPしかない状況になったのですが(笑)。でも、もっともっと前にCakeがあったら違った世界になっていたかもしれません(こちらこちら を参照)。
こういった経緯もあって、秩序というのはとても大切に思っているわけでして、だからといってそうも言っていられない状況もあって、この二つと要領よくつきあっていくことが、フレームワークを効率的に便利に使っていくための大切な事なんじゃないかと思います。

ですが…途中でも書いたのですが「無秩序のものは無秩序を生む」。ここも大切だと思うんですよね。特に、初めてそれを触る人にそのやり方を見せて「何故そうなるのか」を理解しないままそれを「こうやって書くものだ」と覚えられてしまうことが大変に厄介で、そうなったらもうそれが主流になっちゃうんですよね。
何が言いたいのかというと、そういうルールから外れる裏技的な使い方は、出来るということは分かっていて使うこともアリっちゃーアリだけど、それを主流にしてはいけないんじゃないか、ということです。
主流にすることは結構簡単で、Cakeだったらブログなんかにやり方を書いちゃえば、あるいはフォーラムやBakeryで書いちゃえば、簡単にあっという間に広まっちゃうことも。センセーショナルな使い方なら、なお早いかもしれない!

でも、フレームワークの制作者側の立場としては、思想と反対のことを広めたくないはずです。Cakeに「ありのままの自由」「ルール作り」という2つのテーマ(ほかにも箇条書きであったのですが)があるのなら、使う側にもそれを十分に理解する必要があると思うのです。
Cake Software Foundationに肩入れしているような発言かもしれないのですが、それは自分もKtai Libraryというプロジェクトを公開しているからでして、「こういう風に使ってもらいたい」ってのはやっぱりあるからですね。今のところ想定外の使い方をされているところはないですけど(使いようもないのですが、強いて挙げればCake用と思われてしまっている点かな。一応汎用的に使えるのに(^^;;;)、間違った方向にプロジェクトが進んでしまって欲しくないと思いますから。Cakeの裏技って結構あると思うんですよね。でも、それが場合によっては、こういった情報がプロジェクトにとって「毒」になってしまうこともあるんじゃないかと。

なので、もし「こうしたらもっと便利に使える」という点があるのなら、それは合法的(?)に解決していくべきかなーと思います。つまり、もしその裏技が本当に他の開発で役に立つのなら、チケットなんかで「この機能はヘルパーだけど、汎用的に使えるからコアライブラリにした方が良い」とか、「この機能はヘルパーでも使いたい」とかフィードバックしていく方が。むしろ積極的に。でなければ、裏技は裏技にとどめておいて、分かる人だけの特権にしておいた方が、フレームワーク個体としてはいいものになっていくのではないかと思います。

個人的に、こういった「ゆるさ」というか書きたいことが書けちゃうCakePHPは醍醐味で、止められない理由の一つでもあるのですが(笑)。とてもいいツールだと思うので、これからもより良い方向で発展してくれることを願い、また「Cakeはとてもいいんだよ!」と啓蒙していきたいと思います。偉そうなことを言える立場ではないと思いますが、こんなにどっぷりはまってしまって、しかも本まで出してしまった以上、もうただ使っていくだけの一ユーザには戻れないかなーなんて(^^;;;。

なんか議論していた以上のことをついでに一気に書いてしまった感じなんですけど、自分の考えを再認識するいい機会でした。こういった議論が出来たことを感謝しています!

【戯れ言】あえてCakePHPをヨイショしてみる

prog-logo今回はCakePHP記事としてでなく、一般的なプログラミング…いや、完全個人的な戯れ言としてちょっと書いてみたいと思う。

発端は最近のはてブのエントリで、こちらこちら の記事でいろいろなフレームワークの比較が書かれているのだが、どうも直感的に各フレームワークの良さが伝わってこなかったのだ。例えば前者の方は、最終的にZendFrameworkをお勧めしているのだが、そのお勧め理由が全くよく分からないというのか、「自分が好きだから良いですよ」的な結論にどうしても見えてしまう(いえ、その主張そのものを否定はしないです)。というのは、以前まで話されていたフレームワークの比較が、現時点では通用しなくなってきているというのか、どれもバージョンアップを重ね、よりよいものになってきているからである。自分も このような記事 を書いていたりするけど、丁度良い機会なので、今自分が感じていることを以下に書いてみる。

個人的に言わせてもらえば、私のメイン使用であるCakePHPも「かなりのところでいい線いっているんじゃないか」と最近思っている。CakePHPを初めてさわってまだ1年半程度だけど、この間にとんでもないほど機能は向上し、そして安定もしたように思う。Cakeは現在メジャーバージョンは1.2で、当初はまだ1.1だった。この進化の過程で、Cakeの出来ることは格段に増えていった。

例えば、フレームワーク比較で良く目にする違いとして「symfonyは大規模向け、CakePHPは中小規模向け」というのがある。これはいったい何なのだろう? よく読むと、大抵は「symfonyはプラグイン機能があるため、大規模向け」という書き方がされている。これは本当にそうなのだろうか?

まず簡単なツッコミをさせていただくと、CakePHPは、既にプラグイン機能を装備している。symfonyとほぼ同じように、一連機能を持った仕組みを簡単に入れたり抜いたり出来る。つまり、この定義でいけば、CakePHPは十分に大規模向けだ。

でもよく考えてみて欲しい。プラグイン機能は本当に「大規模」なサイトを構築するための機能なのだろうか? いいや、私はそうは思わない。本当の大規模サイトというのは、フロントエンドは何十ものサーバがロードバランサなりラウンドロビンなりで制御され、バックエンドも同様に複数のDBがレプリケーションなりで動いているような、そんな状態のことを言うのではないのだろうか? そして、そのような機能を搭載しているフレームワークこそが「大規模向け」ではないだろうか? 安定しているかどうかは置いといて、CakePHPはこれらを支援する機能を持っているし、実際に「Firefox アドオン」サイトのような大規模なサイトもちゃんと動いている。
そしてプラグインは、私はむしろ逆の、中小規模向けのものと考える。何故なら、第三者の制作したものを、手っ取り早く自分のものに出来るというのが実質的なところであり、そういった用途のものは中小規模向けに書かれているものが多いからだ。そして、それを使う人も勿論中小規模の環境をもつ人が多い。大規模環境を持っていれば、間違いなく自前で専用にカスタマイズされたものを持たざるを得ない。

そういった意味で、最近のCakePHPは隙が全くない。大規模から小規模まで自在だし、前からPHPは4でも5でも動く(いずれ出るであろう6にもサポート表明している)。1.2になって軽量とは言えなくなってきてしまってはいるが、それはサーバスペックを上げればどうにでもなるし、そもそもキャッシュを使えば重さは感じなくなる。何より強力なアソシエーション機能は、おそらくDBをもっとも都合良く操作できるのではないだろうか?

勿論、symfonyやZendFrameworkなど他のフレームワークには、それぞれの良さがあると思うし、繰り返すけどどれもバージョンアップを重ね、機能的にはどれも大差ないように思える。まあどちらかと言えば、Cakeとsymfonyは同じグループで、ZendFrameworkはグループが違うように思う。Zendのほうは各機能が独立して機能する点は今でも継承していて、それが特徴的かつ有用な機能の一つだからだ。Adobeとかがサポートしている点も大きいと思う。symfonyの豊富なプラグイン機能は、未だ優位だと思うし(でも多少勢いの衰えた感はある)。

だけど私は、Cakeの良さは、単体としての機能はとりあえず置いといて、別の点で「少なくとも日本では」優位であることを主張したいと思う。それは、特に最近、いろんな事例やTipsなど、豊富なドキュメント…とりわけ日本の有志の方が残した「日本語のドキュメント」がおそらく一番多く存在している点…これこそ、CakePHPの最大の強みではないだろうかと思う。フレームワークが高機能だったり高速だったり、そういった要素も勿論大事だと思うけど、結局は満足に使いこなせなければ意味がないわけで、それを使いこなすための手法が、ネットで検索すれば沢山手に入る。それも手に入る情報は「使えない」「改造が必要」といった後ろ向きなものでなく、前向きなものばかりだ。CakePHPのドキュメントは、本当に宝の山に思える。フォーラムも活発で、勉強会の頻度もおそらくは一番だ(Zendは少なくともPHPフォーラムであまり見たことがない)。

思えばその1年半前、さんざんに悩んだあげく、メインで使っていくフレームワークをCakePHPに絞った。もしかしたら機能的な面で言ったら、ZFやsymfonyの方が良かったかもしれない。いや、RoRだったり、Javaだったりした方が良かったかもしれない。だけど、勉強会に参加したり、ブログで情報を発信したりしてみて、Cakeに関係する皆さんがそれぞれ知り得た情報がしっかりと共有できているというか、もっといえば繋がりが非常に強く結束力のようなものがあり、皆でCakePHPを盛り上げているという実感がある。その辺が凄く楽しい! 新しい発見があるし、それが自分への刺激にもなって心地よい。自分の情報はその中でどのくらい役に立っているのかは分からないけど、それでも自分の情報が、その人にとって有効に働いてくれたら嬉しいし、それがきっかけでその人も更にCakePHPを盛り上げていってくれるのならもっと嬉しいと思う。今回CakePHP勉強会で壇上でKtai Libraryを発表することになったけど、これもそんな気持ちで現在取り組んでいる(まあもっともKtai LibraryはCake以外でも是非使って欲しいんだけど)。

何か最後は終わった後の感想みたいになっちゃったけど、まあ言いたいことは、「CakePHPはいろいろな点でなかなかイカしているぜ!」みたいな感じですかね(笑)。そして改めて、情報を公開されている方には感謝をしたいと思います。いつもありがとうございます。

【Ktai】「Ktai Library」バージョンアップ少し延期のお知らせとviewの話

icon_ktai何度もお騒がせしてしまい申し訳ございません。
先ほど「バージョンアップ予定」としましたが、心配していた内容は回避されていたため、少し延期します。

このままでは、やると言ったり止めると言ったり、訳が分からないでしょうから、経緯説明を兼ねて、CakePHPの仕組みについて説明をしようと思います。ちょっとCakePHPの内部の込み入った話をしますので、もしかしたら難しいと感じるかもしれませんが、まあそういうものだと思っていただいてOKです。

発端は、ヘルパーでライブラリ本体である「lib3gk」の初期化をbeforeRender()内で行っているのですが、よくよく考えてみたら、beforeRender()はView::_render()内で「何度も呼ばれてしまうのではないか」という懸念があったからです。というのは、beforeRender()とafterRender()はView::_render()でコールされるのですが、ビューファイルが展開されるたびにView::_render()が呼ばれるからです。ここで言うビューファイルというのは、一般的なビュー以外にもlayoutファイルも含まれ、最低でも2回は毎度呼ばれることになります。つまり、その都度ライブラリがクリアされ、初期化されるようであれば、非常に無駄が発生することになり、バグも生みかねません。

で、結論から言いますと、beforeRenderおよびafterRenderは、ヘルパーが初めて読み込まれた場合、つまり初めてView::_render()が呼ばれた場合だけ行われるため、実害がなかったことが分かりました。なので、現バージョンのままお使いいただいて問題なく、結果バージョンアップは不要だという判断になりました。

そしてその副産物として、今まで不可解だったhelperコールバックのことがやっと少し分かりました。
今回outputがどうしてもうまく受け取れなくて困っていたのですが、この挙動に問題があったようです。

私はCakePHP1.1後期からのCake使いなのですが、RC1が出るまでは、outputはafterRender内でob_get_clean()で受け取るのが一般的な手法で、私もそのように行っていました。ところが、RC1からこの手法が使えなくなり、変わりにView::outputプロパティが受け渡しとして使われるようになりました。そして、この頃からbeforeRender,afterRender以外に「beforeLayout()」「afterLayout()」コールバックが追加されました。

今回KtaiLibraryを実現するにあたり、outputは「afterLayout()」で受け取れば問題なく処理が出来ることが分かりました。方法は

function afterLayout(){
  $view =& ClassRegistry::getObject('view');
  $view->output = mb_convert_encoding($view->output, 'SJIS');
}

という感じです。
ところが、「afterLayoutで受け取れるんだから、初期化はbeforeLayoutだろう」という思惑でいたところ、どうも具合がおかしい。ライブラリのインスタンスが作成されていない状態で、エラーが出てしまいます。初期化はbeforeRenderでやらないとダメなのです。

私は、「beforeLayout()→ (beforeRender()→afterRender()){n} →afterLayout()」の順番で処理が行われると推測していたのですが、初期化はbeforeRenderとなると、「beforeRender()→beforeLayout()→afterLayout()→afterRender()」の順番ではないか? でも実際にはafterRender()では何も入手できない(ob_start()でバッファスタートしているにもかかわらずob_get_clean()では何も入っていない)のです。

想定とは全く違っていました。
「beforeRender()→afterRender()→beforeLayout()→afterLayout()」というのが正しい順番です。

なぜこうなるのか。
それは「View::_render()内で初めてhelperが読み込まれたときだけ」実行されるから、だったのです。
ビューの処理は、まずビューを読み込んでからレイアウト処理を行います。この「まず」の部分でbeforeRenderとafterRenderのコールバックが呼ばれます。次に、レイアウト処理に入って、まずafterLayout。そしてレイアウトファイルをView::_render()を使って読み込むのですが、この中のbeforeRenderとafterRenderは2度目となるので実行されません。そして最後にafterLayoutが呼ばれ、終了…という感じなのです。

なぜこのような仕組みが提供されているのか、その心はさっぱり分かりません。
これでは、afterRenderとbeforeLayoutはほとんど役立たずです。
また、始まりがbeforeRenderで終わりがafterLayoutというのも、なんだかスマートではない…
「beforeLayout()→ (beforeRender()→afterRender()){n} →afterLayout()」という流れが一番自然な気がします。
この流れは改善の余地があるのではないでしょうか?

ちなみに、Ktai Libraryはバージョンアップしたいと言えばしたいです。
うっかりミスをしてしまった箇所がありまして…
なんと、get_version()でとれるバージョン番号が「0.0.2RC4」となってしまっているのです。
バージョン番号を直すのを忘れてしまいました(大汗)。
これだけを直すバージョンアップは、さすがにアレですので(^^;;;

とうわけで、お騒がせしてしまったこと、重ねてお詫び申し上げますm(__)m。

【追記】(2009.11.18)
コールバックの順番ですが、1.2.5(それ以前も?)では修正がされていて、「beforeRender()→beforeLayout()→afterLayout()→afterRender()」の順になっていました。どうやらこの現象はバグだったようです。
icon_ktai何度もお騒がせしてしまい申し訳ございません。
先ほど「バージョンアップ予定」としましたが、心配していた内容は回避されていたため、少し延期します。

このままでは、やると言ったり止めると言ったり、訳が分からないでしょうから、経緯説明を兼ねて、CakePHPの仕組みについて説明をしようと思います。ちょっとCakePHPの内部の込み入った話をしますので、もしかしたら難しいと感じるかもしれませんが、まあそういうものだと思っていただいてOKです。

発端は、ヘルパーでライブラリ本体である「lib3gk」の初期化をbeforeRender()内で行っているのですが、よくよく考えてみたら、beforeRender()はView::_render()内で「何度も呼ばれてしまうのではないか」という懸念があったからです。というのは、beforeRender()とafterRender()はView::_render()でコールされるのですが、ビューファイルが展開されるたびにView::_render()が呼ばれるからです。ここで言うビューファイルというのは、一般的なビュー以外にもlayoutファイルも含まれ、最低でも2回は毎度呼ばれることになります。つまり、その都度ライブラリがクリアされ、初期化されるようであれば、非常に無駄が発生することになり、バグも生みかねません。

で、結論から言いますと、beforeRenderおよびafterRenderは、ヘルパーが初めて読み込まれた場合、つまり初めてView::_render()が呼ばれた場合だけ行われるため、実害がなかったことが分かりました。なので、現バージョンのままお使いいただいて問題なく、結果バージョンアップは不要だという判断になりました。

そしてその副産物として、今まで不可解だったhelperコールバックのことがやっと少し分かりました。
今回outputがどうしてもうまく受け取れなくて困っていたのですが、この挙動に問題があったようです。

私はCakePHP1.1後期からのCake使いなのですが、RC1が出るまでは、outputはafterRender内でob_get_clean()で受け取るのが一般的な手法で、私もそのように行っていました。ところが、RC1からこの手法が使えなくなり、変わりにView::outputプロパティが受け渡しとして使われるようになりました。そして、この頃からbeforeRender,afterRender以外に「beforeLayout()」「afterLayout()」コールバックが追加されました。

今回KtaiLibraryを実現するにあたり、outputは「afterLayout()」で受け取れば問題なく処理が出来ることが分かりました。方法は

function afterLayout(){
  $view =& ClassRegistry::getObject('view');
  $view->output = mb_convert_encoding($view->output, 'SJIS');
}

という感じです。
ところが、「afterLayoutで受け取れるんだから、初期化はbeforeLayoutだろう」という思惑でいたところ、どうも具合がおかしい。ライブラリのインスタンスが作成されていない状態で、エラーが出てしまいます。初期化はbeforeRenderでやらないとダメなのです。

私は、「beforeLayout()→ (beforeRender()→afterRender()){n} →afterLayout()」の順番で処理が行われると推測していたのですが、初期化はbeforeRenderとなると、「beforeRender()→beforeLayout()→afterLayout()→afterRender()」の順番ではないか? でも実際にはafterRender()では何も入手できない(ob_start()でバッファスタートしているにもかかわらずob_get_clean()では何も入っていない)のです。

想定とは全く違っていました。
「beforeRender()→afterRender()→beforeLayout()→afterLayout()」というのが正しい順番です。

なぜこうなるのか。
それは「View::_render()内で初めてhelperが読み込まれたときだけ」実行されるから、だったのです。
ビューの処理は、まずビューを読み込んでからレイアウト処理を行います。この「まず」の部分でbeforeRenderとafterRenderのコールバックが呼ばれます。次に、レイアウト処理に入って、まずafterLayout。そしてレイアウトファイルをView::_render()を使って読み込むのですが、この中のbeforeRenderとafterRenderは2度目となるので実行されません。そして最後にafterLayoutが呼ばれ、終了…という感じなのです。

なぜこのような仕組みが提供されているのか、その心はさっぱり分かりません。
これでは、afterRenderとbeforeLayoutはほとんど役立たずです。
また、始まりがbeforeRenderで終わりがafterLayoutというのも、なんだかスマートではない…
「beforeLayout()→ (beforeRender()→afterRender()){n} →afterLayout()」という流れが一番自然な気がします。
この流れは改善の余地があるのではないでしょうか?

ちなみに、Ktai Libraryはバージョンアップしたいと言えばしたいです。
うっかりミスをしてしまった箇所がありまして…
なんと、get_version()でとれるバージョン番号が「0.0.2RC4」となってしまっているのです。
バージョン番号を直すのを忘れてしまいました(大汗)。
これだけを直すバージョンアップは、さすがにアレですので(^^;;;

とうわけで、お騒がせしてしまったこと、重ねてお詫び申し上げますm(__)m。

【追記】(2009.11.18)
コールバックの順番ですが、1.2.5(それ以前も?)では修正がされていて、「beforeRender()→beforeLayout()→afterLayout()→afterRender()」の順になっていました。どうやらこの現象はバグだったようです。

【Ktai】「Ktai Library」を再度バージョンアップをします

icon_ktai早速新版をダウンロードされた方には大変に申し訳ないのですが、「Ktai Library」を再度バージョンアップしようと思っています。たぶん0.0.2をお使いいただいて問題ないとは思いますが、念のための対処です。機能的には変えないつもりなので、そのまま差し替えていただいてOK だと思います。現在テスト中です。
また、バージョンナンバーは、一応区切りとして0.1.0とします。今後のバージョンナンバーは、機能拡張があった場合は下一桁を、バグフィックスは下二桁をカウントアップすると思います。

取り急ぎの報告ですが、よろしくお願いいたします。

【追記】
影響がないことが分かったため、バージョンアップは中止します。
詳しくは こちら をご覧ください。

【プログラミング関連】FirefoxでUTF-8以外が文字化けする原因(の1つ)

prog-logo最近、仕事で携帯サイトを作っていて、CakePHPを用いて製作しているのですが、ShiftJISでコーディングしているのですがなぜかFirefox「だけ」文字エンコーディングがUTF-8に強制的にされてしまうことで悩んでいました。

まあこの現象は前から認識していて、例えばたまたま以前にCakePHPフォーラムで返答したことのある こんな 話題がはてブの新着に上がってきているんですが、結局このときは良く分からないままそのままになってしまっていたんですよね。
今回のこの件も、別にPCから見れる訳ではないので気にしなくても良い問題なのですが、とはいえ何だかあまり気持ちが良くないので、少し調べてみることにしました。

とにかくおかしいのは、いくら内部エンコーディングがShiftJISになっていても、header()で書き込んでも、何をしてもUTF-8にされてしまう点です。IE、Safari(iPhone)、実機携帯(今回はSoftbank920Pをメインにチェックしてます)では問題がないのです。
そしてさらに不思議なのは、ページによってこの現象が出る所と出ないところがある点。トップページは出ないのですが、例えば画像だけしか表示しないようなページだと、ナビ用のテキスト(「戻る」とか)が化けます。当然絵文字コードも化けます(あ、携帯ライブラリ を通せば化けません)。

いろいろなものをとっかえひっかえしてみたのですが、なんと意外な事実が判明!
まあタネを知ってしまえば「な~んだ!」「つ~かしっかりやれよ」と言われそうなのですが、こんな落とし穴があったとは……

ShiftJISの場合、皆さんはどのようなcharset文字列を入れていますでしょうか?
「SjiftJIS」? 「Shift-JIS」? 「Shift_JIS」? それとも「SJIS」?
そうなんです、Firefoxは、このcharset文字列がかなり厳密で、「Shift-JIS」もしくは「Shift_JIS」でないとNGらしいのです。

私はPHPマニュアルでmb_???系の解説で「sjis」と記されているので、ずっとsjisと書いていたのですが、これが引っかかっていたようです。

分かりにくいのは、sjisと書いても文字化けにならないことがあり(というか今までならなかった場合が圧倒的に多かった)、これについては原因は特定できていないのですが、一番最初の文字列だとか、頻度だとか、そういった要因で自動判別に引っかかり、「たまたま」文字化けが起きていなかっただけなのかもしれません。とにかく、「ShiftJIS」「SJIS」はNGなのです。
というのも、こちら の仕様でもしっかり「Shift_JIS」と書かれていて、これが正式のようです。なので、本来「Shift_JIS」と書くべきなのでしょう。まあだいぶShiftJISで書く機会は減ってきていると思いますが、携帯サイトなどではまだまだ現役でしょうから、気をつけた方が良いですね。
ちなみに他のブラウザは寛大で、「ShiftJIS」「SJIS」は認識します。内部でしっかりとエイリアスされているのかもしれません。

まだ他に文字化けの理由はあるのかもしれませんが、心当たりのある方はとりあえずチェックされてみてはいかがでしょうか?

【サーバ関連】iptablesの設定で苦戦

年末から始めているサーバ設定ですが、結構苦戦していたりします。
ネットワークの構成を複雑にしてしまったため、iptablesの設定に苦労しています。

LAN-WAN一対一の設定例は、ネット上に結構転がっているのですが、今回はLANを2種類にしていまして、管理用と一般用でセグメント分けしているためです。しかも、同じものが独立してるのならまあ一対一を二つ書くイメージでOKなのですが、一般用はサーバアクセスに対していろいろな制限を付けるため(例えばSSHを禁止するとか)、非常にめんどくさい…

また、今回はこれを2枚のネットワークカードに混在させているために(つまりLAN側は同じネットワークカード)、iptablesの書き方も多少特殊になっています。さらに、これがうまく表現できたらVPNアクセスについても同じように一般用と管理用のネットワークに乗せたいと考えているため、今後さらに複雑になりそうな予感です。

iptablesの設定はサーバ設定の中で一番苦手です。それ以前はsendmailだったのですが、最近はPostfixに鞍替えしたので比較的簡単に出来るようになったのですが…
なんかこう、フローチャートみたいな感じで線を引っ張ったりして簡単にロジックが組めるような、GUIなツールなんか無いですかね。分かりやすいツールなら、いざというときに他の(サーバ管理をやったことがないような)人に操作してもらったりも出来るんですけどね。

【プログラミング全般】私が出来る限りソースコードを公開しない理由

prog-logoお気づきかもしれませんが、私は滅多にソースコードを相手に提示したり公開したりしません。
例えば、フォーラムなどで質問されたものに対してアドバイスはしますが、その中に適切なソースコードを書きません。

言い訳めいた話をするかもしれませんが、そのようにしていることにはちゃんと理由があり、漠然とした言い方をすれば、そうすることでご自身に「学んで欲しい」からです。

現在は、インターネット…もっと言えば検索サイトでのネット検索で、大抵のものは入手できる、大変便利な世の中になりました。ソースコードはいろんな所から入手でき、ダウンロードするかコピペするかでその機能を自分のものにすることが出来ます。これはこれで便利なのですが、それが原因で技術者もあまり成長していないのではないかと考えています。

私はどちらかというと「旧世代」の人間で、インターネットどころか外部記憶が「カセットテープ」の頃からやっていまして、その頃は何もない「自分で作るしかない」世界でした。雑誌や書籍が唯一の参考書で、それを片手にプログラムを「手で打ち込み」身体で覚えたものです。
だからといって、「この頃のプログラム技術が生きている」なんて野暮な言い方はしませんし、実際に現在のプログラム技術と比べたら、この頃のプログラムの知識など全く役に立ちません。しかし「自らが苦労して得た「考え方」はいつまでも忘れない」というのは確かにあります。コピペしたものは1年もしないうちにたやすく忘れますが、自分で苦労して作ったもの(あるいは手を入れたもの)は、詳細は覚えていなくとも「どこで苦労したか」は思い出すことが出来ますし、「失敗したことは二度としない」でしょう。他で似たような現象が出れば「これもあれと同じではないだろうか?」と推測が出来ますから、確実に新しく考えなければならない範囲が狭くなるのです。
ちなみに当時は機種毎に動作するプログラムが違うのは勿論、その機種そのものの絶対数も少なく、その機種用の出来合いプログラムは少なかったです。他機種用 ですらもそのプログラムは大変に貴重で、自分の機種用に移植することでいろいろな勉強が出来ました。つまり、ソースそのものは役立たずだけど、そのソース を読解することで「どのような仕組みで動くのか」を自分で理解することが必要であり、そこから得たことの方が何倍も価値がありました。

現在に置き換えるのであれば、PHPだろうがJavaScriptだろうが、プログラムの仕組み・考え方に本来の価値があるのであって、同じ言語であっても自分の環境に適合しているかどうかも分からないソースコードを引っ張ってくるだけではダメだということです。逆に言えば考え方さえ分かれば、素のPHPプログラムを提示されても、CakePHP用やsymfony用に作ることが出来たり、Windowsアプリや今はやりのiPhoneアプリにだって出来る、というわけです。

こういった「知識」をつけることは、受動的では覚えないと思います。「能動的」に経験をしなければ身につかないと思います。まあこれが「技術」というものだと思います。「(動く)ソースを貼り付けないから不親切だ」的な書き方をされたことが過去あるのですが、私としては、そうすることで「技術を習得することを妨げてしまう」方が不親切ではないかと思います。まあ、この辺は過去苦い経験があったから言えるのかもしれませんが。

CakePHPフォーラムを見ていると、(動く)ソースを要求していると思われる書き込みが最近増えているように感じます。常連さんはもちろんそんなことはないですが、CakePHPどころかプログラム経験が浅いと思われる方は、大抵ソースが出てくるのを待っているような書き込みをされます。自分でやらなくて済みますから、そりゃその方が楽だとは思います。ですが、それではいつまでも「技術」を学ぶことが出来ないと思います。かといって相手も困っているのでしょうから、「考え方」だけは提示して、後は自分で考えて作ってもらう…というのが私の方針で、その質問者が憎たらしいとか、困らせてやりたいとか、そんなことは考えたこともありません。そう思ったら書き込まないだけなので。
ただ、以上の理由からソースそのものよりも「考え方」が一番大事なのだということ、そしてその考え方について「自分も考え、行動してみる」ことが技術に繋がることを私は主張したいと思います。

ちなみに、CakePHPをはじめとするフレームワークを「簡単だから」という理由だけで導入する初心者の方がいるようですが、言わせてもらうと「素のPHPで同じ機能のものを作れる人が、作業を簡素化するために使う」のは大変に有効だと思いますが、「プログラムを覚えることなく作れる」ものではないと言いたい。もしPHPをあまり知らなくても、知る努力はして欲しいものです。それが出来ないのであれば、誰かに作ってもらって対価を払う方が、自分のためにもみんなのためになります。

そして、アドバイスしてくれた人には、必ずお礼をして欲しい。
最近質問しっぱなしでそのままのものが多すぎます。
少なくとも、その人が限りある時間の一部を割いてくれたのですから、それが自分の求めていた情報ではなかったとしても、感謝の一言くらいは。せっかくの情報がコピペと同じ扱いでは、可哀想すぎです。
まあ私にはどっちでも良いですけど、せめて他の人には必ずお願いします。

【連想くん】Zend Optimizerが動かん…

shunさんからいただいたアドバイスを元に、またサーバ設定を見直しています。
Zend Optimizerも手に入れ、インストールしてみたのですが…
動かん(;-;)。
Apacheを再起動して、1回目のページは開くのですが、2回目以降は白くなってしまいます。
libcのバージョンが合わないとか、apcとの相性とか、そういった理由でしょうか?
時間をかけるのもアレなので、とりあえず元に戻しました。

アプリケーションレベルでは、前から気になっていた「wikipediaのリンクが壊れている」件の修正を試みました。テストプログラムで問題なさそうなので、今晩にでも実装します。

理由は、googleから受け取ったレスポンス中のURLで、なぜかwikipediaのリンクだけ、urlencode中の「%」が「%25」になっているんです。つまり2回urlencodeしてしまっている状態です。Googleはwikipediaが嫌いなんでしょうか?(^^;;;

Ajaxがらみも、最初のリクエストのかけ方を少し直したいです。Fiewfoxだと、ページを一気に表示してくれないで、Ajaxの展開を都度やってしまうので、動きがイマイチです。SafariだとAjaxは後回しになるんですが。document.bodyにonLoadイベントを追加してやれば解決できるかな?