Nested where clause on Zend_Db_Select

Zend Framework: 1.11.0
PHP: 5.3.3

目的

権限や表示属性を確認しつつ検索するWHERE句を持つSQLをZend_Db_Selectを使って作る。
簡単に書くと、以下の構造を持つSQLを作りたい。

SELECT * FROM table
WHERE
(
    (データへのアクセス権チェック)
    AND
    (データのチェック)
) AND (
    (検索条件)
)

結論

追記:

    $where_permit = array();
    $where_permit[] = $this->_db->quoteInto('g=?', 1);
    $where_permit[] = $this->_db->quoteInto('o=:id');
    $where_permit = implode(' AND ', $where_permit);

    $where_data = array();
    // 略

    $where_search = array();
    // 略
    $where_search = implode(' OR ', $where_search);

    $select->where($where_permit)->where($where_data)->where($where_search);

これが好きかも。


SQL文を書きましょう。

$select->where(
'(' .
    '(データへのアクセス権チェック)' .
    ' AND ' .
    '(データのチェック)' .
')'
);
$select->where(
'(' .
    '(検索条件)' .
')'
);

ダサすぎる。
いつでも$select->where('条件','値')を呼び出せるのが利点なのに、
自作のWHERE句管理機構を作って最後に流し込むようなもの。
これではエスケープしか利点がないと思う。

Zend_Db_Selectを見る

公式マニュアル - http://framework.zend.com/manual/en/zend.db.select.html

$select->where()が呼び出されると以下のように続いていくが、
結局はZend_Db_Select._parts[self::WHERE][]に'key = value'の形で追加されてゆく。

$select->whereの道筋

1. Zend_Db_Select.where()
2. Zend_Db_Select._where()
3. Zend_Db_Adapter_Abstract.quoteInto()
4. Zend_Db_Adapter_Abstract.quote()
(以下略)

Zend/Db/Select.php

    /**
     * Adds a WHERE condition to the query by AND.
     *
     * If a value is passed as the second param, it will be quoted
     * and replaced into the condition wherever a question-mark
     * appears. Array values are quoted and comma-separated.
     *
     * <code>
     * // simplest but non-secure
     * $select->where("id = $id");
     *
     * // secure (ID is quoted but matched anyway)
     * $select->where('id = ?', $id);
     *
     * // alternatively, with named binding
     * $select->where('id = :id');
     * </code>
     *
     * Note that it is more correct to use named bindings in your
     * queries for values other than strings. When you use named
     * bindings, don't forget to pass the values when actually
     * making a query:
     *
     * <code>
     * $db->fetchAll($select, array('id' => 5));
     * </code>
     *
     * @param string   $cond  The WHERE condition.
     * @param mixed    $value OPTIONAL The value to quote into the condition.
     * @param int      $type  OPTIONAL The type of the given value
     * @return Zend_Db_Select This Zend_Db_Select object.
     */
    public function where($cond, $value = null, $type = null)
    {
        $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true);

        return $this;
    }


追加されてゆくけれど、残念ながら以下のようには書けません。

$where_permit = $db->select()->where('権限確認');
$where_data   = $db->select()->where('データ確認');
$where_search = $db->select()->where('検索条件');
$select->where(
'(' .
    (string)$where_permit .
    ' AND ' .
    (string)$where_data .
') AND (' .
    (string)$where_search .
')'
)

Zend_Db_Selectは__toString()でSQLを出力できるけれど、
その過程でFROM句が入っていないとWHERE句を出力しないように
してあります。
また、名前の通りSELECTが容赦なく文字列の先頭に挿入されます。

(string)$selectの道筋

1. Zend_Db_Select.__toString()
2. Zend_Db_Select.assemble()
3. Zend_Db_Select._renderWhere()
※ _renderClause は Zend_Db_Select::_partsInit 順に実行される

Zend/Db/Select.php

    /**
     * Render WHERE clause
     *
     * @param string   $sql SQL query
     * @return string
     */
    protected function _renderWhere($sql)
    {
        if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) {
            $sql .= ' ' . self::SQL_WHERE . ' ' .  implode(' ', $this->_parts[self::WHERE]);
        }

        return $sql;
    }

注意しなくてはいけないのは

たとえば以下のようなコードはだめ。

$select = $db->select()->from($table);
$select->where('権限確認');
$select->where('データ確認');

if (operation == 'AND')
{
    $select()->where('検索条件');
} else {
    $select()->orWhere('検索条件');
}

echo (string)$select;
>> SELECT * FROM $table WHERE (権限確認) AND (データ確認) OR (検索条件)


この順番だからかろうじて権限は守られているけれど、
(データ確認) AND (権限確認) OR (検索条件) とかやったら。
怖すぎる。

そもそもorWhereに需要はあるのだろうか。
A=? AND B=? OR C=? AND D=? とか考えるだけでいやになる。

まとめ

これを見てる企業の人、本家にその拡張を提案してください。

ぼやき

しかし、このフレームワークを使っている人たちは
みんなどうやってるんだろうか。
こういった処理は特殊じゃなくて確実に発生すると思うのだが。