baserCMSで管理画面を即席で作る

baserCMS Advend Calendar 2012の25日目です。前日は、柴田さんの「開発スピードを爆速にしたいプログラマへ!baserCMSのテーマ16選」でした。
なぜか流れ的に僕が最後のトリを締めさせて頂くことになりました。

Advend Calendarでいろんな方の記事を見てみると同じ技術対象でも求めているモノが違ったり、立場の違い(PG, デザイナー, ディレクター, サイト運営者等)で違う解釈やアイデアがあったりしてすごく面白いと思いました。僕ごときがトリで締めるのは恐縮ですが、最後なんでまずは感想を。

Advent Calendarの感想

12/17(月) 「オレカスタマイズテーマから新しく配布されている公式テーマに変更してみた実録」隆行 権藤さん

テーマコンテストで色々テーマが増えてきましたが、時間がなくてまだ全然試してなかったのですが、試したい気にさせていただきました。

12/18(火) 「CSSフレームワークのFoundation 3をbaserCMSのテーマに対応させました(一部のみ)」 Kaneuchi Toruさん

Twitter bootstrapは知っていましたが、Foundation 3というCSSフレームワークは知りませんでした。もう少し調べて良ければ採用したいなーと思います。

12/19(水) 「baserCMSとお仕事と僕」リュウジ エガシラさん

今までのご苦労とbaserCMSの歴史で楽しく拝見させて頂きました。
コミュニティとってはすごく重要な話だと思います。フォーラムなどで機能追加要望を出してもbaserのシンプルという主義に合わなくて拒否られるのを見たりします。提案した人からすればそれはすごく納得いかないのではないでしょうか?ご本人の実益もあるでしょうが、それでも客観的に判断してよかれと思って提案されている訳ですから。
でも「baserCMSが何故生まれてどういった思想がある」のかが分かれば納得ですね。そういう意味で作者の思想を知るのは大事だと思います。

12/20(木)「自分のパソコンに開発環境を作る」松岡 謙治さん

僕もほぼ同じ環境(Virtual box + Ubuntu)で開発とかしていますが、Sambaとか使っていなかったので参考になりました。

12/21(金)「デザイナーさんでも出来る! baserCMS のプレフィクス認証機能を使ったログイン画面の作り方」Kyo Hata さん

今までAuthコンポーネント駆使して自前で作りまくってました。目からウロコでした。要件に合えばぜひ使いたいテクですね。

12/22(土)「baserCMSのプラグイン作り(フック処理)を楽しんでみましょー。」arataさん

個人的にすごく参考になりました。実は、この記事がなければ今日の僕の記事はプラグインに関して書くつもりでした。でも僕が書くとなんか仕組みとかダラダラ晒してみたいな感じになっちゃうので、プラグインを普段から書かれている人の記事はさすがにより実践的で価値があると思いました。

12/23(日)「baserCMSのライセンスのステキな関係 」Sunao Kiyosueさん

OSSのライセンスは僕がこの業界に入る前に(僕は異業種からなんの因果かIT業界に迷い込んでしまった人間なので)、Linuxとかストールマンなどのことを色々調べている時に書籍で勉強した経験があって「コピーレフト」って言葉を久しぶりに目にしてすごく懐かしかったのです。僕自身は経営をやっておりませんのでそれほどライセンス自体を意識したことはありませんでしたが今後はもっと意識したいと思い直しました。

12/24(月) 柴田篤志さん

Croogoは知っていたんですがWildflowerっていうのは知りませんでした。ちょっと調べてみようかと思います。(いや浮気したい訳ではなくて。。。)
書かれていることはなにかシンパシーを感じました。案件自体それほど多くないですがあっても短納期で予算もあまりない案件が多くて、それほど意識が高くなく平均的なスキルしかないサラリーマン的なプログラマーだと厳しい開発の現場になってきたように感じます。
受注の段階でもう上から流れてきた案件で技術が決まっていたり、工夫をして工数を短縮できる余地が無く、そのままやって残業繰り返してプライベート時間が無くなった挙句、予算割れとかどうしようも無い時もありますが生産性をあげようとフレームワークを絞ったりCMSのようなものを用意して武器を持とうとすることは重要だと思います。
プログラマーでも費用対効果とか利益的なことを考えないといけない時代になってきたのかもしれません。

管理画面を即席で作る

では、僕の記事に入りたいと思います。1回目はかなり堅苦しい記事を書いてしまったので今回は、ゆる〜い感じにしたいと思います。

