憂鬱なSQLのためのアレ、またはPDOと仲良くして枕を高くしてねむる

人間は皆誰しも実行時にSQLを組み立ててデータを取り出すことが大好きだ。そしてその行為の危険さもわからぬまま人類は太陽に向かってゆく。

あとこの記事で紹介してるライブラリは私が書いたコードじゃないです ヾ(〃＞＜)ﾉﾞ

## はじめに

まあ落ち着いて、去年のPHPカンファレンスの徳丸先生[^1]のスライドを読んでほしい。ちなみに私は去年のPHPカンファレンスは風邪でダウンしてたので行けなかった。QiitaにはSlideShareの埋め込みができないので、↓の枠はただの画像リンクだ。

[![今どきのSQLインジェクションの話題総まとめ](https://qiita-image-store.s3.amazonaws.com/0/4121/5026c3b3-73ae-9835-6a60-f0456e316d57.png)](//www.slideshare.net/ockeghem/phpcon2015)
<strong> <a href="" title="Phpcon2015" target="_blank">Phpcon2015</a> </strong> from <strong><a href="//www.slideshare.net/ockeghem" target="_blank">Hiroshi Tokumaru</a></strong> </div>

「うちはO/Rマッパーを使ってるよ」ってひとには、この記事はかんけいない。そう、人類はDoctrineのような叡智を手にしてる。素直に素晴らしいMVCモデルやDBレイヤーを持ったフレームワークを選ぶべきだ。私は使ったことがないので無責任な発言はできないが、Laravelのクエリビルダーもなかなか使ひ勝手は良さそうに見える。

* [データベースと Doctrine (“The Model”) | Symfony2日本語ドキュメント](http://docs.symfony.gr.jp/symfony2/book/doctrine.html)
* [Database: Query Builder - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/5.1/queries)

## PHPとDB/SQL

それでも人類は、自分の手でSQLを書きたがる。[PDO](http://php.net/manual/en/book.pdo.php)を使って。そんなときは必ず以下の記事の内容を頭に叩き込まなければならない。

* [PHPでデータベースに接続するときのまとめ - Qiita](http://qiita.com/mpyw/items/b00b72c5c95aac573b71) by @mpyw

世の中には「フレームワークとかよくわからないし、シンプルなPHPで…」とおっしゃる初心者の型も居るが、そんなひとには「急がば回れ」との名せりふを贈りたい。たぶん有名なフレームワークに乗った方が安全だよ[^2]。

あ、PDO(PHP Data Objects)を使ってないひとは、これ以上この記事を読む意味はあんまりないです。

-----

### PDOのいいところ

文字列とかをいい感じにエスケープしてくれるところ。

```php
$stmt->bindValue(':user', $user, \PDO::PARAM_STR);
```

### PDOのだめなところ

可変個のパラメータに弱すぎる… つまり、IN修飾子。

```sql
SELECT * FROM `users` WHERE `id` IN (123, 343, 893, 114, 514)
```

この形式をPDOでプレースホルダに埋め込む方法は当然あるのだけれど、ここでは紹介しない。

## TetoSQL

* [BaguettePHP/TetoSQL](https://github.com/BaguettePHP/TetoSQL)
* [zonuexe/tetosql - Packagist](https://packagist.org/packages/zonuexe/tetosql)

Packagistに登録済みなので、Composer導入済みの環境で`composer require zonuexe/tetosql`を実行すれば導入可能。

このライブラリはSQLにバインドする値の実行時型検査を行った上でPDOの機能をラップするあたりが特徴で、SQLインジェクション脆弱性に一定の効果はあるだろう。配列をサポートすることでIN修飾子も問題なく利用できる。

御託はいいので利用例を見せる。

```php
<?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()](http://php.net/manual/pdo.quote.php)の仕組みによって各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)](http://qiita.com/tadsan/items/cdbbb5b08591af2b110d)シリーズとかで使ってます… が、最近全然更新できてなかったのでそろそろ再開するよ ヾ(〃＞＜)ﾉﾞ☆

## 未来

私、実は[O/Rマッパー](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%96%A2%E4%BF%82%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0)ってしっくりこないんです… って感じはあるので、それなりにメンテナンスとか機能追加とかしてく気がします。ｷｶﾞﾑｲﾀﾗﾈ…

## 脚注

[^1]: 「徳丸本」の作者として有名な型です
[^2]: 私は「既存のWAFってしっくりこないんです…」と言って設計を始めてしまったけど