Posts tagged CakePHP
CakePHP 1.2 の deleteAll
1CakePHP 1.2 の saveAll その1
CakePHP 1.2 の saveAll その2
のエントリーを書いているときに deleteAll というメソッドがあり気になったので調べてみました。
CakePHP 1.1 で開発時に delete するときに ID を指定するしか方法がなく、ある条件でまとめて削除したいときなどは findAll してからループで delete していました。このときも条件を指定して削除する方法がないかとソースを調べたのですがありませんでした。
使い方は非常に簡単です。findAll のように条件を指定して deleteAll を実行するだけです。
$conditions = array('User.name'=>'suzuki'); if ($this->User->deleteAll($conditions)) { $this->Session->setFlash('削除しました'); } else { $this->Session->setFlash('削除に失敗しました'); }
実行される SQL は以下のような感じです。
SELECT `User`.`id` FROM `users` AS `User` WHERE `User`.`name` = 'suzuki' DELETE `User` FROM `users` AS `User` WHERE `User`.`id` IN (8, 10)
SELECT で条件に合う ID を抽出して WHERE IN で DELETE しています。
ただし CakePHP1.2 の削除ですが、MySQL 4.0 では SQL の DELETE 文でエラーになってしまいます。
MySQL 5.0 ではエラーにならずに削除できました。エラーになる原因は “DELETE” の後ろにテーブルのエイリアス名があるためです。
これは “cakephp1.2でのPostgreSQLエラー。” フォーラム – CakePHP Users in Japan でもあるように既に修正されているようですが、昨日 CakePHP のサイトからダウンロードした CakePHP 1.2.0.6311 beta ではまだ修正されていないようです。
CakePHP 1.2 の saveAll その2
3CakePHP 1.2 の saveAll その1 では同一モデルへの複数レコードを saveAll で保存しました。今回はアソシエーションのモデルのデータを saveAll で保存する方法です。
アソシエーションのモデルのデータを保存
モデル
user.php
< ?php class User extends AppModel { var $name = 'User'; var $hasMany = array('Comment'); } ?>
comment.php
< ?php class Comment extends AppModel { var $name = 'Comment'; var $belongsTo = array('User'); } ?>
コントローラ
users_controller.php
function add() { if (!empty($this->data)) { $this->cleanUpFields(); $this->User->create(); if ($this->User->saveAll($this->data)===false) { $this->Session->setFlash('保存に失敗しました); } else { $this->Session->setFlash('保存しました'); } $this->redirect(array('action'=>'index'), null, true); } }
ビュー
users/add.ctp
< ?php echo $form->create('User');?> < ?php echo $form->input('User.name');?> < ?php echo $form->input('Comment.body'); ?> < ?php echo $form->end('Submit');?>
CakePHP 1.2 の saveAll その1 の同一モデルへの複数レコードの保存よりも使い道は多いのではないかと思います。
追記
このエントリーは CakePHP 1.2.0.6311 beta で検証しています。
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 携帯専用サイトを作成する
6CakePHP で携帯用のページを作成する際に CakePHP 携帯用ビューを表示する | Shin x blog のページが大変参考になります。
しかし、/m/ のような URL ではなく携帯専用サイトにしたかったので下記のような方法で実装しました。なお、PC でアクセスしたときには /pc.html という静的なページを表示するようにしてあります。
また、PC、携帯の振り分けはユーザエージェントで行っています。
携帯用コンポーネント
app/controller/component/mobile.php を作成し、PEAR の Net_UserAgent_Mobile を使用しています。このコンポーネントでユーザエージェントを判定して PC だったら pc.html へリダイレクトします。
PEAR の Net_UserAgent_Mobile はサーバにインストールしてもいいのですが、今回は app/vendors/ ディレクトリに入れました。また、app/vendors/ に include_path を通すために、CakePHPガイドブック を参考に include_path_vendors.php を作成しました。
app/controller/component/mobile.php
vendor("include_path_vendors"); vendor("Net/UserAgent/Mobile"); class MobileComponent extends Object { function startup(&$controller) { $this->controller = $controller; $mobile = &Net_UserAgent_Mobile::factory(); if ($mobile->isNonMobile()) { $this->controller->redirect("/pc.html"); } } }
今回は簡単に PC か携帯でアクセス振り分けているだけですが、画面の大きさやキャリアなどによって色々な処理の振り分けが考えられます。
携帯用ヘルパー
app/views/helper/mobile.php を作成して HTML 出力時に文字コードを Shift-JIS へ変換します。ヘルパーの afterRender メソッドを使用しています。
class MobileHelper extends Helper { function afterRender() { $out = ob_get_clean(); $out = mb_convert_kana($out, "rak", "UTF-8"); $out = mb_convert_encoding($out, "SJIS", "UTF-8"); ob_start(); echo $out; } }
コントローラで携帯用コンポーネント、ヘルパーを使用する
app/app_controller.php で携帯用コンポーネントと携帯用ヘルパーを使用します。
var $components = array('Mobile'); var $helpers = array('Mobile');
app_controller.php で設定しておけば全てのコントローラで共通に読み込むので各コントローラにその都度書く必要がなくなります。DB の管理画面など PC 用のコントローラが必要な場合は各コントローラに書いた方がいいでしょう。今回は完全に携帯用にしています。PC 用の管理画面もあるのですが、サブドメインを変えて app ディレクトリも違うものを使用しています。
また、各コントローラ内で設定するタイトルの文字コードを変換しないといけないので、app_controller.php の beforeRenderメソッドでタイトルの文字コードを変換します。
function beforeRender() { $this->pageTitle = mb_convert_encoding($this->pageTitle, "SJIS", "UTF-8"); parent::beforeRender(); }
以上で携帯専用のサイトが CakePHP で作成できました。
コントローラの afterFilter で文字コードを変換する方法
コントローラの afterFilter で文字コードを変換することもできます。
app/controller.php
function afterFilter() { parent::afterFilter(); $out = ob_get_clean(); $out = mb_convert_kana($out, "rak", "UTF-8"); $out = mb_convert_encoding($out, "SJIS", "UTF-8"); ob_start(); echo $out; }
この場合は、beforeRender でのタイトルの文字コード変換の処理が必要ないのと、携帯用ヘルパーは必要ありません。
PC 用のコントローラも作りたいときは携帯用コンポーネント、携帯用ヘルパーを使用する方法がよいかと思います。
CakePHP アクションでエレメントを出力する方法
2Rendering elements from controllers – cakebaker
上記エントリでエレメントのみを出力する方法が紹介されています。
バージョン1.2 の場合
app/views/elements/controller/hoge.thtml のエレメントを出力する場合
$this->render(DS.'elements'.DS.'controller'.DS.'hoge');
バージョン1.1 の場合
app/views/elements/controller/hoge.thtml のエレメントを出力する場合
$this->render(null, null, ELEMENTS.'controller'.DS.'hoge.thtml');
注意点
レイアウトは指定しないと default になります。Ajax などでエレメントのみを出力したい場合はレイアウトに ‘ajax’ を指定してhead タグや body タグが出力されないようにする必要があります。
v1.2$this->render(DS.'elements'.DS.'controller'.DS.'hoge', 'ajax');
v1.1$this->render(null, 'ajax', ELEMENTS."controller".DS."hoge.thtml");
CakePHP JQuery ヘルパー
3JQuery helper for CakePHP ( PQuery port ) at NGCoders
CakePHP から JQuery を簡単に使うことができます。
正確には PQuery ヘルパーかもしれません。PQuery は JQuery を PHP から簡単に使用するライブラリで PQuery ヘルパーと同じ開発者が開発しています。
参考:PQuery – PHP and JQuery at NGCoders
インストール
JQuery helper for CakePHP ( PQuery port ) at NGCoders から JQuery ヘルパーをダウンロードし、解凍した pquery.php を /app/views/helpers にコピーします。
そのほかに jquery.js ファイルも必要になります。こちらもダウンロードして /app/webroot/js にコピーします。
コントローラ
Pquery ヘルパーと Javascritp ヘルパーを使用します
var $helpers = array('Pquery', 'Javascript');
ビュー
jquery.js の読み込み
<?php echo $javascript->link('jquery.js'); ?>
使用例
トグルボタン
<div id='msg'>Message...</div> <?php echo $pquery->link_to_function('toggle', $pquery->toggle('#msg'));?>
フォームで送信された内容により HTML を更新
入力したテキストを /controller/action/ に GET で送信し、id=idtoupdate に受け取った HTML を表示する
<?php echo $pquery->form_remote_tag(array('url'=>'/controller/action/','update'=>'#idtoupdate'));?> <input type='text' name='field' /> <input type='submit' /> <div id='idtoupdate'></div>
pquery.php を見ると色々なメソッドがあります。使い方はソースを見ればすぐに分かると思います。
CakePHP Security コンポーネントのまとめ
1CakePHP の Security コンポーネント の動作を調べたのでまとめておきます。
この Security コンポーネントをうまく使用すればクロスサイトリクエストフォージェリ(CSRF) を防ぐことができるでしょう。
トークンの使用
フォームにワンタイムトークンを実装する方法です。
コントローラの beforeFilter メソッドでトークンをチェックするアクションを指定
function beforeFilter() { $this->Security->requireAuth('login'); }
ビューのフォーム内にトークンを設定
<?php echo $html->formTag(); ?>
トークンが hidden 属性で生成される
トークンが一致しない場合
requireAuth で指定したアクションに POST でアクセスがあるとセションに保存したトークンとフォームから送られてきたトークンが一致するかチェックします。またそのほかにトークンの有効期間もチェックします。有効期間は CAKE_SECURITY の設定により違います。
CAKE_SECURITY | 有効期間 |
---|---|
high | 10分 |
medium | 100分 |
low | 300分 |
トークンが一致しないと SecurityComponent の blackHole メソッドが実行されます。このメソッドでは
header('HTTP/1.0 404 Not Found');
を出力して exit します。(画面は空白)
任意の処理を実行したい場合は blackHoleCallback でコールバック関数を指定します。
設定できるのは同じコントローラ内のアクションのみになります。
function beforeFilter() { $this->Security->blackHoleCallback = "securityError"; $this->Security->requireAuth('login'); } function securityError() { die("security error!"); }
トークンチェックをするアクションを複数指定するときはカンマでつなげる
$this->requireAuth('login', 'delete');
特定のコントローラからのポストのみ許可する
$this->Security->allowedControllers = array("users");
これを指定するとたとえトークンが一致しても許可のないコントローラからのポストの場合は blackHole メソッドを実行します。
特定のアクションからのポストのみ許可する
$this->Security->allowedActions = array("action");
これを指定するとトークンが一致しても許可のないアクションからのポストの場合は blackHole メソッドを実行します。
ポストのみ受け付けるようにする
$this->requirePost('login');
これはトークンとは違い、POST のみ許可して GET でのアクセスを不可にします。
/users/login/ のような URL でのアクセスも GET なので不可になります。
POST の処理だけを実行したいようなアクションに使用します。
CakePHP Textヘルパーの truncate を全角文字に対応させてみた
5CakePHP の Text ヘルパーに truncate という指定された文字列を任意の長さに省略するメソッドがあります。
しかし、このメソッドは全角文字を考慮していないため全角文字に用いると文字化けすることがあります。
そこでこのメソッドを全角文字に対応させてみました。
function truncate($text, $length, $ending = '…', $exact = true) { if (strlen($text) < = $length) { return $text; } else { mb_internal_encoding("UTF-8"); if (mb_strlen($text) > $length) { $length -= mb_strlen($ending); if (!$exact) { $text = preg_replace('/\s+?(\S+)?$/', '', mb_substr($text, 0, $length+1)); } return mb_substr($text, 0, $length).$ending; } else { return $text; } } }
直接 Text ヘルパーを修正するとバージョンアップなどのときに困るので、
cake/libs/view/helpers/text.php を app/views/helpers/mb_text.php にコピーしクラス名を
class MbTextHelper extends Helper{
として、truncate メソッドを上記のように修正しました。
使用するときはコントローラ内で、
var $helpers = array("MbText");
view で
echo $mbText->truncate("あいうえおかきくけこ", 5, "…", true);
とすると
あいうえ…
と表示されます。
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. コントローラの関数
11月に読んだ本
2最近は仕事関係の本や自己啓発関係の本が多い。特に今年は小説を読んでないので、今月は少し小説を読もうと思っています。