一応、システムはDBからデータを取得したり動的な画面を生成したりはしますが管理画面がないケースがあったりします。それは運用をお客さんの方では行わず技術者がDBをphpMyAdminなどでデータを追加したり編集したりして行うような場合です。このようなシステムっていうのはほぼ予算の関係で優先度を付けて機能を削ってしまった挙句こうなってしまうか、納期的な都合でリリースを分けてファーストリリース時にはそうなってしまうケースが多いです。

ですが納品時に「やっぱり、管理画面はないとつらいよねー。編集したりしたいんでそら必要ですよ。(でも金は無いけど)」みたいな不条理なことが起こってしまったりします。その際は予算追加とか「言った言わんかった」みたいな感じになるのですが、実経験をもとにそこをbaserCMSで乗り切った話をします。

てっとり早く管理画面とマスタメンテを作る方法は無いか?
そうだマスメンはCakeのbakeで作ろう!
管理画面はどうする?認証系は?結構面倒なんだよね。最悪basic認証にする?
そうだbaserCMSを使おう!

みたいな感じの流れからbaserCMSを管理画面専用のアプリケーションとして用意することにします。

ステップ1

ドキュメントルートに通常どおり、htmlファイルなどを配置したい場合を参考にbaserCMSを用意します。

ディレクトリ構造は下記のようになります。

ユーザからはhttp://hostname/base/adminみたいなURLでアクセスできるようにする訳です。baseの部分は好きにしていいと思います。admin/adminってなるのが嫌でbaseってやってますがなんでもいいです。管理画面が欲しいっていうのが目的なだけなので。

ステップ2

インストーラに従ってインストールします。

ステップ3

必要な機能や画面をOFFにします。

システム設定で携帯とスマホの機能をOFFにします。スマートURLはmod_rewriteを使える環境ですとONにします。

プラグインを全てOFFにします。

ページを全てOFFにします。

ステップ4

シンプルなスケルトンに変更する

Not Foundの画面は下記のようになるので、必要に応じて画面を編集してやる必要がありますがここでは割愛せて頂きます。

ステップ5

管理者ユーザがアクセス出来るページを制限する為にアクセス制限設定を編集します。

新しく作成したアプリケーションのマスタ画面のみアクセス出来るよう権限を新規作成します。

必須ではないですが、プラグインは使用しないのでプラグインのテーブルは削除します。まだまだ要らないテーブルもありますが無難なところで妥協しています。

ステップ6

管理ユーザを1つ増やし、ステップ5で作成したアクセス制限を適用します。

ステップ7

作成したユーザでログインし直します。

どうでしょう!CMS機能が無いスケルトンな管理画面が出来ました。ここからはCakePHPのスキルがあれば拡張していけると思うので割愛しますが、サブディレクトリに管理画面を作成しマスタメンテに必要な機能だけを作って、てっとり早く要件を満たすことができました。数時間で実装して「おおー!すごいですね」「いやー、こんなんでよければ。。。」みたいな感じになります。

baserCMSというよりCMSの機能をOFFにしてますのでただのbaserということになりますが、別にサブディレクトリにしなくてもコレをメインにしてその上に独自アプリケーションを構築していけるなど、ケース・バイ・ケースでいろいろやれます。

baserCMSを別にCMSとして使わなくてもいいというかなりイレギュラーな使い方ですが、色々試せること自体がbaserCMSの良さでもあります。開発者としては様式美みたいなものがあって別のフレームワークとか技術を使ったシステムの公開ディレクトリ内にサブディレクトリ化したCakePHP1系のCMSの管理画面があるキメラっぽいシステムは作りたくはないですが、予算、工数、手間という制約があるなかでこれが求められるベストに近いのなら試してみる価値はあると思います。

PHPMatsuriの発表で、CMSとしてはCakePHPが1.2でも問題ないが開発者としては不満だと言いましたが、CakePHP2に対応っていうのもCMSとして使うだけではなく再利用可能な足場的な使い方もあるのでベースとなる部分が最新のCakePHP2っていうのは重要になってくるんですね。開発で使いたいのはCakePHP2の最新機能だったりしますので。

最後に

Advent Calendarに人生初でしかも2回も投稿するといった経験をしましたが、これまではスキルのめちゃくちゃ高い方が惜しげも無くノウハウを晒すというようなイメージを持っており自分が参加することがあるなど夢にも思っていませんでしたが勇気を出してやってみて良かったと思います。自分がたいしたことが無いと思っているノウハウでも他人にとってみれば価値のあることもあるかもしれません。

