CakePHPでセションメッセージ、フラッシュメッセージを二度以上使いたかった

CakePHPでセションメッセージを二度以上使いたかった。

CakePHPでセションを使ってメッセージを表示することができる。通常はエラーメッセージを表示させたりするためにeditやaddなどでエラーメッセージを収めておき、リダイレクト先のviewでflush()を呼んで表示している。

便利な機能なのだけれど、setFlashという名前の通り、このメッセージは一度限り設定できる。コントローラが大きくなってくるとセションメッセージの設定位置が散らばったりするため、意図せずしてセションメッセージを後から上書きしてしまうことになってしまう。今回はこの問題を調べてなんとかしてみた。

1. やりたいこと
下のようなコードがあったとする。

Class HogeController extends AppController {
public function action() {
$this->Session->setFlash('hoge');
$this->Session->setFlash('fuga');
}
}

このとき、actionのviewでflush()でセションのメッセージを呼び出したとき、hogeは無くなってしまってfugaが表示される。
今回はここで何とかして hogefuga という風に連結したメッセージを表示させてみる。

$this->Session->setFlash('hoge');
$flashMessage = $this->Session->read('Message.flash');
$this->Session->setFlash($flashMessage['message'] .= 'fuga');

これでセションのフラッシュメッセージを追加できる。この場合だと hogefuga が表示される。

こうすると動かせるようになるのは以下の理由。
SessionComponent.phpにあるsetFlashのメソッドの定義を見てみる。

public function setFlash($message, $element = 'default', $params = array(), $key = 'flash') {
CakeSession::write('Message.' . $key, compact('message', 'element', 'params'));
}

setFlashというのはCakeSessionのwriteメソッドを呼んでいる。このメソッドの定義はCakeSession.phpにある。このクラスにはwriteの他にreadもある。このreadを使って上記のように取り出して追記している。

public static function read($name = null) {
if (!self::started() && !self::start()) {
return false;
}
if (is_null($name)) {
return self::_returnSessionVars();
}
if (empty($name)) {
return false;
}
$result = Hash::get($_SESSION, $name);

if (isset($result)) {
return $result;
}
self::_setError(2, "$name doesn't exist");
return null;
}
public static function write($name, $value = null) {
if (!self::started() && !self::start()) {
return false;
}
if (empty($name)) {
return false;
}
$write = $name;
if (!is_array($name)) {
$write = array($name => $value);
}
foreach ($write as $key => $val) {
self::_overwrite($_SESSION, Hash::insert($_SESSION, $key, $val));
if (Hash::get($_SESSION, $key) !== $val) {
return false;
}
}
return true;
}

readとwriteするときにはキーによって制御している。このキーはsetFlashを使ったときには ‘Message.flash’ が用いられている。なのでこれを手動でreadして改めて連結してsetFlashしている。

これでセションのメッセージを追記することができる。
なにか別名を与えてsetFlashの代わりにこれを全部使っても良いかなと思うのだけど、しばらく様子をみてからにしよう。

apacheのvirtualhostの設定でservernameが一致しなかったときの挙動

apacheのvirtualhostの設定でservernameが一致しなかったときの挙動について思った通りの挙動ではなかったので修正した。

apacheのコンフィグファイルの中にホスト名ごとにDocumentRootなどを変えるvirtual hostの機能がある。これはとても便利なんだけれど、思った通りの挙動ではなかったので調べてみた。

FreeBSDではportsからapacheをインストールするとそのコンフィグファイルは /usr/local/etc/apache22に置かれる。

その中の extra/httpd-vhosts.conf にvirtualhostの設定が書いてある。デフォルトコンフィグがあるのでそれを参照しながら書いたりしてる。これを使うとホスト名での環境の変更、ポート番号ごとによる変更などが可能になる。
virtualhostは参照されたホスト名によってコンフィグを切り替えるので、アクセスされたホスト名を示す ServerName によって切り分ける。
具体的には下記の例。

<VirtualHost *:80>
DocumentRoot /usr/local/www/www1.example.com
ServerName www1.example.com
</VirtualHost>

<VirtualHost *:80>
DocumentRoot /usr/local/www/www2.example.com
ServerName www2.example.com
</VirtualHost>

この設定であればwww1.example.comなホスト名でアクセスすると /usr/local/www/www1.example.com のファイルが参照される。
www2.example.com でアクセスすれば /usr/local/www/www1.example.com が参照される。
ここで www3.example.com でアクセスされるとこのコンフィグには設定が存在しない。
この設定の上位には元を設定するための httpd.conf があって、そちらの設定が適用されるのかと思ってた。だけどどうやら違うらしく、httpd-vhosts.conf の最上部が適用されるらしい。
つまりこの例であれば /usr/local/www/www1.example.com が表示されるということに。
これだと思ってたより違う感じで困ってしまうので、そのような場合のためにServerNameが一致しないときのDcoumentRootを設定しておく。
設定のやりかたは簡単でServerNameを記述しないディレクティブを一つ置いておけば良い。

# servernameが一致しなかった時用
<VirtualHost *:80>
DocumentRoot /usr/local/www/apache22/data
</VirtualHost>

これでServerNameが合わなかった時の設定に合致するので変なホスト名の設定がひょじされることはなくなった。

めでたしめでたし。