Abeanibot introduced #2

前の記事で、

検索結果をローカルに保持してある sqlite のデータベースと照合する。新しいものがあればそれをツイートする

と書いた。ここのやり方は sqlite ならではのものになっているので一応メモしておく。

普通に考えると、番組 A が既にデータベースにあるかを照合し、その結果によって処理を振り分けるには

  • [cci]select * from program where name=:A[/cci] する
  • レコードセットが行を返さなかったら [cci]insert into program (name) values (:A)[/cci] する。そしてツイートする

と、1 番組につき 2 回クエリを発行しなければならない。これは無駄な感じがするので、例の sqlite 側の関数の実体を php で実装できる機能を使う。


$db = new SQLite3("program.db");
$db->createFunction("php_name", __NAMESPACE__ . "\\phpName");

$insertedNames = [];

function phpName ($name) {
global $insertedNames;
$insertedNames[$name] = 1;
return $name;
}

こんな感じにしといて、[cci]insert or ignore into program (name) values (php_name(:A))[/cci] とすると、番組 A がテーブルに存在していなかった時のみ php_name 関数が呼ばれ、そして行が追加される。php_name 関数自体は何も副作用をもたらさないのだがこの時 PHP 側に制御がいったん移るので、そこで適当なものに覚えておく。こうすると 1 番組につき 1 クエリで済むようになる。

ところで createFunction の第 2 引数には PHP 側の関数を指定する。PHP で関数オブジェクトを指し示す方法としては、関数名の文字列、[cci]array([$this object], [method name string])[/cci] のような配列、クラス名を前置した静的メソッド名文字列などが段階的に追加されてきた。加えて最近の PHP では function リテラルそのものも使えるようになっている(これは Closure クラスのインスタンスが実体だ)。

この段階的というのがミソで、sqlite3::createFunction は最も初歩的な関数名文字列しか対応していないっぽいのである(かろうじて、名前空間は認識するが)。このへんの「ううn…」さがまあ PHP らしさなのだが。

Leave a Reply

Your email address will not be published. Required fields are marked *