来年もAdvent Calenderがあればもっといろんな方の知見やノウハウを知りたいです。今回参加していない方もぜひ参加されることを期待しております。

有難うございました。

なぜbaserCMS?

遅ればせながら始まりましたbaserCMS Advent Calender 2012の1日目を担当させて頂く@kanjihtmtです。(12/1ではないですが)

僕は最近baserCMSのコミュニティに参加したばかりですので、いきなりAdvent Calenderに参加しても「誰?」みたいに思われるでしょうから、どんな活動をしているかをまずは紹介させて下さい。

10月3日にありました「PHP Matsuri 2012」に参加させて頂き、@ryuringさんや@itm_kiyoさんと徹夜でbasercamp(CakePHP2対応版-baserCMS3の構築)を行いました。(眠たかったー) その後も引き続き空いた時間を見つけてbasercampをしております。 baserCMS3ですが年内を目処にしておりましたが、私達3人とも本業が忙しく予定どうりに進んでおりません。なるべく早くリリースが出来るように頑張りたいと思ってはおりますので、皆様は安定している2系を使いつつ温かくbaserCMS3を見守ってやって下さい。よろしくお願いします。

前置きはこれぐらいにして始めます。

私とbaserCMS

僕がbaserCMSを使い始めたときはまだ1系でしたが、日本で導入実績が多く人気のあるフレームワークを採用した本格的なCMSはありそうでなかなか無く(MVCでないと気持ち悪くなる病の僕は)baserCMSを見つけた時は「きたコレ!」と即採用し、かなりハマリながらbaserCMSでなんとかサイトを作った覚えがあります。(最初のサイトは今思うとバットノウハウ満載でした)

PHP Matsuri 2012」の発表(Ustreamにありますのでご興味があればどうぞ)でも少し説明しましたが、僕が気に入っているbaserCMSの特徴はappフォルダにbaserのソースをコピーして編集すればそちらが優先されて、コアに手を入れずカスタマイズが出来るという点になります。これはデザイナーの方などは「何がうれしい?」と思われるかもしれませんが、プログラマー的には重要です。(ですよね?)
例えば、EC-CUBEなどではカスタマイズ可能なサブクラスが用意されてますが、その手法はカスタマイズする側からすると正直面倒くさいですし、エレガント感がないように感じます。baserCMSの作りですとbaserのコアをバージョンアップする際に、独自にカスタマイズした部分ときっちりコアが別れてます(設定ファイルやキャッシュなど一部は除く)ので差分を取ったり更新(アップロード)しないフォルダやファイルを間引いたりすることなく、まんま上書きできるなどが良いところになります。(もちろんバージョンアップによって動かなくなるかどうかテストする必要はあります)

baserCMSを始めた時に気に入らないところが1つありました。実はbaserCMSをやり始めた時にはCakePHPの経験が無かったため、CakePHPの書籍などをひと通り読んだりして知識をつけた後で本格的にbaserCMSを始めました。そういった過程を経て改めてbaserCMSのフォルダ構成なんかを見ると「なんだこりゃ?」「そもそもなんでこんな作りになってんの?」みたいな教科書どうりでない特徴に不満が出てきました。この先入観を持った人は僕だけじゃないはずです。(CakePHP辞典やKtai Libraryで有名なMASA-Pさんもブログで同じようなことを書かれてますし)
もし僕がbaserCMSのオリジナル開発者で、スクラッチからbaserCMSを作ったとしたら、プラグイン化してそれをCakePHPから読み込んで(RouterなどでURLを変えたりしつつ)CMS機能を実現するような構成にしていたことでしょう。こうしなかった良さは後で説明します。

正直、「うーん。外国製だけどやっぱCroogoにしようかな?」と浮気心が芽生えだしたところに、ふとしたきっかけでbaserCMSのソースを読んでみる気になりました。なんでこんな構成になっていてどんな変態的なことをやってbaserよりappの方を先読みさせているのか?と言ったことを知りたくなったのです。

baserCMSのブートストラップ

CakePHPではアプリケーションの初期化処理をフロントコントローラと呼ばれるindex.phpファイルからbootstrap.phpファイルというファイルを読み込むことで実現しています。このbootstrapファイルは主にConfigure系の設定とか定数群(paths.phpに外出ししてますが)などを記述します。このファイルを読み込みDispacherクラスが起動されるまでの処理を追うことでbaserCMSのイケている特徴の秘密が分かるようになります。

