人間は皆誰しも実行時にSQLを組み立ててデータを取り出すことが大好きだ。そしてその行為の危険さもわからぬまま人類は太陽に向かってゆく。
あとこの記事で紹介してるライブラリは私が書いたコードじゃないです ヾ(〃><)ノ゙
はじめに
まあ落ち着いて、去年のPHPカンファレンスの徳丸先生1のスライドを読んでほしい。ちなみに私は去年のPHPカンファレンスは風邪でダウンしてたので行けなかった。QiitaにはSlideShareの埋め込みができないので、↓の枠はただの画像リンクだ。
Phpcon2015 from Hiroshi Tokumaru
「うちはO/Rマッパーを使ってるよ」ってひとには、この記事はかんけいない。そう、人類はDoctrineのような叡智を手にしてる。素直に素晴らしいMVCモデルやDBレイヤーを持ったフレームワークを選ぶべきだ。私は使ったことがないので無責任な発言はできないが、Laravelのクエリビルダーもなかなか使ひ勝手は良さそうに見える。
- データベースと Doctrine (“The Model”) | Symfony2日本語ドキュメント
- Database: Query Builder - Laravel - The PHP Framework For Web Artisans
PHPとDB/SQL
それでも人類は、自分の手でSQLを書きたがる。PDOを使って。そんなときは必ず以下の記事の内容を頭に叩き込まなければならない。
世の中には「フレームワークとかよくわからないし、シンプルなPHPで…」とおっしゃる初心者の型も居るが、そんなひとには「急がば回れ」との名せりふを贈りたい。たぶん有名なフレームワークに乗った方が安全だよ2。
あ、PDO(PHP Data Objects)を使ってないひとは、これ以上この記事を読む意味はあんまりないです。
PDOのいいところ
文字列とかをいい感じにエスケープしてくれるところ。
$stmt->bindValue(':user', $user, \PDO::PARAM_STR);
PDOのだめなところ
可変個のパラメータに弱すぎる… つまり、IN修飾子。
SELECT * FROM `users` WHERE `id` IN (123, 343, 893, 114, 514)
この形式をPDOでプレースホルダに埋め込む方法は当然あるのだけれど、ここでは紹介しない。
TetoSQL
Packagistに登録済みなので、Composer導入済みの環境でcomposer require zonuexe/tetosql
を実行すれば導入可能。
このライブラリはSQLにバインドする値の実行時型検査を行った上でPDOの機能をラップするあたりが特徴で、SQLインジェクション脆弱性に一定の効果はあるだろう。配列をサポートすることでIN修飾子も問題なく利用できる。
御託はいいので利用例を見せる。
<?php
namespace MyApp;
use Teto\SQL;
const find = '
SELECT * FROM `users`
WHERE `status` IN (:statuses@int[])
LIMIT :offset@int, :limit@int
';
$conn = new \PDO('sqlite:/tmp/db.sq3', null, null, [PDO::ATTR_PERSISTENT => true]);
$data = Query::execute($conn, find, [
':statuses' => [1, 3],
':offset' => 60,
':limit' => 30,
])->fetch(\PDO::FETCH_ASSOC);
おてがるですね。業務では基本的にMySQLを使ってるが、おそらくSQLiteなどのDBでも動作するだろう。
対象DB
このライブラリはもともとMySQLで利用するために書かれたが、PDOがサポートするDBであればおよそ動作するだろう。ただし、後述するINTの範囲により意図しない動作をすることはできる。
SQL文
現在の仕様では、TetoSQLが受け入れるSQL文はすべて改行コードがLFであることを期待し、これをすべて取り除く。変数が代入される前に処理するため、入力する値から改行コードが取り除かれることはない。この仕様は要望があれば将来的に変更することはありうるが、強い希望がなければ特に変更されることはないだろう。
念のために断っておくが、このライブラリはSQLの構文について干渉しない。実行結果が妥当なSQL文になることは保障しない。文字列のクオーテーションについてはPDO::quote()の仕組みによって各SQLドライバがいい感じにしてくれるだろう。たぶん。
変数
TetoSQLは「SQL」と名はつくが、実際はただのテンプレートシステムである。SQLの構文の妥当性については一切保障しない。変数名は:name
のように、かならず:
が前置される。また、SQL文の中では:name@string
のように型が明示される。
変数はSQL文中で複数回展開することができる。この仕様は、たとへばMySQLのINSERT 〜 ON DUPLICATE KEY UPDATE
などに利用できる。
型
TetoSQLには型の概念がある。バインドする値は必ず型を記述する必要がある。
-
@int
:-9223372036854775808
から9223372036854775807
の範囲の整数値 -
@string
: 文字列 -
@int[]
: 複数の@int
値 -
@string[]
: 複数の@string
値 -
@ascdesc
:ASC
とDESC
(ORDER BY
の昇順/降順)
int
は、いはゆる符号付きBIGINTと一致する。また、64bit環境におけるPHP_INT_MAX
, PHP_INT_MIN
の範囲とも一致する。
[]
が付いた型表記は,
で区切って展開される。この型表記が期待するのは単純なリストであり、いはゆる連想配列を期待しない。典型的な利用例はWHERE 〜 IN
である。
PDO
TetoSQLはPDOまたは、PDOと同じインターフェイスを持つクラスを受け取る。PDOクラスを独自実装するためにPDOInterface
を用意したが、タイプヒントは指定していないので、PDOと同様の動作をする最低限のメソッドがあれば問題ないだろう。
PDOStatement
クラスについても同様で、PDOStatement
っぽい振舞をするならば動作は期待できる。PDOStatementInterface
も用意したが、特に利用は必要ない(現在のところ)。
実用例
インスパイヤされて掲示板を作りたくなった(1)シリーズとかで使ってます… が、最近全然更新できてなかったのでそろそろ再開するよ ヾ(〃><)ノ゙☆
未来
私、実はO/Rマッパーってしっくりこないんです… って感じはあるので、それなりにメンテナンスとか機能追加とかしてく気がします。キガムイタラネ…