Posts tagged model
CakePHP 1.2 の saveAll その1
1CakePHP 1.2 から model に saveAll メソッドが追加されました。
同一モデルへの複数データの一括保存やアソシエーションのモデルのデータの保存が saveAll でできます。
同一モデルへの複数データの保存
$data = array( array('name'=>'tanaka'), array('name'=>'suzuki'), array('name'=>'yamada') ); if ($this->User->saveAll($data)===false) { $this->Session->setFlash('保存に失敗しました'); } else { $this->Session->setFlash('保存しました'); }
ただβバージョンのためか、saveAll の戻り値が成功時には NULL 失敗したときには false が返って来ます。
そのために
If ($this->User->saveAll($data)) {
とやると成功の判定が正しく行えません。
CSV を読み込んでデータをインポートするようなときにいいかもしれません。
追記
このエントリーは CakePHP 1.2.0.6311 beta で検証しています。
CakePHP find や findAll の条件指定にちょっとだけ便利な postConditions
1CakePHP の コントローラに postConditions というちょっとだけ便利なメソッドがありました。
cake/libs/controllers.php
function postConditions($data) { if (!is_array($data) || empty($data)) { return null; } $conditions = array(); foreach($data as $model => $fields) { foreach($fields as $field => $value) { $conditions[$model . '.' . $field] = $value; } } return $conditions; }
例えば検索をするフォームなどで User.name で検索するような場合
view で
input('User/name');
と指定すると$this->data は
Array ( [User] => Array ( [name] => 山田 ) )
この $this->data を postConditions に渡すと
$data = $this->postConditions($this->data);
$data は
Array ( [User.name] => 山田 )
となり、このまま find や findAll に渡すことができます。
$this->User->find($data);
参考:CakePHP マニュアル
7.2. コントローラの関数
CakePHP 環境によってデータベースを切り替える
2テストデータを入れるなどテストと環境でデータベースを切り替えたいときがあります。
CakePHP でそれをやる方法です。
モデルの $useDbConfig に app/config/database.php で定義されている $default がデフォルトで使用されます。
database.php に $test など使用したいデータベースの分だけ定義を増やし、
それをモデルで
$this->useDbConfig = 'test';
のように指定すればいいだけです。
app/app_model.php のコンストラクタで設定するのが一番簡単かもしれません。
CakePHP 環境に応じてDBの設定を変える | Shin x blog
で色々な方法が紹介されています。
“CakePHPで超簡単スケーラビリティ” フォーラム – CakePHP Users in Japan
また、この $useDbConfig を使用して
「マスターとスレーブのMYSQLサーバがあります。レプリケーション機能で、マスターからスレーブにデータが常にコピーされています。データの更新・追加はマスターに対して行い、データの検索はスレーブで、という場合にはどうすればよいでしょうか?」
というような場合のすごく簡単な方法が紹介されています。
モデルの beforeSave,afterSave,beforeDelete,afterDelete を使用してマスターとスレーブを切り替えています。
CakePHP MySQL で文字化けを防ぐ設定
4MySQL で文字化けを防ぐためには
SET NAMES utf8
のように SET NAMES を実行するのが有効なのですが、これを app/config/database.php で設定する方法です。
'encoding'=>'文字コード'
をデータベースの設定項目に追加してやるだけです。
具体的には app/config/database.php が下記のようになります。
var $default = array( 'driver' => 'mysql', 'connect' => 'mysql_connect', 'host' => 'localhost', 'login' => 'user', 'password' => 'password', 'database' => 'dbname', 'prefix' => '', 'encoding' => 'utf8' );
cake/libs/model/dbo/dbo_mysql.php で下記のように実行されています。
function connect() { (略) if (isset($config['encoding']) && !empty($config['encoding'])) { $this->setEncoding($config['encoding']); } return $this->connected; }
function setEncoding($enc) { return $this->_execute('SET NAMES ' . $enc) != false; }
CakePHP モデルの validation の拡張 同じ項目で違うメッセージを出す
1CakePHP のモデルの validation の拡張のメモの続きです。
同じフォームの項目で違うエラーメッセージを出したいときがあります。
例えばユーザ登録フォームでユーザID がフォーマットエラーなのか、既に使用されているのかなどです。
models/users.php
var $validate = array( 'loginid' => '/^[0-9a-zA-Z]{8}$/', ); function validates($data=array()) { if(empty($data)) { $data = $this->data; } parent::validates($data); // loginid のユニークチェック if ($this->findByLoginid($data["User"]["loginid"])) { $this->invalidate("loginid_unique"); } if (count($this->validationErrors)>0) { return false; } else { return true; } }
既にloginid が登録済みの場合は
$this->invalidate('loginid_unique');
としています。
invalidate に指定するのは実際に存在しないものでも大丈夫なのでそれを使用して view でエラーメッセージを分けて表示します。
views/users/regist.thtml
ID:< ?php echo $html->password('User/loginid'); ?> < ?php echo $html->tagErrorMsg("User/loginid", "IDは英数字8文字で入力してください"); ?> < ?php echo $html->tagErrorMsg("User/loginid_unique", "IDは既に使用されています。他のID を指定してください"); ?>
CakePHP モデルの validation の拡張
1CakePHP のモデルの validation の拡張のメモです。
ユーザ登録フォームなどでパスワードの再確認のバリデーションを行うときの方法です。
views/users/regist.thtml
パスワード:< ?php echo $html->password('User/password'); ?> 確認用パスワード:< ?php echo $html->password('User/password2'); ?> < ?php echo $html->tagErrorMsg('User/password2', 'パスワードが一致しません');
※users テーブルにpassword2 のカラムはなくてかまいません。
models/users.php
function validates($data=array()) { if(empty($data)) { $data = $this->data; } parent::validates($data); if ($data["User"]["password"]!==$data["User"]["password2"]) { $this->invalidate("password2"); } if (count($this->validationErrors)>0) { return false; } else { return true; } }
AppModel クラスの validation メソッドも実行する必要があるので、必ず
parent::validates($data);
を書かないといけません。
CakePHP findBy での複数指定を調べたメモ
2先のエントリで書いた「CakePHP findBy で複数条件の指定で OR も指定可能」ですが、findByHogeAndFoo などの処理がどうなっているか気になったので調べたときのメモです。
- findByHogeAndFoo メソッドが実行されると model_php5.php ( Model クラス ) の __call メソッドが実行される
- __call メソッドでは DboSource クラスの query メソッドに findByHogeAndFoo のメソッド名と条件の値を渡す
- DboSource クラスの query メソッドでは、findBy または、findAllBy の後ろの文字列を取り出す
- 取り出した文字列を小文字に変換してさらに AND を _and_、OR を _or_ に変換する
- _or_ または _and_ で分割してフィールドを取り出す
- AND または OR にしたがって SQL の条件を組み立てる
というようなこと処理がされていました。
まとめると
- 複数指定はいくつでも可能
- AND または OR は混在して指定はできない
いくつでも指定はできるようですが、たくさんの条件をこれで指定するとソースの可読性が下がりそうですし、AND と OR が混在できないので普通はそんな指定はしないでしょう。
指定する場合は2つくらいにとどめておいて、それより多くなる場合は find の $conditions で指定した方が無難だと思います。
CakePHP findBy で複数条件の指定で OR も指定可能
2[cakephp]findByで複数条件の指定 | blog.hereticsintheworld
で紹介されていた
$this->Model->findByIdAndName( $id, $name )
が気になりソースを調べてみたところ OR も指定可能でした。
$this->Model->findByIdOrName( $id, $name )
ちなみに findAll でも同様に指定可能です。
CakePHP select タグの選択していないときの状態を指定する
0Cute CakePHP Trick of the Day – GenerateList Empty Slot In List | Web Development 2.0: Web Design, CakePHP, Javascript
で select タグの選択していないときの状態の値を指定する方法が書かれていますが、この方法は間違っています。
元のエントリのコメントでも指摘されていますが、ヘルパーのメソッドの使い方が違います。コードを実際に書いて検証してみれば、動かないのがすぐに分かります。
元の紹介されている方法はそもそも PHP の文法的におかしいのでそこは適当に修正して紹介します。(変数名に $ がなかったり、generateList() を出力をどこにも保存していないかったりします。)
コントローラで下記のように generateList でリストを取得して
$state_id = $this->State->generateList();
ビューで
echo $form->input($state_id, array('empty' => '--'));
と紹介されています。
CakePHP1.1 の Formヘルパーには input メソッドがないので CakePHP 1.2 でのやり方だと思いますが、実際に Form ヘルパーの input メソッドは
function input($fieldName, $options = array())
と定義されています。上記のように書くと
で、実際にはどう書けばいいかというと
echo $form->input('Model/Field', array('empty'=>'---', 'type'=>'select', 'options'=>$state_list));
または、
echo $form->select('Model/Field', $state_list, null, null, array('empty'=>'---'));
と書けば select タグの先頭に
<option value=''>---</option>
と入ります。
CakePHP1.1 では 「CakePHP HTMLヘルパーで select タグを表示する」のエントリで書いたように結構面倒だったので新しい Form ヘルパーは使いやすいですね。
CakePHP Model::generateList メソッドの {n}
2CakePHP で select タグを生成する際に generateList メソッドをよく使います。
ちなみに使い方は下記の通りです。
$this->Model->generateList($conditions, $order,$limit, $keyPath, $valuePath);
$coditions :検索条件
$order :ソートの指定
$limit :取り出す数
$keyPath :配列のキーにするフィールド
$valuePath :配列の値にするフィールド
この $keyPath と $valuePath の指定方法が曲者で
{n}.Model.field
のようにモデル名の前に「{n}.」をつけなくては正しくデータを取得できません。
この「{n}」というのがなんなのかが気になり調べてみました。
詳しくは下記リンク先を参照していただくと分かります。
{n} ? – Cake PHP | Google グループ
以下実際に CakePHP のコードを調べてみたことをまとめます。
- generateList メソッドでは検索条件などによって findAll する
- findAll したデータから Set::extract メソッドを使用して該当するフィールドデータのみ抜き出す
- Set::extract メソッドは配列以外にもオブジェクトからも extract できるように設計されている
- 配列の場合はモデル名の前に「{n}」があるかどうかで判定している
という感じのようです。
ちなみに Set クラスは /cake/libs/set.php で定義されていて、コメントを見ると配列用のライブラリクラスのようです。
また、generateList で $keyPath, $valuePath を指定しなかった場合は下記のようになるようです。
- $keyPath には モデルで指定した $primaryKey を使用
- $primaryKey の指定がなければ ‘id’ フィールドを使用
- $valuePath には モデルで指定した $displayField を使用
- $displayField の指定がなければ ‘title’ フィールドが存在すれば ‘title’ フィールドを使用
- ‘title’ フィールドが存在しなければ ‘name’ フィールドを使用
- ‘title’ フィールドも ‘name’ フィールドも存在しなければ ‘id’ フィールドを使用
フィールド名の付け方も重要ですね。