CakePHPのcoreのbootstrapをincludeした後に、Configureクラスのインスタンスを生成します。正確に言えばこのクラスはデザインパターン的に言えばシングルトンになっていますのでオブジェクトが生成されていない場合のみインスタンスが生成されます。

Configure::getInstance();

インスタンス生成時に密かにプライベートメソッドをコールします。このメソッドがキモです。

<?php
 624     function __loadBootstrap($boot) {
 625         $modelPaths = $behaviorPaths = $controllerPaths = $componentPaths = $viewPaths = $helperPaths = $pluginPaths = $vendorPaths = $localePaths = $shellPaths = null;
 〜 省略 〜
 671             if (!include(CONFIGS . 'bootstrap.php')) {
 672                 trigger_error(sprintf(__("Can't find application bootstrap file. Please create %sbootstrap.php, and make sure it is readable by PHP.", true), CONFIGS), E_USER_ERROR);
 673             }
 674
 675             Configure::buildPaths(compact(
 676                 'modelPaths', 'viewPaths', 'controllerPaths', 'helperPaths', 'componentPaths',
 677                 'behaviorPaths', 'pluginPaths', 'vendorPaths', 'localePaths', 'shellPaths'
 678             ));
 679         }

Configure::buildPathsによってモデルやコントローラ等のパスを設定しているんですね。baserはこの仕組みになんらかの方法でフックしてパスを追加しないといけません。

それでは、671行目のincludeに注目しましょう!app/config/bootstrap.phpを読みだしています。そちらのファイルでbaser独自のbootstrap(baser/config/bootstrap.php)を読み出しにいきます。

<?php
 33 /**
 34  * Baserパス追加
 35  */
 36     $modelPaths[] = BASER_MODELS;
 37     $behaviorPaths[] = BASER_BEHAVIORS;
 38     $controllerPaths[] = BASER_CONTROLLERS;
 39     $componentPaths[] = BASER_COMPONENTS;
 40     $viewPaths[] = BASER_VIEWS;
 41     $viewPaths[] = WWW_ROOT;
 42     $helperPaths[] = BASER_HELPERS;
 43     $pluginPaths[] = BASER_PLUGINS;
 44     // Rewriteモジュールなしの場合、/index.php/css/style.css 等ではCSSファイルが読み込まれず、
 45     // $html->css / $javascript->link 等では、/app/webroot/css/style.css というURLが生成される。
 46     // 上記理由により以下のとおり変更
 47     // ・HelperのwebrootメソッドをRouter::urlでパス解決をするように変更し、/index.php/css/style.css というURLを生成させる。
 48     // ・走査URLをvendorsだけではなく、app/webroot内も追加
 49     $vendorPaths[] = WWW_ROOT;
 50     $vendorPaths[] = BASER_VENDORS;
 51     $localePaths[] = BASER_LOCALES;
288 /**
289  * テーマヘルパーのパスを追加する
290  */
291     $themePath = WWW_ROOT.'themed'.DS.Configure::read('BcSite.theme').DS;
292     $helperPaths[] = $themePath.'helpers';

内容的にはbaserのpaths.phpで定義した定数を埋め込んでますがこれだけです。このまま先ほどのCakePHPのコアのConfigure::buildPathsメソッドが実行されればCakePHPのデフォルトパスにbaserのパスがマージされます。

ではパスの内部がどうなっているのかダンプしてみましょう。

<?php
[modelPaths] => Array
 (
     [0] => /your/to/path/docroot/app/models/
     [1] => /your/to/path/docroot/baser/models/
     [2] => /your/to/path/docroot/app/
     [3] => /your/to/path/docroot/cake/libs/model/
 )
