Zend_Filter_Input で重複のチェック
unique なカラムのチェックをバリデート時に行いたい。
Table scheme
CREATE TABLE `page` ( `id` int PRIMARY KEY, `user_id` int, `url` varchar(255), UNIQUE (`user_id`, `url`) );
My/Validate/Unique.php
<?php class My_Validate_Unique extends Zend_Validate_Abstract { const DUPLICATE = 'duplicate'; protected $_messageTemplates = array( self::DUPLICATE => "already exists" ); protected $_messageVariables = array(); private $table; public function __construct($table) { $this->table = $table; } /** * @param $fields array field の連想配列。 * primary key, 重複チェック対象 field ... */ public function isValid($fields) { $table = new $this->table; $select = $table->select(); reset($fields); $select = $this->addWhere($select, key($fields), current($fields), false); // 自身は除外 array_shift($fields); foreach ($fields as $field => $value) { $select = $this->addWhere($select, $field, $value); // 重複チェック対象 $this->_messageVariables[$field] = $field; $this->$field = $value; } if ($row = $table->fetchRow($select)) { $this->_error(self::DUPLICATE); return false; } return true; } private function addWhere($select, $field, $value, $match = true) { if (!is_null($value)) $select = $select->where($field . ($match ? ' = ?' : ' != ?'), $value); return $select; } }
My/Filter/Input.php
<?php class My_Filter_Input extends Zend_Filter_Input { public function __construct($filterRules, $validatorRules, array $data = null, array $options = null) { parent::__construct($filterRules, $validatorRules, $data, $options); $this->addValidatorPrefixPath('My_Validate', 'My/Validate'); $this->setDefaultEscapeFilter(new My_Filter_Ident()); // DB にいれるのでエスケープはしない } public function getMessages() { // <field>-n は <field> とみなす // (fields 指定ありなしを混在させるために) $messages = parent::getMessages(); foreach ($messages as $field => $strs) { if (preg_match('/^(.*)-\d+$/', $field, $matches)) { $originField = $matches[1]; if (array_key_exists($originField, $messages)) $strs = array_merge($message[$originField], $strs); $messages[$originField] = $strs; unset($messages[$field]); } } return $messages; } } // 何もしない filter class My_Filter_Ident implements Zend_Filter_Interface { public function filter($value) { return $value; } }
以上のものを使う。
Filter_Input 設定ファイルは ini だと表現力が足りないし XML だと面倒なので http://wadslab.net/2008/05/zend_config-3/ を使って YAML で。
conf/page_filter.yml
filters: *: StringTrim validators: url: - - StringLength - 1 - 255 # url のエイリアス # fields なしの validator も利用するためにやむなく url-2: - - Unique - Page - fields: - id - user_id - url - messages: - URL "%url%" は既に使用されています。
ActionController でみせているけど rails みたいにモデルでやるほうが楽でいい気がする。
<?php class PageController extends Zend_Controller_Action { const FILTER_INPUT_CONFIG = 'page_filter.yml'; private function getFilterInput() { $config = new My_Config_Yaml(APPLICATION_ROOT . '/conf/' . self::FILTER_INPUT_CONFIG); return new My_Filter_Input($config->filters->toArray(), $config->validators->toArray()); } public function createAction() { $req = $this->getRequest(); $params = $req->getPost(); unset($params['id']); // id をセットされるとチェックを抜けられる $params['user_id'] = $req->getParam('id'); // どこかから $input = $this->getFilterInput(); $input->setData($params); if ($input->isValid()) { ...
うーん。Zend_Form だともっと簡単にできたりするんだろうか。あれもなんかいまいちな雰囲気が。Zend Framework って基本の構成はできてるけど使いやすくするための部分がまだまだという感じがしますねぇ。ライブラリーの疎結合を謳っているのでそこが阻害要因になっているというのもあるかもしれません。