Zend_Frameworkにおけるバリデーションコードの置き場所
Zend_Frameworkにおけるバリデーションコードの置き場所ってどうよ? ってのをいろいろ試行錯誤した結果、今のところこうしてるよ、というのをまとめてみる。
DBアクセスレイヤーは基本的にZend_Db_Tableを使っている。テーブルにマッピングできないものも、データ操作レイヤーに関するものはapplication/models/以下に置いて、Zend_Db_Tableと同じようなインターフェースを持たせて使う。で、共通化できるバリデータはこのモデルクラスにまとめて持たせる。
class ModelBase extends Zend_Db_Table
{
protected $_validators = array();
public function getValidators()
{
return $this->_validators;
}
public function getValiator($name)
{
if (!isset($this->_validators[$name]) {
$this->_validator[$name] = new Zend_Validate();
}
return $this->_validator[$name];
}
}
みたいなベースクラスを作っておいて、 各実装クラスでは、
class FooTable extends ModelBase
{
public funciton init()
{
$this->getValidtor('foo')
->addValidtor(new Zend_Validate_StringLength(5, 15));
}
}
みたいな感じでinit()内でバリデータをセットしておく。そうすると、Zend_Db_Table_Rowとかでこのバリデータを使いたい場合は、
class FooTable_Row extends Zend_Db_Table_Row
{
public function isValid()
{
$result = true;
foreach ($this->getTable()->getValidators() as $name => $validator) {
$result = $validator->isValid($this->$name) && $result;
}
return $result;
}
}
なんて感じでバリデーションを書ける(上の例は共通バリデータをぶん回すだけでOKな場合の話ね)。また、このFooTableに対してのCRUDを行うようなZend_Formでは、
class FormFoo extends Zend_Form
{
public function init()
{
$fooTable= new FooTable();
$foo = $this->createElement('input', 'foo');
$foo
->addValidator($fooTable->getValidator('foo'))
->addValidator(new CustomValidatorForForm());
$this->addElement($foo);
}
}
なんて感じでFooTableの該当パラメータ用のバリデータを使うことができるし、それにフォーム専用の追加パラメータを追加することもできる。
アクションコントローラのパラメータの場合は、
class FooController extends Zend_Controller_Action
{
public function fooAction()
{
$foo = $this->_getParam('foo');
$fooTable = new FooTable();
$validator = $fooTable->getValidator('foo');
if (!$validator->isValid($foo)) {
// IIIINNNNVALIIIIIIIDDDDDDDDDDD!!!!!!!!!!!!!!!!
}
}
}
なんて感じで、使いたい要素のバリデータだけ引っ張り出して使える。
ということで、バリデータはモデルクラスにパラメータ名ごとに共通(使い回せる)部分を突っ込んでおいて、シチュエーションごとにそれを取り出しつつ、シチュエーションに特化したバリデータはその場でaddValidator()して使う、いう置き方が一番きれいなんじゃないかと思うのだが、どうだろう。
フィルターに関しては、まとめて同じ処理をしたいという要求がそれほど大きくない、というか同じパラメータでも入力シチュエーションによってどういうフィルターをかけたいかが変わるんで、必要な場所(アクションメソッドとかフォーム要素とか)にそれぞれ書くのが無難な気がする。フィルタークラス自体はアプリケーションに特化したものをapplication/filters以下に用意しておいた方が楽だけど。
と、久しぶりにZend_Frameworkで新しいアプリを書こうと思い、どうせならZend_Frameworkのコンポーネントを使い倒した書き方にしてみるかといろいろ試行錯誤した結果、こんな感じになったという話でした。
Windows Server 2008でManaged DirectXを使ったASP.NET MVC3が動かないという、ググってもレアケース過ぎて解決策が見つからないシチュエーションからの現実逃避の一環でもある。単にDirect 3Dのベクトル演算関数をサーバーサイドでも使いたいだけなんだけどなー。なんで依存関係が解決できないんだろう。
Zend_View_Filterで文字コード変換するときの問題
前提条件
Zend_Frameworkで、内部エンコーディングとしてUTF-8を利用しているWebアプリケーションを構築していて、一部ページのみガラケー(SHIFT_JIS)対応したい。
問題
Zend_View_FilterでUTF-8からSHIFT_JIS変換するフィルターを作り、ガラケー用コントローラのinitで
public function init()
{
$this->view->addFilter('文字コード変換フィルター名');
}
とかしてみた。すると、一見ちゃんと文字コード変換がかかったかと思いきや、一部文字化けしている。
調べてみると、addFilterしたフィルターはZend_Viewがレンダリング処理を走らせるたびに実行されるんで、レイアウトとかを使って内部で複数回Zend_View::renderされてしまうと、その部分で複数回文字コード変換がかかる→文字化けする、ということらしい。
ZendFrameworkで文字コード変換 - slumbersでは、Zend_Layout_Controller_Plugin_layoutを拡張したクラスで出力時に変換をかけたらうまくいったよって話が書いてあるけど、もうちょい楽な方法ないかなー。
解決策
複数回フィルターがかかるのがいやなら、出力する文字列が最後まで確定した後にフィルターがかかればいいんじゃないか、ということで、ガラケー用のレイアウトファイルの一番最後に、
<html>
....(省略)
<?php echo $this->layout()->content; ?>
....(省略)
</html>
<?php $this->addFilter('文字コード変換フィルター名');
とかしてみた。うまくいった。