[behaviorPaths] => Array
(
    [0] => /your/to/path/docroot/app/models/behaviors/
    [1] => /your/to/path/docroot/baser/models/behaviors/
    [2] => /your/to/path/docroot/cake/libs/model/behaviors/
)
[controllerPaths] => Array
(
    [0] => /your/to/path/docroot/app/controllers/
    [1] => /your/to/path/docroot/baser/controllers/
    [2] => /your/to/path/docroot/app/
    [3] => /your/to/path/docroot/cake/libs/controller/
)
[componentPaths] => Array
(
    [0] => /your/to/path/docroot/app/controllers/components/
    [1] => /your/to/path/docroot/baser/controllers/components/
    [2] => /your/to/path/docroot/cake/libs/controller/components/
)
[viewPaths] => Array
(
    [0] => /your/to/path/docroot/app/views/
    [1] => /your/to/path/docroot/baser/views/
    [2] => /your/to/path/docroot/
    [3] => /your/to/path/docroot/cake/libs/view/
)
[helperPaths] => Array
(
    [0] => /your/to/path/docroot/app/views/helpers/
    [1] => /your/to/path/docroot/baser/views/helpers/
    [2] => /your/to/path/docroot/themed/demo/helpers
    [3] => /your/to/path/docroot/app/
    [4] => /your/to/path/docroot/cake/libs/view/helpers/
)
[pluginPaths] => Array
(
    [0] => /your/to/path/docroot/app/plugins/
    [1] => /your/to/path/docroot/baser/plugins/
)
[vendorPaths] => Array
(
    [0] => /your/to/path/docroot/app/vendors/
    [1] => /your/to/path/docroot/vendors/
    [2] => /your/to/path/docroot/
    [3] => /your/to/path/docroot/baser/vendors/
)
[localePaths] => Array
(
    [0] => /your/to/path/docroot/app/locale/
    [1] => /your/to/path/docroot/baser/locale/
)
[shellPaths] => Array
(
    [0] => /your/to/path/docroot/cake/console/libs/
)

配列のインデックスが優先度の高い順(app, baser, cake)になっているのが分かりましたでしょうか。実は、baserCMSはめちゃくちゃ変態なことをやっている訳ではなく、かなりCakePHPの機能をそのまま素直に使って独自性を出しているのが分かります。
(baserCMS3ではCakePHPAPIが大きく変わってますがApp::buildを使って同じようなことを実現します)

以上の仕組みによりbaserCMSのファイルよりappが優先するようになっています。正直この仕組みを知った時からbaserCMSがもっと好きになりました。「あの人イケてるんだけど、変態らしいわよ。ありえなーい」だったのが実は誠実だったことが分かったら、そら「惚れてまうやろー!」(チャンカワイ) って感じですね。あれ?なんやそりゃ?って感じですか?(笑)

Beyond CMS (CMSを超えてゆけ!)

baserよりappを先読みする仕組みは分かったが、なぜbaserCMSの構成がこうなっているのか?という疑問を解決していないじゃないか!と思ったあなたは鋭いです。

今の構成にしたのはオリジナル開発者の@ryuringさんの最初から狙った戦略的なものなのか、色々試していった結果、偶然現在の形に辿り着いてしまったのか分かりませんがいずれにせよ副産物を生み出してしまったのです。baserCMSと聞いてCMSに反応するのではなくbaserの方に注目して欲しいです。そもそも何でbaserなんて名前付けてるのでしょう?

みんなbaserCMSの真価に気付こうよ! baserCMSというのはその名のとおりCMSではありますが実はプラッガプルなプラットフォームです。いやコンテナと言ってもいいかもしれません。図にすると下記のようになります。

図にしてみることで「羊の皮を被った狼」っぷりが分かりますでしょうか?

CMSに独自に機能を追加したりカスタマイズをしたいのでしたら、appにどんどんコピーしたり新たな機能用のファイルを追加していって下さい。それ以外にもプラグインでも拡張性があります。
例えば、baserCMSの独自のプラグインを作ったり誰かが作ったプラグインgithubなどから見つけたとします。ある案件にはそれはベストマッチでしたが別の案件では似て非なる機能が必要になってきました。さあどうしますでしょうか?baserのプラグインをコピーしてappに入れるだけです。プラグインも大丈夫です。コアのプラグインに手はいれずにapp内のコードだけの修正になります。githubでFolkしてどんどんカスタマイズしていっても構いません。

実は個人的にbaserCMS3ではこの流れを活性化しようと考えています。たくさんのプラグインがあってそれを独自に拡張したりされたり、コーポレートサイトだけに留まらないことが容易に出来るようになる。強いていえばWordpressのようなことをもっとソリッドな方法や規模でやれるというか道のりは長いでしょうが今後はそういう風になっていけばよいと思っていますし、がんばりたいです。

最後に

何年も開発の現場にいますがソフトウェア開発というのは、再利用可能なモジュール群によってプログラミングは必要無くなってくるなどとMDAとか時代によって言葉を変えながら昔から言われ続けていますが全然そんな兆しはなく本当に泥臭いものです。ある程度フレームワークやライブラリーなどにより開発が楽になったり高品質なものを作ることはできるようになっていますが、果たしてプログラマーの作業自体は楽になっているのでしょうか?

これまでそういった厳しい現実と向かいつつ、拡張性や柔軟性がありながらも統一感のあるやり方でサイトを構築したり生産性を上げる方法を模索していました。baserCMSは同じくプラッガブルな環境のXOOPSなどとは違ってフレームワークを使って汎用性を出しつつ尚且つシンプルである1つの理想形を作っていると思っています。ぜひ新たに違った視点からbaserCMSを見なおしてみることをお勧めいたします。

