CakePHPのDboSource::expression()を調べてみた。conditions等にSQLを書くためのものかな?

DboSource::expression() というメソッドを見かけて、ちょっと気になったので調べてみました。自分としてはもっと魔法のメソッド的な便利なのだったらいいなーとか思ってたんですが、残念ながらそうでは無いみたい。でも折角調べたのでエントリーにしておきます。

たぶんconditions等にSQLを書くためのもの

情報があんまり見つからなかったのでちゃんと把握仕切れてません。なので見かけた使い方を貼っていきます。間違いも含む可能性高いです。逆に、もしexpression()を使ってたら、どういう風に使ってるか教えてもらえると嬉しいです。

SQLのNOW()を使って、INSERT時の時間で保存する
<?php
$this->data['SomeModel']['your_datetime_field'] = DboSource::expression('NOW()');
$this->Model->save($this->data);
php - Submitting current timestamp in CakePHP - Stack Overflow
サブクエリを使う
<?php
$conditionsSubQuery['`User2`.`status`'] = 'B';

$dbo = $this->User->getDataSource(); //Datasourceオブジェクトを取得

$subQuery = $dbo->buildStatement(
    array(
        'fields' => array('`User2`.`id`'),
        'table' => $dbo->fullTableName($this->User),
        'alias' => 'User2',
        'limit' => null,
        'offset' => null,
        'joins' => array(),
        'conditions' => $conditionsSubQuery,
        'order' => null,
        'group' => null
    ),
    $this->User
);
$subQuery = ' `User`.`id` NOT IN (' . $subQuery . ') ';
$subQueryExpression = $dbo->expression($subQuery);

$conditions[] = $subQueryExpression;

$this->User->find('all', compact('conditions'));
http://book.cakephp.org/ja/view/1030/%E8%A4%87%E9%9B%91%E3%81%AA-find-%E3%81%AE%E6%9D%A1%E4%BB%B6

リンク先では、全然id属性が無いので、良い位置に#でリンク出来ないですが、ページのかなり下のほうです。

CASE式を使う
<?php
$expression = $this->testDb->expression("CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col");
$result = $this->testDb->fields($this->Model, null, array("id", $expression));
https://github.com/cakephp/cakephp/blob/1.3.6/cake/tests/cases/libs/model/datasources/dbo_source.test.php#L2978
エスケープは自分でする

Notice that every user input should be properly sanitized and escaped
if you plan to use it as a SQL expression with DboSource::expression
().
So, as you can see in my article, you can use SQL functions like this:

<?php
// inside the model scope 
$db = $this->getDataSource(); 
$this->data[$this->alias]['signedup'] = $db->expression('CURDATE()'); 

but be aware that expressions won't be automatically escaped so you
need to it manually with DboSource::value() :

<?php
$db = $this->getDataSource(); 
$expression = sprintf("CONCAT('[', %s, ']')", $db->value($userInput)); 
$this->data[$this->alias]['signedup'] = $db->expression($expression); 
http://cakephp.1045679.n5.nabble.com/REgarding-using-Mysql-functions-like-NOW-CURDATE-etc-td1309285.html#a1309290

expression()で書いたSQLはエスケープされないので、外部からの入力値などはDboSource::value()でエスケープして置かないとです。

解説

http://book.cakephp.org/ja/view/1030/%E8%A4%87%E9%9B%91%E3%81%AA-find-%E3%81%AE%E6%9D%A1%E4%BB%B6 で色々書いてあるとおり、conditionsにはCakePHPでの書き方で条件を書いていきます。なのでexpression()でSQLを書くのは、それと干渉しないようにっぽいですね。

<?php
function expression($expression) {
	$obj = new stdClass();
	$obj->type = 'expression';
	$obj->value = $expression;
	return $obj;
}
https://github.com/cakephp/cakephp/blob/1.3.6/cake/libs/model/datasources/dbo_source.php#L206

コードはすごくシンプルです。単にstdClassに詰めて返すだけなので、このオブジェクトを受け取った側でうまいこと処理するんでしょう。たぶん・・・。そこまで追いきれませんでした。全文検索をかけても、stdClassのtype === 'expression'を見てるのが3箇所くらいしか見当たらないんですよねぇ。

環境

Mac Mac OS X 10.5.8(Leopard)
MAMP 1.7.2
CakePHP 1.3.6
php 5.2.6