CakePHPのプラグイン、cakeplus pluginの機能と使い方

プラグインを拡張する方法(how to extend a plugin) - cakephperの日記(CakePHP, Laravel, PHP)@cakephper さんの cakeplus plugin です。
CakePHPでの開発で良く使う機能をいくつかまとめたプラグインの様で、ちょっと具体的に何が出来るのか分からなかったので、調べてみました。

機能

Component
HtmlEscape
View で一々 nl2br(h($foo))しなくても良いように、予めController で一括してやっちゃう。Arrayも再帰的に処理する。
Behavior
AddValidationRule
CakePHP本体では足りないバリデーションルールの追加。マルチバイト文字列の文字数カウントや日本のTEL/FAXといった、日本特有のものが主。
ValidationErrorI18n
バリデーションのエラーメッセージを地域化と国際化対応と、同じメッセージはまとめて書けるようにする。
ValidationPatterns
複数のバリデーションルールを組み合わせたセットに名前をつけて、使いまわせるようにする。ValidationErrorI18n と併用も可能。

Helper

Formhidden
Input/Confirm/Complete*1の確認画面で、Inputと同じ構成のinput要素を、type="hidden"で生成する。

導入

$ pwd
/path/to/app/plugins
$ git clone http://github.com/ichikaway/cakeplus.git
Initialized empty Git repository in /path/to/app/plugins/cakeplus/.git/
$ ls cakeplus/
README				controllers			views
cakeplus_app_controller.php	models
cakeplus_app_model.php		tests

Github から cakeplus plugin を clone してくる。
CakePHP の app にあるplugins フォルダに、cakeplus という名前のフォルダで導入する必要があるので、上記の通り plugins フォルダで git clone すれば楽です。

<?php
class AppModel extends Model {
    public $actsAs = array(
        'Cakeplus.AddValidationRule',
        'Cakeplus.ValidationErrorI18n',
        'Cakeplus.ValidationPatterns'
    );
<?php
class AppController extends Controller {
    public $components = array('Cakeplus.HtmlEscape');
    public $helpers = array('Session', 'Html', 'Form', 'Cakeplus.Formhidden');

cakeplus は名前の通り CakePHPに追加する機能なので、使うならアプリケーション全体で使うと思います。
なので、使っていくなら AppModel/AppController に設定するのがよさそう。
Helper は注意が必要で、AppController が継承する Controller で 'Session', 'Html', 'Form' の3つが設定されています。
AppController で設定する際は、この3つも書いておかないと、HtmlHelper や FormHelper が使えなくなります。

HtmlEscape

<?php
$this->set('posts', $this->HtmlEscape->nl2br_h($this->paginate( 'Post' ),null, array( 'Post.title') ) );

こんな感じで使うようです。
nl2br_h() ってちょっと入力しにくいので、もっと短いのにしても良いんじゃないかとか思ったり。

第2引数
h() の第2引数が htmlspecialchars() の第3引数に使う文字コードなので、nl2br_h() も文字コードを指定できます。
第3引数
適用を除外するキーを指定できるみたい。
第4引数
nl2br_h()が再帰する際に、自身で使う様です。

AddValidationRule

tel_fax_jp
日本のTEL/FAX番号かどうかを検証するルール。ただ、GitHub - cakephp/localized: I18n and L10n related CakePHP codeという新しいプラグインで、国別バリデーションとしてphone, postal, ssn*2の3つは提供されてます。
mobile_email_jp
日本の携帯キャリアのメールアドレスは、RFC違反*3のものがあったりします。それを検証できるみたいです。ただ、CakePHPで携帯サイト開発だとKtai Libraryという有名なライブラリがあるので、こっちで出来るかも?
alpha_number
CakePHP組み込みのバリデーションルール、alphaNumericは、なぜか日本語を通します。対策として、ちゃんと機能する半角英数用のルールを作ったみたいです。

他にもいくつかルールがあるけど、注意が必要そうなのだけ抜粋しました。
プラグインを拡張する方法(how to extend a plugin) - cakephperの日記(CakePHP, Laravel, PHP) で AddValidationRuleBehavior の拡張方法が解説されてるので、バリデーションルールが足りなかったら作ったりできます。

ValidationErrorI18n

<?php
class AppModel extends Model {
    public $actsAs = array(
        'Cakeplus.AddValidationRule',
        'Cakeplus.ValidationErrorI18n',
        'Cakeplus.ValidationPatterns'
    );
    
    public function __construct($id = false, $table = null, $ds = null){
        parent::__construct($id, $table, $ds);
        $errorMessages = array(
            'username_notempty' => __('error.username.empty',true),
        );
        $this->setErrorMessageI18n($errorMessages);
    }
    