ここまで読んで頂きありがとうございました。

Lithium完全ガイド

しばらくご無沙汰していたら、Lithiumもマニュアルが充実してきましたので、本格的に読んでみようと思います。以下翻訳記事です。

最新のRAD技術でタイトなアプリを書くことに興味があるのでしたら、PHPで最もRADなフレームワークであるLithiumは歓迎します。

このガイドは、Lithiumは何が出来き、最も重要である、あなたにとって何ができるか知りたいと思っているPHPプログラマーのためのものであり、Lithiumフレームワークの全体像を伝えることを意図しています。読み終えれば、MVC、データアクセス、認証、権限、バリデーション、国際化、レイアウト、単体テストのやり方がわかるようになります。分かりやすくいうと、Lithiumスタイルのラピッドアプリケーション開発に関するすべてを学ぶことができます。

前置きは十分です。それでは始めましょう。

貢献

このプロジェクトは現在作業進行中で私たちはあなたの支援を必要としています。Lithiumユーザの初心者も経験者も楽しんで参加してくれることを歓迎しています。もし何かに困っていたら解決方法を記載してください。そうすれば他の人達が簡単に解決方法を見つけることができます。あなたが支援できるたくさんの方法があります。

  • 現在のドキュメントを充実させるためのコード例
  • 概念の説明をわかりやすくするリストや表
  • よく使うプロセスを説明したラフなメモ
  • 訂正作業(タイポ、間違いなど)
  • 翻訳

もし、支援したいのでしたら、GitHubでプロジェクトを簡単にフォークしたり、新しい課題をオープンしたり、コアチームメンバーの一人と連絡をとったりしてください。Freenodeの#li3で我々に質問したり、メール(anderson.johnd at gmail dot com)で連絡をとったりできます。

FAQ

テンプレートにPHPショートタグを使ってますか?非推奨なのではないですか?

LithiumのテンプレートはPHPのショートタグ(つまり<?=)を使っていることに気づいたかもしれません。心配しなくて構いません。これらは実際にPHPで解釈されることはありません。Lithiumは内部のテンプレートコンパイラを使ってこれらのタグを<?php echo ... ?>に変換しています。自動的にコンテンツをエスケープすることができるので、XSS攻撃に関して心配する必要がありません。また、コンパイラはヘルパーなどから渡されたコンテンツをちゃんと認識するので、2重にエスケープされてしまうことはありません。もっと詳しく知りたいのでしたらテンプレートシステムのドキュメントをチェックして見て下さい。

いろんな箇所でスタティックを使ってますが、テストしくくないでしょうか?

厳密に言えば、スタティックはテストしやすいのです。Lithiumフレームワークだけでなく、フレームワーク上に構築されたアプリケーションもテストし易くできるよう関数プログラミングからいくつかのコンセプトを拝借しています。これらのコンセプトの幾つかを理解する為には、まず2、3の用語を定義しなければなりません。

状態(ステート)

ソフトウエアを記述するにあたって本質的な部分になります。Wikipediaでは、「システムの様々な条件を計測したスナップショット」として"状態(ステート)"を定義しています。PHPアプリケーションの構築では、コンテキストやスコープによって異なりますが、index.phpを最初にロードした時にGETやPOSTデータ、$_SERVERや$_ENVに入っているシステム情報、その他のスーパーグローバル変数といったスクリプトをリクエストする為に使われるデータとして"状態(ステート)"が定義されます。"状態(ステート)"は日付や時間といった明示的ではないものも含まれます。メソッドでは、ステートは引数や、メソッドがバインドされたオブジェクト内のもの(例えば$this)、定義されたその他データやグローバルスコープで利用可能なデータになります。

副作用

コンセプトは状態(ステート)密接に関連します。副作用はメソッドや他のルーチンが自分自身のスコープがにあるものに影響する変化になります。副作用は、グローバル変数、オブジェクトのプロパティ(たとえば$this)の値の変更や、データベースやファイルシステムのデータの更新、参照で渡されたパラメータ値の変更や出力のechoのようなものが含まれます。

可変性

可変性は、変更可能はもののクオリティになります。PHPにおける可変性の基本的な具体例は、変数やオブジェクトのプロパティになります。それに対して、不変性とは変更できないもの(例えばグローバル定数やクラスレベルの定数)になります。それゆえに可変性のある状態(ステート)は上記で述べたように変更される可能性があるアプリケーションの状態(ステート)の要素であります。これは当たり前のように思えるかもしれませんが、あとで非常に重要なことだと気づくと思います。

