【KtaiLibrary】バージョン0.4.1をリリースしました

Ktai Libraryの0.4.1をリリースしました。
今回はバグフィックスがメインになっています。

Lib3gkEmoji::emoji()で生成した絵文字をさらに自動絵文字変換を行っている際に文字化けしてしまう不具合がありました。これは emoji()の出力が既に対象キャリアの絵文字であるのに、その絵文字をdocomo絵文字と認識して再度変換をかけてしまっているためです。なので自 動絵文字変換処理の場合はemoji()ではdocomo絵文字を出力するように修正しました(以前のコードにはこの処理が入っていたけど絵文字コンバー ト手法を変更した際にそのコードを処分してしまったらしいです)。

また、Lib3gkCarrier::is_android()のラッパーメソッドがどこにも無いため追加しました。

あと、これは修正項目ではありませんが、docomoの比較的新しい機種で「SJISで拡張絵文字の数値文字参照形式(#xxxxx;)の絵文字を表示できない」現象を見つけました。P-01A等でこの現象が出ます。なのでSJISに関してはバイナリ絵文字を出力した方が良いようです。お気を付けください。

【BaserCMS】テーマファイルを安全に配置する方法

BaserCMS関連のTips続編です。

とにかく導入が楽でお手軽に使える事を目指しているBaserCMSですが、一つだけ個人的に良くないかなーと思う点があります。「テーマ機能」を用いて簡単にデザインを変えられる機能がありますが、ビューファイル(*.ctp)もドキュメントルート内に置く仕様になっているため、ctpファイル単体が閲覧できてしまう問題があります。

例えばTopページのビューは、インストール直後は「/themed/demo/pages/index.ctp」にあります。なので「http://www.example.com/themed/demo/pages/index.ctp」とすると次のように見えてしまいます。

PHPの関数コールが分かってしまう

ソースコードが丸わかり。ビューにコードをいろいろ書いてしまうと危険!

これはあまり気持ちの良い事ではないので、ビューファイルだけは見えないところに移動してみましょう。
テーマファイルは「app/webroot/themed/」以外に「app/views/themed/」内においても動作をします。そこで次の対処を行います。

  1. 「app/views/」内に「themed」ディレクトリを作成する
  2. さらに、対処したいテーマ名のディレクトリを「themed」内に作成する。今回は「app/views/themed/demo/」となる
  3. 「app/webroot/themed/demo」内にある「elements」「feed」「leyouts」「pages」を「app/views/themed/demo/」内に移動する

なお、これが可能なのは「webrootをドキュメントルートとしている」場合のみです。ドキュメントルート内にBaserCMS本体を置いて運用されている方は対処する事が出来ません。(2011.01.26追記:「スマートURL」設定であれば、どのようなインストール方法でも上記は有効です)
また、上記対処をする事で問題点もあります。管理画面上でテーマファイルの編集が出来なくなりますので、別の方法でファイルを直接扱わなければならなくなります。まあもっともプログラマーの方でしたら特に問題はないでしょう。

もしビューファイルの拡張子がphpでしたらPHPコードとして実行されるためエラーコードがはき出されるのみですみますが、そうでない場合は普通にテキストファイルとして入手できてしまうので、場合によっては脆弱を生む事になります。
まあこれはBaserCMSに限った事ではなく、CakePHPを丸ごとドキュメントルート内において運用する場合にも起こりうる問題です。ビューにはあまりコードを書かないようにするか、ちゃんとwebrootをドキュメントルートにした上で、上記のようにthemedからビューファイルを外に追い出した方が安全でしょう。

とはいえ、システムの方で一応ディレクトリを分ける対応をした方が問題がなさそうな気がします。丸ごとインストールされているとまあどうしようもないのですが…

【追記】(2011.01.26)

@nojimageさんから「.htaccessにAddHandlarでctpも加えたら」というアイデアをいただきました(ありがとうございます!)。
もしお使いのサーバで「.httaccessの編集権」「AddHandlar使用権」がある場合は、ctpファイルをPHPコードと見なす事で、webroot内にctpファイルがあっても、コードの中身を閲覧する事を(一応)防ぐ事が出来ます。
「app/webroot/.htaccess」に次のように記述します。


#インストールしているPHP環境によって書き方が違うかもしれない(下記はPHP5用)
AddHandler php5-script .ctp
AddType text/html .ctp

たいていの場合、内部のメソッド・関数が見つからないために致命的エラーとなり、閲覧が出来なくなります。
また、ドキュメントルートに丸ごとBaserCMSをインストールしている場合は「/(BaserCMSのインストールディレクトリ)/.htaccess」に記述する事でBaserCMS内の全てのctpファイルを防げます。

【BaserCMS】app/webrootをドキュメントルートにする場合の注意点

いままでBaserCMSの話題が全くなかったので、ちょっと取り上げてみようと思います。

あ、そうでした。
4月頃、東京で「BaserCMSの勉強会」の開催をもくろんでいます。興味がありましたら是非ご参加ください。またお手伝いいただける方、会場をちょろっとお借りできる会社様等ありましたら是非ご協力お願いいたします。
このへんについてはまた後日。

で本題なのですが、Bakerの皆さんがCakePHPを扱う場合、おそらくapp/webrootをドキュメントルートとしてサーバ設定をする事が多いと思いますが、同じようにBaserCMSもこのように設定すると、インストーラ実行後、表画面もしくは裏画面のどちらかがうまく表示されなくなってしまうかと思います。BaserCMSは標準ではドキュメントルート内にアーカイブ一式をコピーする事を前提で設定がされているため、いくつかの箇所で不具合を生じてしまいます。

app/webrootをドキュメントルートにする場合は次のようにします。

■修正1:.htaccessを次のように設定する


RewriteEngine on
#↓ここを修正
#RewriteBase /app/webroot
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

■修正2:app/config/core.php内の次の箇所を変更する


//↓ここを修正
//Configure::write('App.baseUrl', env('SCRIPT_NAME'));
 Configure::write('App.baseUrl', '');

インストール後うまくいかないようでしたら、たぶんこの辺が影響していると思います。
是非ご確認ください。

【CakePHP】「CakePHP新春勉強会」に行ってきました

CakePHPの勉強会が久々にありましたので参加しました。
大規模なイベントは昨年10月のPHP Matsuri以来となりますが、当方は顔を出したものの完全参加はできなかったので、実質昨年5月以来です。

さて、今回はいつもの「Ktai Library」ではなく、CakePHPで製作されたCMSアプリケーション「BaserCMS」をLTでご紹介させていただきました。スライド(をPDF化したもの)を公開いたします。


クリックするとpdfを参照できます

Cake界隈では「Croogo」あるいはかなり前から存在している「Wildflower」などがありますが、これらは日本向けのCMSではないため、かなりカスタマイズをしないと難しい点がありました。しかしBaserCMSは日本で開発されているため作りも日本向けであり、サポートもされやすいてんがメリットです。また「コーポレートサイト」が簡単に作れるよう機能的にも的を絞っており、他の(Cake以外の)CMSと比べてもコーポレートサイト向けなら導入する価値が大変にあると思います。

当方も開発メンバーでありますが、本業が忙しくてなかなか出来ないところがあります。しかし今年はちょっとてこ入れをしていくつかプラグイン等を作っていきたいと思っています。何故なら本業でも売っていきたいので(笑)。ネタはいくつか考えていますが、一般向けのものについては是非公開していきたいと思います。

で、BaserCMSは福岡では勉強会も回数をこなしており、普及を促している方もたくさんいらっしゃるため盛り上がっているのですが、残念なことに関東では情報がやってこないためなかなか認知度がありません。なのでまずはこの便利なCMSを知っていただきたいと思い、今回勉強会を開催してみようと思っています。Cakeを日頃お使いの方はもちろんですが、特にデザイナーの方にも知っていただきたいと思っております。是非デザイナーさんをお誘いのうえ参加していただけますと大変に嬉しいです。
現時点ではとりあえず「やる」と表明しただけで、何も決まっていない状態です。現実的になってきましたら改めて告知させていただきます。また手伝っていただける方、会場をお貸しいただける方(企業)も絶賛大募集です。まずは小さくと思っておりますが、関東でも是非盛り上げていきたいと思いますのでよろしくお願いします。

…と告知が長くなってしまいましたが(汗)、勉強会そのものも大変に楽しかったです。
内容については他の方がレポートされていますし、ちょっとまとめる時間もなさそうなので今回は割愛させていただきますが、CakePHP2.0は今年の大注目になりそうですね。

2次会はカラオケボックス内でLT大会になりました(笑)。しかもそれがUstされたり!
こんな事をするのはCakeメンバーくらいかもしれません。
第2回CakePHP温泉部」をはじめ、私もいろいろと企画したいと思います。

【Ustreamによる発表録画はこちら】

http://www.ustream.tv/channel/cakephpstudy#utm_campaign=unknown&utm_source=712880&utm_medium=social

【参加された方のブログなど】

http://techlog.knocking.co.jp/archives/190
http://d.hatena.ne.jp/deeeki/20110118/cakephp_newyear_study
http://blog.livedoor.jp/hamichamp/archives/51564338.html

【CakePHP】Shellを拡張してTaskで親シェルのメソッドが使える「ShellEx」クラス

CakePHPを利用するにあたり、基本的にはWeb上で動作するアプリケーションを作成すると思いますが、バックエンド等でCakeの機能を用いながら処理を実行したい場合も当然出てきます。そんなときは「シェル」が活躍します。みなさんシェルは活用されていますか?

CakePHP1.2/1.3では、シェルの中で「タスク」という小機能を呼び出す仕組みがあります。Shellの中に膨大な機能を全部押し込まなくても済むようになるためコードの可読性が上がり、大変に便利な機能です。膨大な機能を提供しているCake機能の一つ「Bake」も、コアコードを覗けば多数のタスクで構成されています。
使い方も簡単です。「app/vendors/shells/」内に、例えば「hoge_shell.php」を作成します。中身はShellクラスを継承した「HogeShell」クラスを作成します。そしてその中には「main()」メソッドを用意すれば動作します。詳しくは こちら をご覧いただくか「Pocket詳解 CakePHP辞典」をご覧ください(出来れば買って!(笑))。

さてここからなのですが、Taskを使う場合、ShellからTaskが呼ばれるのですが、Shell内は素通りしてしまうため例えばタイトルなどをShell内で制作しても表示されません。また、タイトルを表示するためのメソッドを作って共通化をはかろうとしても、タスクからは呼び出し元のシェルはアクセスが出来ないため、利用することが出来ません。タイトルに限らず、シェル内で利用したいプロパティ・メソッドがあってもアクセスが不可能のため利用不可能です。

そこで、タスク内から親シェルのオブジェクトを参照することが可能な機能を付加する「ShellExクラス」を作成してみました。やっていることは非常に簡単で、タスクの初期化時に親クラスのオブジェクト参照を付加するのみです。しかしタスクの初期化・スタートアップ・実行が全てシェルのinitialize()とstartup()の間に挟まれていますので付加するタイミングがありません。このため、ShellExクラスのinitialize()内でタスクの初期化を先行して行っています。タスク初期化は「Shell::loadTasks()」が行っていますが、結果として二度実行されます。
Shell::loadTasks()が二度実行されることで若干問題点があります。それは親シェル内にロードされる各タスクオブジェクトが1回上書きされることです。幸いだったのが、このとき上書きされるのはClassRegistry::getObject()で入手できる参照オブジェクトでした。実体ではないため、結果として同じオブジェクトになります。単純に無駄な処理が走るだけです。多少気持ち悪さは残りますが、とりあえず要求していた機能は実現できます。

ソースコードはgistに上げました。このファイルを「app/vendors/shells」等にアップロードし、シェル内であらかじめApp::import()でこれを読み込み、extendsを「ShellEx」にすれば動作します。ファイル名は「shell_ex.php」とします。

利用例


App::import('Shell', 'ShellEx');
class MyShell extends ShellEx {
	var $tasks = array('Mytask');

	function commonFunc(){
		//共通処理
		$this->out('MyShell::commonFunc()');
	}

	function main(){
		//メインで共通処理を呼び出します
		$this->commonFunc();
	}
}


App::import('Shell', 'ShellEx');
class MytaskTask extends ShellEx {
	function excute(){
		//共通処理を実行
		$this->parentShell->commonFunc();
	}
}

タイトル云々の話は、Ktai Library1.0のシェル関連機能を実装している最中に必要になりました。やっぱりオリジナルのタイトルは作りたいので(笑)。まあこのほかいくつか共通機能を実装したかったので、それだけのためって事はないですが…
また @hiromi2424 さんによりますと、CakePHP2.0ではShell機能は改良されているそうです。まあ今回の記事は思いっきりバッドノウハウですね(^^;。

【CakePHP温泉部】第2回開催のご案内と受付開始!

cakephp_spaお待たせいたしました。第2回のCakePHP温泉部の開催について、ようやくご案内できる運びとなりましたのでお知らせいたします。
基本的に、おいしいものを食べつつ温泉にゆったりとつかり、Cake話で盛り上がるといったかなりゆるい内容です(笑)。気軽に参加が可能なイベントですので、是非ご検討の上お申し込みいただけますと幸いです。皆さん日頃激務かと思いますので、のんびりと時間を過ごしましょう!

場所についてはいろいろ検討していたのですが、今回も箱根にするとずっと箱根になってしまいそうでしたので、都度温泉地を変えるという趣旨で今後場所選定する事にしました。今回は箱根のお隣になる熱海にしました。

【日時】 2011年2月26日~27日(土日・1泊2日)
【場所】 静岡県熱海市中央町14番9号
山木旅館
http://www.yamakiryokan.co.jp/

(アクセス)
・電車&バスの場合
新幹線東京-熱海間 50分
JR東海道線[熱海駅]下車
バス(バスターミナル2番のりば) 10分 or タクシー 5分

・車の場合
東名高速~厚木IC~東名厚木ICから小田原厚木道路~真鶴道路~国道135号線経由60分

※電車の場合の集合場所、もしくは車での参加は後日希望を伺います。

【日程】 1日目:
12:50 集合
13:00 チェックイン
13:30~ 温泉部オリエンテーション(自己紹介など)
~16:30 活動1
17:00~18:00 温泉Time(は~と)
18:00~20:00 食事・休憩
20:00~23:00 活動2
23:00~ 自主活動(ほどほどに)・就寝

2日目:
~ 9:00 起床・食事・片付け
9:30~ 活動3
~11:30 クロージング
12:00 チェックアウト
12:20 解散

※日程は状況に応じて適宜変更になる場合があります

【活動内容】 今回も基本的に、CakePHPについての自分の考えや、日頃の業務での出来事などをざっくばらんに話しつつ、「温泉に入って日頃の疲れを癒す(ここが重要!!)」ことが主な活動になります。
また当日は「CakePHP温泉部的ベストプラクティス(仮)」と称して、日頃疑問に思っているCakePHPにまつわるコーディングテクニック・サイト構築・運営などを1人1つだけ持ち寄っていただいて、それについて話し合っていきたいと思います。
何か発表したい人も大歓迎です。

具体的な活動内容については、後日追記させていただきます。

【参加資格】 CakePHPを好きな方ならどなたでも参加できます。
(Welcome all of people LOVING CakePHP!)
女性の参加もOKです。今回は大部屋に宿泊する予定ですが、個室等出来る限り配慮いたします。
【費用】 およそ20,000円程度(当日現金にて徴収します)。
別途、交通費がかかります。
【定員】 10名(先着順)。
定員になり次第受付を終了させていただきます。
キャンセル待ちは可能ですが、あまり期待はしないでください。
【申し込み方法】 上記「お問い合わせ」より、題名を「温泉部参加希望」と明記の上、ご連絡ください。
お問い合わせ時のメールアドレスに「google groups」の招待メールを送信します。受信しましたら一度アクセスいただき、自己紹介欄に参加表明をお願いします。なお詳細情報やご連絡についてもこちらで全て行います。
また、運営者より直接ご連絡させていただく場合もありますのでご了承ください。

申し込み締め切りは「2011年1月10日」までです。 締め切りました!

【注意事項】 ・キャンセルは1週前までにお願いいたします。過ぎますとキャンセル料がかかってしまいますのでご注意ください(10日前に最終確認のメールをさせていただきます。1週前までに返事がない場合はキャンセル扱いになります)。
・お部屋は大部屋になります。
・部屋には有線のLAN回線あります。ハブ等はこちらで用意いたします。
お車の方は、あまりお酒を飲み過ぎないように!(笑)
【twitter】 @cakespa
こちらでも随時情報をつぶやきます

会場となる室内。かなり広い!
温泉その1。大浴場。気持ちよさそう!
温泉その2。檜のお風呂。趣あってイイ!
温泉その3。露天風呂。なおこれら温泉は時間によって入れるところが違うので注意!
お打ち合わせの際に、温泉まんじゅうをいただきました!うまい!

【前回の様子はこちら】
【CakePHP温泉部】第一回活動報告デス!
第1回cakePHP温泉部に参加した

【現在の参加表明】
MASA-P(@ecworks_masap)[言い出しっぺ:スタッフ]
T.FUJIWARA(@tfmagician)[スタッフ]
konsan(@konsan)
kara_d(@kara_d)
hiromi(@hiromi2424)
hidetoshing(@hidetoshing)
bin(@binbin4649)
871_(@871_)
8nohe(@8nohe)

_

【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!!

【CakePHP】CakePHP Advent Calendar2010(day 7) : Tips for Routes

CakePHP Advent Calendar2010 の当番が回ってきました。
本来はもっとかる~い内容でいくつもりでしたが、皆さんがっつりとやられているので、慌てて秘蔵のネタ(?)を引っ張り出すことにしました。まあとはいえ実際はそんなでもないので軽く読んでいただければ(笑)。

さて、Modelにまつわる話が多いようなので今回も違う話題にしましょう。Modelは今までTipsが出にくい仕組みの一つだったのかもしれないのですが、同じくなかなか情報として出回らないRoutes関連でも取り上げてみようと思います。

CakePHPであることを隠蔽する (CakePHP Advent Calendar 2010 2日目) でも取り上げられていましたが、CakeぽくないURLを作成するためにはRouteを変更することで自由なURLにコントローラとアクションを振り分けることができます。しかし多くの場合「/:controller/:action/*」という標準のルート指定で問題がないため、修正する機会はなかなかないのではないかと思います。そこで2ステップでRoutes活用法をご紹介します。

ステップ1:パラメータチェックをRoutesで済ませてしまう

Routesの設定は上記のようにURL変更というのも活用法のひとつかもしれませんが、もう一つとても便利な使い方があります。それは、URL中にパラメータを含む場合に、そのパラメータチェックをRoutes内で行うことができる点です。
たとえば

http://www.example.com/foo/hoge/0123456789

というURLを想定するとしましょう。fooコントローラ内のhogeアクションで1つの値を入手したい場合です。この場合、デフォルトですとコントローラ内の「$this->params[‘pass’][‘0’]」に値が入ります。そこで一般的に、次のようなコントローラを書くでしょう。


class FooController extends AppController {
	var $name = 'Foo';
	var $uses = array();
	function hoge(){
		//まずパラメータチェック
		//
		if(!preg_match('/^[0-9A-Za-z]+$/', $this->params['pass'][0])){
			$this->flash('正しくないURLです', '/');
			return;
		}
		//以下、必要な処理
		//
	}
}

しかし、Routesを活用することで、コントローラにチェックを書かずにチェックを行うことができます。「app/config/routes.php」に次のように記述します。


Router::connect('/foo/hoge/:viewerId', array('controller' => 'foo', 'action' => 'hoge'), array('viewerId' => '[0-9A-Za-z]+'));
Router::connect('/foo/hoge/*', array('controller' => 'foo', 'action' => 'error'));

この場合は$this->params[‘viewerId’]にて値を受け取ることができます。条件に満たさない「/foo/hoge」はすべてerrorアクションに流れます。Router::connect()は先着順でルーティングを決定しますので、一番最後に条件を満たさない場合のルートを書いておけば、デフォルトである「/:controller/:action/*」に流れることはありません。

メリットとしては、まずアクション内でパラメータチェックが要らなくなる点で、fat controllerのスリム化に貢献できると思います。そして不要なアクションがコントローラ内に渡ってこない点にあります。そもそもURLから渡されるパラメータはコントローラに渡ってくる時点で正しいものであるべきかと思いますので、Routesでチェックすることは理にかなっていると言えます。

ステップ2:正規表現で表せないルーティングはどうするか?

上記ルーティングは、あくまでも正規表現で表される場合であり、複雑な条件分岐を伴ったりモデルを参照したい場合などはうまく表現できません。
しかしCakePHP1.3からは「カスタムルーティング」という機能が備わり、その中でチェックしたりデフォルト値を追加したりすることができます
たとえば、既にモデルに登録されている値のみをルーティングし、登録されていない値はアクセスできないようなルーティングを表現してみましょう。まず、カスタムルーティングのコードを「app/libs」に記述します。コードを置く場所はどこでもよいのですが、Cake用のコードなのでvendorsではなくlibsがいいでしょう。ここでは「app/libs/viewer_route.php」とします。


class ViewerRoute extends CakeRoute {

	//Userモデル内のviewerIdをチェックし、userIdを返す
	//
	function __checkViewerId($id){

		$model =& ClassRegistry::init('User');
		$result = $model->find('first', array(
			'conditions' => array(
				'viewerid' => $id,
			),
		));
		if(empty($result)){
			return false;
		}
		return $result[$model->alias]['id'];
	}

	//パース処理(URL => array())
	//
	function parse($url){

		//ここはお決まりの処理
		//
		$result = parent::parse($url);
		if(empty($result)){
			return false;
		}

		//viewerIdが登録されているかをチェック
		//userIdをルーティングパラメータとして追加
		//
		if(isset($result['viewerId'])){
			$userId = $this->__checkViewerId(h($result['viewerId']));
			if($userId === false){
				return false;
			}
			$result['userId'] = $userId;
		}else{
			return false;
		}

		return $result;
	}

	//マッチ処理(array() => URL)
	//
	function match($url){

		//ここに、URL生成に必要な処理を記述する(今回はなし)
		//

		//以下お決まりの処理
		//
		$result = parent::match($url);

		return $result;
	}
}

カスタムルーティングでは、parse()とmatch()を記述する事で、それぞれ正引き(Router::connect())と逆引き(Router::url())で独自の処理を行う事ができます。それ以外のコード(上記では__checkViewerId())は独自メソッドです。
ClassRegistry::init()を用いると、ルーティング処理内でもモデルを扱うことができます。ただし、parse()の段階ではまだコントローラ等は初期化が済んでいませんから、その範囲内での処理しかできないことを留意しておきます。match()は逆引きで用いられるので、タイミング的にcontrollerの初期化が終わっているのでこの限りではありません。
次に、「app/config/routes.php」にカスタムルーティングを適用します。第3引数の連想配列の中にある「routeClass」パラメータに作成したカスタムルート名を指定します。また、このクラスは自動的に読み込まれないので、事前に「App::import()」等で読み込んでおきます。


	App::import('Lib', 'ViewerRoute');
	Router::connect('/foo/hoge/:viewerId', array('controller' => 'foo', 'action' => 'hoge'), array('viewerId' => '[0-9A-Za-z]+', 'routeClass' => 'ViewerRoute'));
	Router::connect('/foo/hoge/*', array('controller' => 'foo', 'action' => 'error'));

これで、viewerIdというパラメータで特定ユーザのみを許可するルーティングが実現できます。また、「ルーティングパラメータに存在しないパラメータを後から追加できる」ことも注目したい点です。上記サンプルでは、モデルから取得したuserIdを追加しています。このパラメータはコントローラ内で「$this->params[‘userId’]」として受け取ることができます。

CakePHPは1.2でも十分に実用的ですが、1.3になってよりいろいろなことができるようになりました。今回の「カスタムルーティング」も使い方次第では大変に便利な機能といえます。また1.2を含めて、まだまだ便利な機能がCakePHPには備わっています。当方の執筆しました「Pocket詳解 CakePHP辞典」では、1.2/1.3の違いを含めコアクラス内に収録されているプロパティ・メソッドを盛りだくさんで紹介しています。こちらのほうもぜひご利用いただけますと幸いです。

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

次の担当は kanonji さんです。
どんなCake話が聞けるか楽しみですね!:D
ちょっと気が早いかもしれませんがMerry Xmas!!

【追記】(2010.12.07)
カスタムルーティングについての解説を若干足しました

【KtaiLibrary】バージョン0.4.0公開!

お待たせしました、Ktai Libraryのバージョン0.4.0を公開いたします!

基本的にはバグフィックスと機種情報などデータ面の更新がメインだったのですが、一つだけ他の携帯向けライブラリ等には存在しない便利な機能を搭載しました。

高解像度携帯の登場で、画像の大きさがまちまちになって画面レイアウトが崩れる件については先に搭載しています画像のフィット機能で改善する事ができますが、これだけでは完全に解決することができませんでした。何故かというと「フォントの大きさ」もキャリア・機種によって異なるため、同じ文字幅で改行する事が困難だったからです。
そこで、キャリア・機種によって異なるフォントサイズの指定を肩代わりして同じように見せるような機能を搭載しました。簡単に仕組みを説明しますと、まず3キャリアでフォント使用の指定タグが異なるためそれを吸収し、またキャリア毎、あるいはSoftBankの高解像度携帯とdocomoのブラウザ2.0以降、それ以外での文字サイズの差をなくすために一工夫している感じです。

ただ、厳密にフォントの大きさを変える事はできません。携帯内が持っているフォントは固定幅でありアウトラインフォントではないため、1ピクセル単位でなめらかに大きさを変える事ができないからです(docomoのブラウザ2.0以降のみできる)。なので、おおむねそれに近い大きさの出力が可能な「small」「medium」「large」の3つの大きさが指定できます。それ以外の大きさも指定は可能ですが、そのまま出力するため同じ大きさに見えるかどうかは分かりません(というか見えないはず)。
それから本機能はXHTMLのみ有効です。HTMLでは機能しませんのでお気をつけください。

具体的にはこのように使用する事ができます。

■コントローラ(デフォルトのフォントサイズを変更する)


App::import('Controller', 'KtaiApp');
class KtaipagesController extends KtaiAppController {

	var $name = 'Ktaipages';
	var $uses = array();
	var $components = array('Ktai');
	var $helpers = array('Ktai');
	var $layout = 'ktai_default';

	//Sample ktai params
	//
	var $ktai = array(
		'use_img_emoji' => true,
		'input_encoding' => 'UTF8',
		'output_encoding' => 'UTF8',
		'use_xml' => true, 		//XHTMLに(重要)
		'default_font_size' => 'small', 	//デフォルトフォントサイズをsmallに
	);

	//Sample index action
	//
	function index(){
	}

}

■レイアウトファイル(ここが一番やりやすい)


<?php
if(Configure::read('debug') == 0){
	echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
}
?>
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"
"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<?php if($ktai->is_iphone()){ ?><meta name="viewport" content="width=260"><?php } ?>
<title><?php echo $title_for_layout; ?></title>
</head>
<body>
<?php $ktai->font(); //フォントサイズをデフォルトに指定 ?>
<?php if(!$ktai->is_ktai()){ ?><div style="width: 240px;"><?php } ?>
<?php echo $content_for_layout; ?>
<div align="center">
<hr width="90%" size="1" color="#333333" noshade>
(C)2009-2010 <a href="http://www.ecworks.jp/">ECWorks</a>
</div>
<?php if(!$ktai->is_ktai()){ ?></div><?php } ?>
<?php $ktai->fontend(); //フォントタグの終端 ?>
</body>
</html>

ktai-devのサンプルにもフォント指定がされていますので、お試しいただく事ができます。ただし、FireMobileSimulatorでは正しい文字サイズで表示する事ができませんので注意が必要です。

また、上記サンプルは完全にオプション値を省略していますが、フォント指定を行うタグを変更したり追加のスタイルを与える事もできます。詳しくは添付ドキュメントもしくはソースコード内のコメントを参照してください。

ちなみに本機能はフォントの大きさについてあまりこだわりのない方向けの機能ではないのですが、レイアウトにこだわる必要があるサイト、特に携帯ソーシャルサイトなどに便利にご利用いただけると思います(というか当方の案件で必要だったため制作しました(笑))。

Ktai Libraryの基本的な情報やダウンロードについては こちら をご覧ください。

【CakePHP辞典】Tips第3回:Model::read()

ModelのTipsが続いてしまいますが、今回はModel::read()についての注意事項です。

通常Modelでデータソースからのデータの読み出しはModel::find()/findBy()を用いるのが一般的だと思いますが、ほかの読み出し方法もいくつか存在しています。その中に今回取り上げます「Model::read()」がありますが、こちらは「プライマリIDを指定してレコード取得」を行うことができます。取得フィールドが指定できるため「find(‘first’, array(‘conditions’ => array(‘id’ =>$id), ‘fields’ => $fields));」に近いと思いますが、意味的にはfindById($id)のほうが近いかもしれません。

取り出し方、結果そのものについてはこれらはそれほど差がありませんが、ひとつだけread()には注意点があります。「Model::validationErrors」プロパティにはモデル保存等でバリデーションエラーが存在する場合にそのエラーメッセージが蓄えられるのですが、read()を実行すると問答無用で初期化します

「Model::validationErrors」プロパティを初期化するメソッドは、「Model::read()」のほか「Model::create()」「Model::saveAll()」の合計3メソッドです。おそらくread()はcreate()のように「モデルを初期化するためのメソッド」という位置づけなのでしょう。create()はコードで初期値を与えますが、read()は既存のレコードから初期値を与える、という感じですかね。なのでバリデーションエラーを保持して読み換えるような場合はread()は使うべきではない、ということになります。find()/findById()のほうがよいと思います。

個人的にはread()はほとんど使わないため「フーン」な仕様なのですが、bakeコマンドでコードを作り出している場合にはread()が含まれたコードを生成するそうなので、注意が必要です。@surface0さん情報ありがとうございました!

当メソッドの解説は「Pocket詳解 CakePHP辞典」の55ページに掲載されています。

ほかにも便利な機能が紹介されている「Pocket詳解 CakePHP辞典」を是非よろしくお願いします!

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