    public function beforeValidate(){
        $this->replaceValidationErrorMessagesI18n();
        return parent::beforeValidate();;
    }
<?php
class User extends AppModel {
    var $name = 'User';
    var $validate = array(
        'username' => array(
            'username_notempty' => array(
                'rule' => array('notempty'),
                'required' => true,
                'allowEmpty' => false,
            ),
        ),

AppModel のコールバックメソッドである beforeValidate() で replaceValidationErrorMessagesI18n() を呼ぶようにしておきます。
あとは setErrorMessageI18n() で__()が付いたエラーメッセージの配列をセットすればOK。
この例の場合、エラーメッセージは username_notempty というキーを使って、セットされています。


CakePHPは普通の方法だと、各Modelのメンバー変数 $validates に、バリデートの配列としてエラーメッセージを書くことになります。
メンバー変数の宣言になるので、関数である__()を使うとシンタックスエラーになってしまいます。
ただ、ソースコード上に__()が無いと、日本語などに置き換える対象として、cake l18nコマンドでピックアップ出来ません。
そこを解決する為のビヘイビアです。


ビヘイビア化される前の様なのでちょっと古い気もしますがバリデーションメッセージをDryにしつつ国際化 - cakephperの日記(CakePHP, Laravel, PHP)に解説があります。

ValidationPatterns

<?php
class AppModel extends Model {
    public $actsAs = array(
        'Cakeplus.AddValidationRule',
        'Cakeplus.ValidationErrorI18n',
        'Cakeplus.ValidationPatterns'
    );
    
    public $validation_patterns = array(
        'email_pattern' => array(
            'notEmptyMail' => array(
                'rule' => 'notempty',
                'last' => true,
            ),
            'validMail' => array(
                'rule' => 'email',
                'last' => true,
            ),
            'uniqueMail' => array(
                'rule' => 'isUnique',
                'on' => 'create'
            ),
        ),
    );
    
    public function __construct($id = false, $table = null, $ds = null){
        parent::__construct($id, $table, $ds);
        $errorMessages = array(
            'notEmptyMail' => __('error.mail.empty',true),
            'validMail' => __('error.mail.invalid',true),
            'uniqueMail' => __('error.mail.duplicated',true),
        );
        $this->setErrorMessageI18n($errorMessages);
    }
    
    public function beforeValidate(){
        $this->setValidationPatterns();
        $this->replaceValidationErrorMessagesI18n();
        return parent::beforeValidate();;
    }
}

AppModel->validation_patterns に上記の様な感じで、複数のバリデーションルールを書きます。
この例では 'email_pattern' というキーに3つのバリデーションルールを設定しました。

<?php
class User extends AppModel {
    var $name = 'User';
    var $validate = array(
        'email' => array(
            'email' => 'email_pattern'
        ),
    );

この様に 'email_pattern' をバリデーションルールに指定して使えます。
アプリケーションの中で、email を検証するModelが複数あるような場合、同じルールをコピペしなくて良いようになります。
email だとあまり無いかもですが、アプリケーション特有のデータがある場合などは、役に立ちそうです。


ちなみに ValidationPatternsBehavior は AppModel->validation_patterns というメンバー変数名に設定されてると想定して動作します。
AppModelじゃなく、各Model内で設定しても動くとは思うけど、共通化のためのビヘイビアなので AppModel が妥当だと思います。


元々は 実"戦"CakePHP Plugin で紹介されているビヘイビアのようです。

ValidationPatternsとValidationErrorI18nを併用する
<?php
    public $validation_patterns = array(
        'email_pattern' => array(
            'notEmptyMail' => array(
                'rule' => 'notempty',
                'last' => true,
            ),
            'validMail' => array(
                'rule' => 'email',
                'last' => true,
            ),
            'uniqueMail' => array(
                'rule' => 'isUnique',
                'on' => 'create'
         
<?php
    public function __construct($id = false, $table = null, $ds = null){
        parent::__construct($id, $table, $ds);
        $errorMessages = array(
            'notEmptyMail' => __('error.mail.empty',true),
            'validMail' => __('error.mail.invalid',true),
            'uniqueMail' => __('error.mail.duplicated',true),
        );
        $this->setErrorMessageI18n($errorMessages);
    }

前述のサンプルコードの抜粋ですが、併用も出来ました。

Formhidden

入力/確認/完了という良くある入力フォームの流れを作る際に使います。
確認画面では、入力したデータを表示しつつ、完了画面に同じデータを再度送信する必要があります。

<input type="hidden" name="Foo" value="var" />

入力画面とほぼ同じ構成で、type="hidden"だけのフォームを用意する方法がありますが、そのフォームを生成するHelperです。
これも2008年12月でちょっと古いですが よくある確認画面でのhiddenデータの持ち回り ver2 - cakephperの日記(CakePHP, Laravel, PHP) に解説があります。


個人的には入力画面から完了画面まで、データを持ち回すのは、セッションを使った方法のほうが好みです。
ただ、少し検索した感じだとCakePHPではこのHelperの有無に限らず hidden を使った方法のほうが、記事が多い気がしました。

*1:ICC

*2:日本用は無い

*3:ドットが連続したりドットで終わるユーザーネームだったり