Zend_Validate_Db_NoRecordExists で特定の条件で除外する

データベース上に既にデータが登録されているかを Zend_Validate_Db_NoRecordExists を使えば、他のバリデーションと一緒に使用できるので凄く便利。
これが新規登録とかだと、何も考えずに入力された値チェックするように書けば良い。

Users テーブルの name カラムに既に存在するかどうかチェック

<?php
$name = 'hoge';
$validator = new Zend_Validate_Db_NoRecordExists('users', 'name');
$ret = $validator->isValid($name);

これで 'hoge' ユーザが存在するかチェックをしてくれる。


これが更新系の場合だと、例えば name はそのまま更新せず、email を更新したいという場合に使うと name は既に存在していると、エラーを返えしてくる。
その場合は特定のレコードを除外して重複チェックをするというオプションがある。

Zend_Validate_Db_RecordExists および Zend_Validate_Db_NoRecordExists には、 テーブルの一部を除外してその内容を調べる方法があります。 where 句を文字列で指定するか、あるいはキー "field" および "value" を含む配列を指定します。
除外条件を配列で指定すると、!= 演算子を使用します。 つまり、テーブル内の残りのレコードの内容を確認してからレコードを変更できるのです (たとえばユーザ情報のフォームなどで使用します)。

http://framework.zend.com/manual/ja/zend.validate.set.html#zend.validate.db.excluding-records

ドキュメントには上記の用に書いてあり、サンプルコードも載っている。

<?php
$validator = new Zend_Validate_Db_NoRecordExists(
    array(
        'table' => 'users',
        'field' => 'username',
        'exclude' => array(
            'field' => 'id',
            'value' => $user_id
        )
    )
);
if ($validator->isValid($username)) {
    // ユーザ名は有効なようです
} else {
    // ユーザ名が無効なので、その理由を表示します
    $messages = $validator->getMessages();
    foreach ($messages as $message) {
        echo "$message\n";
    }
}

上の例は、id = $user_id であるレコードを除いてそのテーブル内に $username を含むレコードが存在しないことを調べます。

http://framework.zend.com/manual/ja/zend.validate.set.html#zend.validate.db.excluding-records

が、このサンプルのままやっても動かない。
なぜなら、第一引数は確かに配列を受ける事ができるが、あくまでテーブル名とスキーマのみとなっている。
Zend_Validate_Db_Abstract のコンストラクタは以下のようになってる。

<?php
public function __construct($table, $field, $exclude = null, Zend_Db_Adapter_Abstract $adapter = null)
{
    if ($adapter !== null) {
            $this->_adapter = $adapter;
        }
        $this->_exclude = $exclude;
        $this->_field   = (string) $field;

        if (is_array($table)) {
            $this->_table  = (isset($table['table'])) ? $table['table'] : '';
            $this->_schema = (isset($table['schema'])) ? $table['schema'] : null;
        } else {
            $this->_table = (string) $table;
        }
    }
}

というわけで、特定の条件を除外したい場合は、以下のように書けばいける。

<?php
$id = 1;
$exclude = array(
    'field' => 'id',
    'value' => $id
);
$validator = new Zend_Validate_Db_NoRecordExists('users', 'name', $exclude);
$ret = $validator->isValid($name);

これで id = 1 のレコードを除いて重複チェックが書けられるようになる。


ところでこれってドキュメントのバグ?英語版もそうなってたし、仕様変更になったのが追いついてないのか、ドキュメントが正しくて実装が間違ってるのか…。
ちなみに Zend Framework のバージョンは最新の 1.93PL1。

追記 2009/10/04 23:00

Issue Tracker に報告してみた。
Issue - Zend Framework