参照透過性

可変性

class A {
    public static function foo() {
        // return some calculated value
    }
}

class B {
    public static function bar() {
        $result = A::foo();
        // perform some calculation on $result
        return $result;
    }
}
class B {
    public static function bar($dependency) {
        $result = $dependency::foo();
        // perform some calculation on $result
        return $result;
    }
}
class Dispatcher extends \lithium\core\StaticObject {

    // ...
    protected static $_classes = array(
        'router' => 'lithium\net\http\Router'
    );
    // ...
}
$router = static::$_classes['router'];
$result = $router::process($request);

この例では、B::bar()の呼び出しの全てはA::foo()の呼び出した結果になります。

Lithiumをインストールしたら次のようなfatalエラーが出ました。

Function name must be a string in .../lithium/util/collection/Filters.php on line ...

これはeAcceleratorをインストールしている為に起こるエラーです。eAcceleratorはPHP5.3ではサポートされないオプティマイザー/オペコードキャッシュになります。解決法はeAcceleratorを無効にして、サポートされているAPCやXcacheのようなアクセラレータに切り替えることです。
もし、eAcceleratorをインストールした覚えがないのでしたら、eAcceleratorをプリバンドルしているMAMPで動かしているのではないでしょうか。この場合は、Homebrewを使ってみて下さい。うまくいくと思います。

なぜlibrariesフォルダはrootディレクトリとappディレクトリの両方にあるのですか?

ライブラリが1つのアプリケーションのみで使われる場合はapp/librariesディレクトリにライブラリーを置きます。一方、1つ以上のアプリケーションでライブラリーを共用する場合、管理が簡単になるようlibrariesディレクトリは提供されています。複数のアプリケーションのapp/librariesディレクトリーにライブラリーを重複させないで済むためディスクスペースも節約できます。

外部アプリケーションでLithiumを使う


参照元appendices/using_in_external_applications.wiki

Lithiumは非常に柔軟ですので、コアフレームワークをいくつかのアプリケーションの環境で起動することができます。そして必要な機能だけを使うことができます。まず、Lithiumが正しくインストールされていることを確認して下さい。

Lithiumのクラスを使う為には、ライブラリを管理しているクラスをただロードし、Lithiumのライブラリー自体を次のように登録することです。

include "/path/to/classes/libraries/lithium/core/Libraries.php";

lithium\core\Libraries::add('lithium');

"path/to/class"はinclude pathと相対パスであるかもしれません。

CakePHPと連携する

CakePHPはこの設定を追加できるapp/config/boostrap.phpという便利な場所を提供しています。それに加えて、Lithiumの(モデルやコントローラといった)クラスをCakephpの相当するクラスと一緒に使いたいのでしたら、次の行をboostrap.phpに追加することができます。

define("LITHIUM_APP_PATH", dirname(__DIR__));
lithium\core\Libraries::add('app', array('bootstrap' => false));

これを正しくappディレクトリをLithiumのクラスローダに設置するとappのデフォルトのbootstrapファイルを使えなくすることができます。CakePHPのクラスで名前空間のあるappクラスをインポートしたりLithiumCakePHPのモデルや他のクラスを並行して使えます。LITHIUM_APP_PATHはLibraries.phpがincludeされる前に定義するのを忘れないで下さい。

データコネクションを定義する

Lithiumのデータレイヤを使うつもりですたら、LITHIUM_APP_PATH/config内にconnections.phpを作成する必要があります。動的にコネクションを定義するのでしたら、このファイルを空に出来ます。ですが、規約によりこのファイルはデータベースとWebサービスのコネクションを設定する為に使われます。これを行う方法の詳しい情報はlithium\data\Connectionsを見て下さい。

Zend Frameworkと連携する


今日は、appendicesの2つを読みました。あと残りはFAQだけです。

参照元appendices/integrating_zend_framework.wiki

Lithiumアプリケーション内でZendフレームワークを使う為には、まずapp/config/bootstrap.phpにいくつかの設定を追加する必要があります。

設定

下記の設定は、incubatorよりも先にtrunkからチェックアウトしたものを追加しています。pathはtrunk/library/Zendを直接librariesディレクトリにおいた場合 、うまくいかないことに注意して下さい。

