CakePHP1.3 バリデーションしつつAuthのログインPASSを登録する方法

CakePHPには、便利で強力なバリデーションと認証のシステムがあります。
システム構築時にはどちらも使うことになると思うのですが、ユーザーを登録する際には少しコツがいります。

というのは、パスワードに4文字以上15文字以内という文字制限を行う場合、cakePHPはハッシュ化されたパスワードフィールドを元にバリデーションを行うので、期待した結果にはならなくなります。
つまり、『SHA-256』でハッシュ化すると64文字になるので、15文字以内の文字制限にひっかかってしまいます。

これを解決するには、以下のような方法を行います。

結論から言うと、ハッシュ化したいパスワードフィールドと違う名前のフィールドにバリデーションを行っちゃいます。
『password』というフィールドをパスワードに使用しているなら、『pass』を用意しちゃう感じですね。

文章力がないので、例を書いちゃいます。

▼モデル

public $validate = array(
    'username' => array(
        array(
            'rule'    => 'alphaNumeric',
            'message' => '半角英数字で入力してください。',
        ),

        array(
            'rule'    => array('between', 4, 20),
            'message' => '半角英数字、4文字以上20文字以内で入力してください。',
        ),

    // 別名で用意したフィールドにバリデーションを行う
    'pass' => array(
        array(
            'rule'    => 'alphaNumeric',
            'message' => '半角英数字で入力してください。',
        ),

        array(
            'rule'    => array('between', 4, 20),
            'message' => '半角英数字、4文字以上20文字以内で入力してください。',
        ),
);

▼ビュー

echo $form->create('User', array('type' => 'post', 'action' => './add'));
echo $form->inputs(
	array(
		'legend' => 'ユーザー登録',
		'username',
		'pass'
	)
);
echo $form->end('登録');

▼コントローラー

public function add() {
    if(!empty($this->data)) {
        // ここでAuthで使用するパスワードフィールドに値を渡しています
        $this->data['User']['password'] = $this->Auth->password($this->data['User']['pass']);
 
        if ($this->User->save($this->data)) $this->redirect(array('controller' => 'users', 'action' => 'index'));
    }

    $this->data['User']['pass'] = null;
}

これで、ハッシュ化される前の状態でバリデーションを行うことができるます。

『CakePHP2.0.1』がリリースされたんですね

いつの間にか『CakePHP2.0.1』がリリースされたんですね。
最近は仕様書ばっかり書いていたし、PHPが恋しくなってきたので少~しだけ触ってみることにしました。


(『CakePHP2.0.1』は、マルチバイト関係の問題があるらしいので2.0.1は使わずに次のリリースを待つか、最新の2.0ブランチを使うほうが良いみたいです。
詳しくは、【CakePHP2.0.1がリリースされましたが、ちょっと待ったほうがいい】cakephperの日記(CakePHP, MongoDB))


触ってみるにしても、何が変わったのかも良く分からないので、とりあえずCakePHP2.0のマニュアルを読んでみることに。
どうやらCakePHP2.Xは、PHP4は完全にサポート外、PHP5も5.2.6以上になったみたいです。
PHP4を使用してサイトを構築している人の為に、CakePHP1.3.Xの開発もしばらくは続くみたいですね。

ってな感じで、マニュアルを読み進めていくと、『2.0移行ガイド』なるものが。

ファイル名の命名規約がアンダースコアからキャメルケースに、ディレクトリ名の命名規約もキャメルケースになったんですね。
コンポーネントのスーパークラスも『Object』クラスから『Component』になったり、『Authコンポーネント』が書き直されてたり、テスト環境が『SimpleTest』から『PHPUnit』に変更になってたりと、結構な量の変更が。

まあ、今日は2.Xを触ってみたいだけので詳しくは後で見ることにして、実際に動かしてみることにします。

まずは、ダウンロード
いつものようにディレクトリを配置していき、いつものようにアクセス。

すると、いつもの見慣れたトップページが表示されました。
『Security.salt』や『Security.cipherSeed』の変更も1.Xと同じですね。
『/app/Config』に新しくできた『email.php』は、多分Eメールを送る新しいライブラリクラスに関わる設定なんだろうと思います。

ためしに、コントローラーとビューを作成。
ファイル名とディレクトリ名の命名規約がキャメルケースになったのを注意するぐらいで、簡単なコントローラーなどは作成できました。
がっつりしたアプリケーションを開発する場合に、1.Xとの差を感じるんでしょうね。

マルチバイト関係の問題もあるみたいですし、実際の案件で使用するのはもうしばらく先だと思うんで、テストで色々と作成してみよ~。

CakePHP 便利な組み込みビヘイビア『ContainableBehavior』の使い方

cakePHPは、複数テーブルの結合を簡単に実現できます。
が、何も指定せずに『find』でデータを取り出すと、結合しているテーブルのデータも大量に取り出すことになります。

「このテーブルは、1つのフィールドからデータを取り出せればいいのに・・・」って時に活躍してくれるのが『ContainableBehavior』です。

取り出したいフィールドや、条件に一致する場合のみデータを取得なんてことが簡単にできるようになります。
そんな便利なビヘイビア『ContainableBehavior』の使い方を、簡単なブログ(?)みたいなシステムを例にして紹介したいと思います。

(モデルでの使用を例にしますが、コントローラーから使用する方法も基本的に同じです。)


▼簡単なブログシステムの構造

このブログシステムには以下の3つのテーブルがあります。

  • 【Post】
    ブログに投稿された記事を管理

  • 【User】
    ブログ利用者を管理

  • 【Comment】
    ブログに投稿された記事に対するコメントを管理

各テーブルの構造は、以下の通りです。

▼Postテーブル

`id` int(11) NOT NULL auto_increment,
`user_id` int(11) default NULL,
`mail` varchar(255) default NULL,
`title` varchar(50) default NULL,
`text` text,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY  (`id`)

▼Userテーブル

`id` int(11) NOT NULL auto_increment,
`alias` varchar(50) default NULL,
`username` varchar(50) default NULL,
`password` varchar(70) default NULL,
`created` date default NULL,
`modified` date NOT NULL,
PRIMARY KEY  (`id`)

▼Commentテーブル

`id` int(11) NOT NULL auto_increment,
`post_id` int(11) default NULL,
`user_id` int(11) default NULL,
`mail` varchar(255) default NULL,
`title` varchar(50) default NULL,
`text` text,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY  (`id`)

記事に対してコメントが連なる感じです。

あくまで『ContainableBehavior』の使い方を紹介するためだけに用意したシステムなので、細かい突っ込みなどは無しでよろしくお願いします・・・


1・準備

『ContainableBehavior』を使用するためには、モデルのメンバ変数『$actsAs』に『Containable』を追加します。

class Post extends AppModel {
    public $name   = 'Post';
    public $actsAs = array('Containable');
}

2・使い方

何も指定せず『find』などでデータを取得すると、以下のように大量のデータを取得してしまいます。

▼何も指定せず『find』した場合

Array (
    [0] => Array (
        [Post] => Array (
            [id] => 1
            [user_id] => 1
            [mail] => info@yasigani-ni.com
            [title] => ブログはじめました
            [text] => 初めてのブログ投稿です。
            [created] => 2011-10-8 18:00:00
            [modified] => 2011-10-08 18:00:00
        )

        [User] => Array (
            [id] => 1
            [alias] => ユーザー01
            [username] => test
            [password] => **********************
            [created] => 2011-10-8 18:00:00
            [modified] => 2011-10-08 18:00:00
        )

        [Comment] => Array (
            [0] => Array (
                [id] => 1
                [post_id] => 1
                [user_id] => 2
                [mail] => test01@yasigani-ni.com
                [title] => コメント
                [text] => 初めてのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )

            [1] => Array (
                [id] => 2
                [post_id] => 1
                [user_id] => 3
                [mail] => test02@yasigani-ni.com
                [title] => コメント
                [text] => 2つめのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )
        )
    )

    [1] => Array (
        [Post] => Array
	    ・・・

これを『Userテーブルからは、aliasだけを取得する』場合、以下のようにします。

▼Postモデルで指定する場合

public function find($type, $options = array()) {
    $data = parent::find($type, array_merge($options, array('contain' => array('User.alias'))));
    return $data;
}

データを取得する際に、オプションに『contain』に取得したいフィールド名を指定するだけです。
これでデータを取得すると、以下のようなデータになります。

▼結果

Array (
    [0] => Array (
        [Post] => Array (
            [id] => 1
            [user_id] => 1
            [mail] => info@yasigani-ni.com
            [title] => ブログはじめました
            [text] => 初めてのブログ投稿です。
            [created] => 2011-10-8 18:00:00
            [modified] => 2011-10-08 18:00:00
        )

        [User] => Array (
            [alias] => ユーザー01
        )

        [Comment] => Array (
            [0] => Array (
                [id] => 1
                [post_id] => 1
                [user_id] => 2
                [mail] => test01@yasigani-ni.com
                [title] => コメント
                [text] => 初めてのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )

            [1] => Array (
                [id] => 2
                [post_id] => 1
                [user_id] => 3
                [mail] => test02@yasigani-ni.com
                [title] => コメント
                [text] => 2つめのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )
        )
    )

    [1] => Array (
        [Post] => Array
	    ・・・

『User』からは、『alias』だけを取得しているのが分かると思います。

「これなら

『$this->Post->find('all', array('fields' => array('User.alias')));』

でいいんじゃない?」
と、お思いの方。

『ContainableBehavior』の実力は、こんなものじゃありません。

連結しているモデルの条件を指定したり、連結が複雑なモデルの場合などで力を発揮します。
単純に『recursive』の指定をしないで済むだけでも、とても楽になると思います。

簡単な条件を指定するには、以下のようにします。

▼簡単な条件指定

public function find($type, $options = array()) {
    $data = parent::find($type, array_merge($options, array('contain' => array('Comment.id = 1'))));
    return $data;
}

これで、idが1のCommentだけ取得することができます。
連結が複雑なモデルの場合でも、取得するフィールドを指定するだけならとても簡単です。
Commentが連結しているUserテーブルのaliasを取得する場合は、以下のようにします。

▼連結が複雑なモデルで取得するフィールドを指定

public function find($type, $options = array()) {
    $data = parent::find($type, array_merge($options, array('contain' => array('Comment.User.alias'))));
    return $data;
}

Commentには、idが1のCommentだけを取得。Userからはaliasを取得したい場合は以下のようにします。

▼条件指定と取得するフィールドの指定

parent::find('all', array_merge($options, array('contain' => array(
                'Comment' => array(
                    'conditions' => array('Comment.id = ?' => 1),
                ),

                'User' => array(
                    'fields' => array('User.alias'),
                ),
            )
        )
    )
);

通常のfindを使う感覚で、簡単に条件指定することができます。
連結が複雑なモデルの場合のは以下のようになります。
例えば、『Commentはtitleと、連結しているUserからaliasを取得する』『Postと連結しているUserからはaliasを取得』したい時は、

parent::find('all', array_merge($options, array('contain' => array(
                'Comment' => array(
                    'fields' => array('Comment.title'),
                    'User' => array(
                        'fields' => array('User.alias')
                    ),
                ),

                'User' => array(
                    'fields' => array('User.alias'),
                ),
            )
        )
    )
);

といった感じになります。


最後に

連結が複雑なモデルでも、取得するフィールドの指定だけならとても簡単です。
複雑な指定や連結が複雑なモデルだと配列だらけで読みにくくなってしまいますが、連結を意識したインデントなどで対処できると思います。

『ContainableBehavior』をまだ使用したことがなく、私の説明を読んだら余計に混乱した方や、難しそうと思った方。
私の文章力が悪いだけなので、だまされたと思って一度使用してみてください。
すごい楽に開発できるようになると思います。

マニュアルはこちらから

6.2 コンテイナブル

CakePHP まとめて『hidden』要素を出力する簡単なヘルパーを作ってみる

コンポーネントとビヘイビアの作成をしたので、ヘルパーの作り方も紹介しちゃいます。

ヘルパーは、呼んで字のごとく「ビューのコーディングの手助けをする」なので、「フォームから渡されたデータを『hidden』要素にする」ヘルパーを作りたいと思います。
ひとつずつ『hidden』を書くより一気に出力できたほうが楽ですからね^^

ヘルパーは、『AppHelper』クラスを継承して作成します。

▼『AppHelper』クラスを継承

class FormArrHelper extends AppHelper {
    public $name = 'FormArr';
}

あとはいつも通り、CakePHPの命名規則に従い、『/app/views/helpers』に保存します。
これでヘルパーとして使用できるようになります。

準備ができたところで、メソッドの実装をします。
ヘルパー内から他のヘルパーを使用することができるので、『hidden』要素の生成自体は『Formヘルパー』のメソッドを使用しています。
注意点は、ヘルパー内で他のヘルパーを使用するときは、『$this->ヘルパー名』となるぐらいです。

▼作成したヘルパーにメソッドを実装する

class FormArrHelper extends AppHelper {
    public $name    = 'FormArr';
    public $helpers = array('Form');

    /**
     *    渡されたフォームデータの全てをhiddenにした文字列を生成する
     *
     *    @return    hiddenの文字列
     */
    public function allHidden() {
        return $this->_arrHidden($this->data);
    }

    /**
     *    渡されたフォームデータの指定されたデータをhiddenにした文字列を生成する
     *
     *    @param     string    指定したいデータまでのキーを『.(ドット)』でつなげた文字列
     *
     *    @return    hiddenの文字列
     */
    public function givenHidden($keyName) {
        $nameArr = explode('.', $keyName);

        if(count($nameArr) == 2) {
            $givenData[$nameArr[0]][$nameArr[1]] = $this->data[$nameArr[0]][$nameArr[1]];
        } else {
            $givenData[$nameArr[0]] = $this->data[$nameArr[0]];
        }

        return $this->_arrHidden($givenData);
    }

    /**
     *	渡された配列を元にhiddenを生成する
     *
     *    @param     array    hiddenを生成したい配列
     *
     *    @return    hiddenの文字列
     */
    protected function _arrHidden($arr) {
        if(!is_array($arr)) return;

        $hdn = null;

        foreach($arr as $fKey => $fItem) {
            foreach($fItem as $sKey => $sItem) {
                if(is_array($sItem)) {
                    // 配列データならもう一度foreach
                    foreach($sItem as $tKey => $tItem) {
                        // hidden要素の作成
                        $hdn .= $this->Form->hidden("$fKey.$sKey.$tKey");
                    }
                } else {
                    // 配列データじゃなければ、そのままhidden要素の作成
                    $hdn .= $this->Form->hidden("$fKey.$sKey");
                }
            }
        }

        return $hdn;
    }
}

あとは、コントローラーでメンバ変数『$helpers』に作成したヘルパーを指定するだけで使用することができます。

▼コントローラー

class PostsController extends AppController {
    public $name  = 'Posts';
    public $users = array('Post');
    public $helpers = array('FormArr');

    public function index() {
        /*
         *    処理は省略します
         */
    }
}

▼ビュー

<?php echo $formArr->allHidden(); ?>

▼出力結果

<input id="PostText" type="hidden" value="test" name="data[Post][text]">
<input id="PostRadio" type="hidden" value="radio3" name="data[Post][radio]">
<input id="PostCheckbox1" type="hidden" value="1" name="data[Post][checkbox][1]">
<input id="PostCheckbox2" type="hidden" value="2" name="data[Post][checkbox][2]">
<input id="PostCheckbox3" type="hidden" value="0" name="data[Post][checkbox][3]">

配列のデータもちゃんと『hidden』要素になっていますので、通常のフォームなら何も意識せずに『$formArr->allHidden();』を使用するだけで『hidden』要素を出力することができます。

以上が、『まとめてhidden要素を出力する簡単なヘルパー』の作り方でした。

CakePHP 『App:import』について

CakePHPでは、『App:import』を使用することで、パスを意識せずにクラスを読みこむことができます。
よく使う処理をまとめたクラスなどを『vendors』に入れておき、『App:import』で読み込んだりすると楽になるみたいです。

マニュアルによると、「App::import()は次のことを保証します。クラスが1度しかロードされないこと。適切な親クラスがロードされること。そして数多のケースで自動的にパスを解決することです。『App::import』は、ファイルを『require』で読み込むことと同じです。その後にクラスを初期化する必要があるということを意識しておいてください。」らしいです。

当たり前といえば当たり前ですが、使用したい場合はインスタンス化を忘れないって事ですね。


コアライブラリ

App::import(‘Core’, ‘インポートしたいコアライブラリ名’)


コントローラ

App::import(‘Controller’, ‘インポートしたいコントローラー名’)


モデル

App::import(‘Model’, ‘インポートしたいモデル名’)


コンポーネント

App::import(‘Component’, ‘インポートしたいコンポーネント名’)


ビヘイビア

App::import(‘Behavior’, ‘インポートしたいビヘイビア名’)


ヘルパー

App::import(‘Helper’, ‘インポートしたいヘルパー名’)


プラグイン

プラグインについては、少し他とは違います。
マニュアルには、「プラグインのクラスを読み込むのは、appとコアのクラスを読み込むのとほぼ同じです。しかし、読み込み元のプラグイン名を指定してください。」とあります。

例1・Commentモデルの読み込み

App::import(‘Model’, ‘PluginName.Comment’);

例2・『APP/plugins/plugin_name/vendors/flickr/flickr.php』 の読み込み

App::import(‘Vendor’, ‘PluginName.flickr/flickr’);


Vendor ファイル

Vendor ファイルも、他とは少し違います。
マニュアルを例に紹介していきます。

例1・『vendors/geshi.php』の読み込み

App::import(‘Vendor’, ‘geshi’);

例2・『vendors/flickr/flickr.php』 の読み込み

App::import(‘Vendor’, ‘flickr/flickr’);

例3・『vendors/some.name.php』 の読み込み

App::import(‘Vendor’, ‘SomeName’, array(‘file’ => ‘some.name.php’));

例4・『vendors/services/well.named.php』 の読み込み

App::import(‘Vendor’, ‘WellNamed’, array(‘file’ => ‘services’.DS.’well.named.php’));


マニュアルはこちらから

『3.4.4 App クラス』