Libraries::add("Zend", array(
    "prefix" => "Zend_",
    'path' => '/htdocs/libraries/Zend/trunk/library/Zend',
    "includePath" => '/htdocs/libraries/Zend/trunk/library',
    "bootstrap" => "Loader/Autoloader.php",
    "loader" => array("Zend_Loader_Autoloader", "autoload"),
    "transform" => function($class) { return str_replace("_", "/", $class) . ".php"; }
));

Libraries::add("Zend_Incubator", array(
    "prefix" => "Zend_",
    "includePath" => '/htdocs/libraries/Zend/incubator/library',
    "transform" => function($class) { return str_replace("_", "/", $class) . ".php"; }
));

使いかた

設定を追加しましたので、全てにおいて重要なステートメントであるuseを「use \Zend_Mail_Storage_Pop3」のように追加することでZendフレームワークのクラスにアクセスできます。

namespace app\controllers;

use Zend_Mail_Storage_Pop3;

class EmailController extends \lithium\action\Controller {

    public function index() {
        $mail = new Zend_Mail_Storage_Pop3(array(
            'host' => 'localhost', 'user' => 'test', 'password' => 'test'
        ));
        return compact('mail');
    }

}

Zend_Mailクラスの使い方に関してはZendフレームワークから提供されているメールドキュメントを参照して下さい。

グローバル化チュートリアル


今日で、公式のdraftsはappendices以外は終わりです。ドキュメントが少ないとはいえ、英語にはかなり苦労しましたのでこのぐらいのボリュームがかえって僕にはよかったのかもしれません。
ここまでくれば、APIドキュメントを読んだりソースを読んだりして実例から学んでいくしかないと思ってます。Lithiumには豊富なテストコードもありますし、コアのソース自体が恐ろしく綺麗ですので(名前空間のせいかPHPじゃないみたい)、これから実践で使ってもソースを読めばなんとかなるかなという感じがします。(個人的にはmongoDBを早く習得したいです)

参照元07_globalization/globalization.wiki

導入

グローバル化(g11n)は、国際化(インターナショナライゼーション)と地域化(ローカライゼーション)に見られる特徴を組み合わせた言葉になります。グローバル化は1つのソフトウェアを異なる言語、文章、地域の言語の違いに対応させることができます。
このチュートリアルでは、Lithiumグローバル化されたソフトウェアに対して取っているアプローチを説明します。
Lithiumにおけるグローバル化はコアフレームワークの機能になり、Lithiumは最初からg11nを念頭に入れて開発されています。

g11nをブートストラップさせる

アプリケーションでg11nを利用するには、まずLithiumのブートストラッププロセスでg11nをロードさせる為にboostrap.phpファイルのg11nの行をコメントアウトします。g11n.phpファイルに屈折、音訳、ローカライズされたバリデーション、そしてローカライズされたテキストがどのようにロードされるかといったアプリケーションのグローバル化ルールが記述されます。

require __DIR__ . '/bootstrap/g11n.php';

デフォルトのロケース設定を作成する

現在のロケールに対する設定(利用可能なロケール)はg11n.php内で環境設定として保持されます。これは、ロケールの切り替えや設定、g11nに関する設定を取得する中心部として利用できます。
ここでどのロケールがデフォルトとして最適なロケールであるかを決定できますし、アプリケーションがサポートするロケールをも決定できます。下記のように環境変数を定義することができます。

Environment::set('development', array(
    'locale' => 'en', // the default effective locale
    'locales' => array('en' => 'English') // locales available for your application
));

ロケールを検知する

効果的なロケールは、グローバル化されたアプリケーションのコンテンツとクライアントによって優先されるロケールに依存します。
しかし、どのように優先ロケールを識別すればよいのでしょうか?

一般的なアプローチ

どのロケールを利用するかを推測するには2つの主な方法があります。1つ目は、リソースの名前(リクエストURLの一部)に基づいて推測することができます。もう一つは、クライアントから受け取った情報(ユーザエージェント情報など)に基づいて推測することができます。
できるだけ分かりやすい処理にする為に、最初のリソースをインスペクトすることを推奨します。そして必要なクライアント情報を当てにします。

テクニカルな実装

効果的にロケールを検知するには
コントローラのリクエストでは、Localeクラスが(ユーザエージェントが送る)Accept-Languageヘッダー内に含まれているロケールをパースします。コンソールのリクエストでは、優先されるロケールが環境設定から取得されます。

g11nフィルター

g11n.phpがブートストラッププロセスにおいて行う最後の1つはコンソールコマンドとコントローラのアクションの両方のディスパッチにフィルターを適用することです。