├── .gitignore ├── .travis.yml ├── composer.json ├── phpunit.xml.dist ├── src ├── SimpleDBI.php ├── SimpleDBIException.php ├── SimpleDBIStatement.php └── SimpleDBIStatementIterator.php └── tests ├── SimpleDBITest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.1 6 | 7 | before_script: 8 | - composer install 9 | - mysql -e 'CREATE DATABASE php_simple_dbi_test' 10 | 11 | script: 12 | - phpunit --coverage-text 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ttsuruoka/php-simple-dbi", 3 | "description": "Simple database interface class for PHP PDO", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "ttsuruoka", 8 | "email": "ttsuruoka@php.net", 9 | "role": "maintainer" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": { }, 14 | "require-dev": { 15 | "phpunit/phpunit": "3.7.*" 16 | }, 17 | "autoload": { 18 | "classmap": ["src/"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | 19 | ./ 20 | 21 | ./vendor 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/SimpleDBI.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | class SimpleDBI 10 | { 11 | protected static $instances = array(); 12 | protected $destination; 13 | protected $dsn; 14 | protected $username; 15 | protected $password; 16 | protected $driver_options; 17 | protected $pdo; 18 | protected $st; // SimpleDBIStatement ステートメント 19 | protected $trans_stack = array(); // トランザクションのネストを管理する 20 | protected $is_uncommittable = false; // commit可能な状態かどうか 21 | 22 | protected function __construct($destination, $dsn, $username, $password, $driver_options) 23 | { 24 | $this->destination = $destination; 25 | $this->dsn = $dsn; 26 | $this->username = $username; 27 | $this->password = $password; 28 | $this->driver_options = $driver_options; 29 | 30 | $this->pdo = new PDO($dsn, $username, $password, $driver_options); 31 | 32 | // エラーモードを例外に設定 33 | $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 34 | 35 | // PDOStatement ではなく SimpleDBIStatement を使うように設定 36 | $this->pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('SimpleDBIStatement')); 37 | } 38 | 39 | public function getDestination() 40 | { 41 | return $this->destination; 42 | } 43 | 44 | public function getDSN() 45 | { 46 | return $this->dsn; 47 | } 48 | 49 | public function getUserName() 50 | { 51 | return $this->username; 52 | } 53 | 54 | public function getPassword() 55 | { 56 | return $this->password; 57 | } 58 | 59 | public function getDriverOptions() 60 | { 61 | return $this->driver_options; 62 | } 63 | 64 | public function getPDO() 65 | { 66 | if (!($this->pdo instanceof PDO)) { 67 | throw new SimpleDBIException('Database not connected'); 68 | } 69 | return $this->pdo; 70 | } 71 | 72 | /** 73 | * データベースの接続設定を取得する 74 | * 75 | * このメソッドは、SimpleDBI クラスのサブクラスでオーバーライドして使われます。 76 | * 77 | * @param string $destination 接続先 78 | * @throws InvalidArgumentException 79 | * @return array DSN などの接続設定の配列 80 | */ 81 | public static function getConnectSettings($destination = null) 82 | { 83 | if ($destination === null) { 84 | $dsn = DB_DSN; 85 | $username = DB_USERNAME; 86 | $password = DB_PASSWORD; 87 | $driver_options = array(); 88 | } elseif ($destination === 'slave') { 89 | $dsn = DB_SLAVE_DSN; 90 | $username = DB_SLAVE_USERNAME; 91 | $password = DB_SLAVE_PASSWORD; 92 | $driver_options = array(); 93 | } else { 94 | throw new InvalidArgumentException("Unknown destination: {$destination}"); 95 | } 96 | 97 | return array($dsn, $username, $password, $driver_options); 98 | } 99 | 100 | /** 101 | * データベース接続のインスタンスを取得する 102 | * 103 | * $destination は、接続先をあらわす文字列で、 104 | * 場合によっては データベースのホスト名と一致しないこともあります。 105 | * 106 | * $destination から実際に接続するデータベースの設定を取得するために、 107 | * getConnectSettings() メソッドを呼び出します。 108 | * 109 | * @param string $destination 接続先 110 | * @return SimpleDBI 111 | */ 112 | public static function conn($destination = null) 113 | { 114 | if (isset(static::$instances[$destination])) { 115 | return static::$instances[$destination]; 116 | } 117 | 118 | list($dsn, $username, $password, $driver_options) = static::getConnectSettings($destination); 119 | 120 | static::$instances[$destination] = new static($destination, $dsn, $username, $password, $driver_options); 121 | static::$instances[$destination]->onConnect(); 122 | 123 | return static::$instances[$destination]; 124 | } 125 | 126 | public function disconnect() 127 | { 128 | if (count($this->trans_stack) > 0) { 129 | throw new SimpleDBIException('Cannot disconnect while a transaction is in progress'); 130 | } 131 | unset(static::$instances[$this->destination]); 132 | $this->pdo = null; 133 | } 134 | 135 | /** 136 | * データベースインスタンスに接続完了時に呼ばれるメソッド 137 | * 138 | * このメソッドはオーバーライドして使います。 139 | * 140 | */ 141 | protected function onConnect() 142 | { 143 | } 144 | 145 | /** 146 | * クエリーの実行が完了したときの呼ばれるメソッド 147 | * 148 | * このメソッドはオーバーライドして使います。 149 | * 150 | * 例)実行時間をデバッグ出力 151 | * 152 | * Log::debug($this->st->exec_time); 153 | * 154 | * @param string $sql 実行した SQL 155 | * @param array $params SQL にバインドされたパラメータ 156 | * @return void 157 | */ 158 | protected function onQueryEnd($sql, array $params = array()) 159 | { 160 | } 161 | 162 | protected function setStatementClass($statement_class) 163 | { 164 | $pdo = $this->getPDO(); 165 | $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($statement_class, array($this))); 166 | } 167 | 168 | /** 169 | * 実行する SQL をパースする 170 | * 171 | * PDO と PDOStatement では対応できない機能に対応するために、 172 | * SQL をパースします。 173 | * 174 | * このメソッドで、IN 句の展開に対応しています。 175 | * 176 | * @param string $sql 177 | * @param array $params 178 | * @return array パースされた SQL とパラメータ 179 | */ 180 | public static function parseSQL($sql, array $params = array()) 181 | { 182 | // 183 | // IN 句の展開 184 | // 185 | 186 | // IN 句の展開をする必要があるかどうかを判断する 187 | // パラメータにひとつでも配列が含まれている = IN 句の展開が必要 188 | $has_array_value = false; 189 | $is_named_param = false; // 名前付きパラメータかどうか(:foo 形式) 190 | foreach ($params as $k => $v) { 191 | if (is_array($v)) { 192 | $has_array_value = true; 193 | $is_named_param = !is_numeric($k); 194 | break; 195 | } 196 | } 197 | 198 | // IN 句の展開をする必要があるときだけ、展開処理を行う 199 | // 200 | // IN (?) に対して array(10, 20, 30) が渡されたとき 201 | // IN (?, ?, ?) に展開する 202 | // 203 | // IN (:foo) に対して array(':foo' => array(10, 20, 30)) が渡されたとき 204 | // IN (:foo_0, :foo_1, :foo_2) に展開する 205 | // 206 | if ($has_array_value) { 207 | if ($is_named_param) { 208 | // 209 | // 名前付きパラメータのとき 210 | // 211 | // SQL: 212 | // :foo を :foo_0, :foo_1, ... に展開する 213 | // 214 | // パラメータ: 215 | // array(':foo' => array(10, 20, 30)) を 216 | // array(':foo_0' => 10, ':foo_1' => 20, ...) に展開する 217 | // 218 | 219 | $unset_keys = array(); 220 | 221 | $sql = preg_replace_callback( 222 | '/:([A-Za-z0-9_-]+)/', 223 | function($matches) use (&$params, &$unset_keys) { 224 | 225 | $name = $matches[0]; // :name 形式の文字列 226 | 227 | // パラメータのキーを取得 228 | // 「:」がついているときとついていないとき両方に対応 229 | $key = isset($params[$matches[1]]) ? $matches[1] : $matches[0]; 230 | 231 | $name_i_list = array(); 232 | if (!is_array($params[$key])) { 233 | // パラメータが配列ではないときは、展開しない 234 | return $name; 235 | } 236 | foreach ($params[$key] as $i => $v) { 237 | $name_i = "{$name}_{$i}"; 238 | if (!preg_match('/^:([A-Za-z0-9_-]+)$/', $name_i)) { 239 | throw new SimpleDBIException("Invalid placeholder name: {$name_i}"); 240 | } 241 | $name_i_list[] = $name_i; 242 | $params[$name_i] = $params[$key][$i]; 243 | } 244 | $unset_keys[] = $key; 245 | 246 | return join(', ', $name_i_list); 247 | }, 248 | $sql 249 | ); 250 | 251 | // 展開済みのキーをパラメータから削除 252 | foreach ($unset_keys as $key) { 253 | unset($params[$key]); 254 | } 255 | 256 | } else { 257 | // 位置パラメータのとき 258 | $a = explode('?', $sql); 259 | $sql = array_shift($a); 260 | $t = array(); 261 | foreach ($params as $k => $v) { 262 | if (is_array($v)) { 263 | if ($v === array()) { 264 | $sql .= '?'; 265 | $t[] = null; 266 | } else { 267 | $sql .= join(', ', array_fill(0, count($v), '?')); 268 | $t = array_merge($t, $v); 269 | } 270 | } else { 271 | $sql .= '?'; 272 | $t[] = $v; 273 | } 274 | $sql .= array_shift($a); 275 | } 276 | $params = $t; 277 | } 278 | } 279 | 280 | return array($sql, $params); 281 | } 282 | 283 | public function getLastExecTime() 284 | { 285 | return isset($this->st->exec_time) ? $this->st->exec_time : null; 286 | } 287 | 288 | /** 289 | * SQL を実行する 290 | * 291 | * @param string $sql 292 | * @param array $params 293 | * @throws SimpleDBIException 294 | */ 295 | public function query($sql, array $params = array()) 296 | { 297 | $pdo = $this->getPDO(); 298 | list($sql, $params) = self::parseSQL($sql, $params); 299 | $this->st = $pdo->prepare($sql); 300 | $r = $this->st->execute($params); 301 | if (!$r) { 302 | throw new SimpleDBIException("query failed: {$sql}"); 303 | } 304 | $this->onQueryEnd($this->st->queryString, $params); 305 | } 306 | 307 | /** 308 | * SQL を実行して、結果から最初の1行を取得する 309 | * 310 | * @param string $sql 311 | * @param array $params 312 | * @return array|boolean 結果セットから最初の1行を配列で返す。結果が見つからなかったとき false を返す 313 | */ 314 | public function row($sql, array $params = array()) 315 | { 316 | $rows = $this->rows($sql, $params); 317 | 318 | return $rows ? $rows[0] : false; 319 | } 320 | 321 | /** 322 | * SQL を実行して、結果からすべての行を取得する 323 | * 324 | * @param string $sql 325 | * @param array $params 326 | * @return array 結果セットからすべての行を配列で返す。結果が見つからなかったとき空配列を返す 327 | */ 328 | public function rows($sql, array $params = array()) 329 | { 330 | $this->query($sql, $params); 331 | $rows = $this->st->fetchAll(PDO::FETCH_ASSOC); 332 | 333 | return $rows ? $rows : array(); 334 | } 335 | 336 | /** 337 | * SQL を実行して、結果から最初の1行の最初の値を取得する 338 | * 339 | * @param string $sql 340 | * @param array $params 341 | * @return mixed 結果セットの最初の1行の最初の値を返す。結果が見つからなかったとき false を返す 342 | */ 343 | public function value($sql, array $params = array()) 344 | { 345 | $row = $this->row($sql, $params); 346 | 347 | return $row ? current($row) : false; 348 | } 349 | 350 | /** 351 | * SQL を実行して、結果から指定した列のみを含むすべての行を取得する 352 | * 353 | * @param $sql 354 | * @param array $params 355 | * @param int $column_number 356 | * @return array 357 | */ 358 | public function columns($sql, array $params = array(), $column_number = 0) 359 | { 360 | $this->query($sql, $params); 361 | $rows = $this->st->fetchAll(PDO::FETCH_COLUMN, $column_number); 362 | return $rows ? $rows : array(); 363 | } 364 | 365 | /** 366 | * 単純な INSERT 文を実行する 367 | * 368 | * @param string $table 369 | * @param array $params 370 | */ 371 | public function insert($table, array $params) 372 | { 373 | $cols = implode(', ', array_keys($params)); 374 | $placeholders = implode(', ', str_split(str_repeat('?', count($params)))); 375 | $sql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $table, $cols, $placeholders); 376 | $this->query($sql, array_values($params)); 377 | } 378 | 379 | /** 380 | * 単純な REPLACE 文を実行する 381 | * 382 | * @param string $table 383 | * @param array $params 384 | */ 385 | public function replace($table, array $params) 386 | { 387 | $cols = implode(', ', array_keys($params)); 388 | $placeholders = implode(', ', str_split(str_repeat('?', count($params)))); 389 | $sql = sprintf('REPLACE INTO %s (%s) VALUES (%s)', $table, $cols, $placeholders); 390 | $this->query($sql, array_values($params)); 391 | } 392 | 393 | /** 394 | * 単純な UPDATE 文を実行する 395 | * 396 | * @param string $table 397 | * @param array $params 398 | * @param array $where_params 399 | */ 400 | public function update($table, array $params, array $where_params) 401 | { 402 | // 対象のカラム名と値 403 | $pairs = ''; 404 | foreach ($params as $k => $v) { 405 | $pairs .= $k . ' = ?, '; 406 | } 407 | $pairs = substr($pairs, 0, -2); 408 | 409 | // WHERE 句 410 | $where = ''; 411 | foreach ($where_params as $k => $v) { 412 | $where .= $k . ' = ? AND '; 413 | } 414 | $where = substr($where, 0, -5); 415 | 416 | $sql = sprintf('UPDATE %s SET %s WHERE %s', $table, $pairs, $where); 417 | $this->query($sql, array_merge(array_values($params), array_values($where_params))); 418 | } 419 | 420 | /** 421 | * 単純な検索クエリーを実行する @DEPRECATED 422 | * 423 | * 使用例: 424 | * $this->search('item', 'id BETWEEN ? AND ?', array(1000, 1999), 'id DESC', array(1, 10)); 425 | * 426 | * @param string $table 対象のテーブル名 427 | * @param string $where WHERE 句 428 | * @param array $params 束縛するパラメータ 429 | * @param string $order ORDER 句(オプション) 430 | * @param string $limit LIMIT 句(オプション) 431 | * @param array $options その他のオプション 432 | * select_expr キー:SELECT で取り出すカラム。デフォルトは * (全カラム) 433 | * @return array 取得結果を配列で返す。結果が見つからなかったとき空配列を返す 434 | */ 435 | public function search($table, $where, $params, $order = null, $limit = null, $options = array()) 436 | { 437 | // SELECT で取り出すカラムを指定 438 | $select_expr = '*'; 439 | if (isset($options['select_expr'])) { 440 | $select_expr = $options['select_expr']; 441 | } 442 | 443 | // SQL 文の組み立て 444 | $sql = sprintf('SELECT %s FROM %s WHERE %s', $select_expr, $table, $where); 445 | 446 | // ORDER 句 447 | if (!is_null($order)) { 448 | $sql .= sprintf(' ORDER BY %s', $order); 449 | } 450 | 451 | // LIMIT 句 452 | if (!is_null($limit)) { 453 | if (is_array($limit)) { 454 | $limit = implode(', ', $limit); 455 | } 456 | $sql .= sprintf(' LIMIT %s', $limit); 457 | } 458 | 459 | return $this->rows($sql, $params); 460 | } 461 | 462 | /** 463 | * トランザクションを開始する 464 | * 465 | * ネストランザクションに対応しています。 466 | * 467 | */ 468 | public function begin() 469 | { 470 | if (count($this->trans_stack) == 0) { 471 | $this->getPDO()->beginTransaction(); 472 | $this->onQueryEnd('BEGIN'); 473 | $this->is_uncommittable = false; 474 | } 475 | array_push($this->trans_stack, 'A'); 476 | } 477 | 478 | /** 479 | * トランザクションをコミットする 480 | * 481 | * @throws SimpleDBIException 482 | */ 483 | public function commit() 484 | { 485 | if (count($this->trans_stack) <= 1) { 486 | if ($this->is_uncommittable) { 487 | throw new SimpleDBIException('Cannot commit because a nested transaction was rolled back'); 488 | } else { 489 | $this->getPDO()->commit(); 490 | $this->onQueryEnd('COMMIT'); 491 | } 492 | } 493 | array_pop($this->trans_stack); 494 | } 495 | 496 | /** 497 | * トランザクションをロールバックする 498 | * 499 | */ 500 | public function rollback() 501 | { 502 | if (count($this->trans_stack) <= 1) { 503 | $this->getPDO()->rollBack(); 504 | $this->onQueryEnd('ROLLBACK'); 505 | } else { 506 | $this->is_uncommittable = true; 507 | } 508 | array_pop($this->trans_stack); 509 | } 510 | 511 | /** 512 | * 最後に INSERT した行の ID を取得する 513 | * 514 | * @param $name 515 | * @return string 最後に INSERT した行の ID 516 | */ 517 | public function lastInsertId($name = null) 518 | { 519 | return $this->getPDO()->lastInsertId($name); 520 | } 521 | 522 | /** 523 | * 直近の SQL ステートメントで作用した行数を取得する 524 | * 525 | * @return int 526 | */ 527 | public function rowCount() 528 | { 529 | return $this->st->rowCount(); 530 | } 531 | 532 | /** 533 | * Queryを実行してIteraterを取得する 534 | * 535 | * @param string $sql 536 | * @param array $params 537 | * @return StatementIterator 538 | * @throws SimpleDBIException 539 | */ 540 | public function iterator($sql, array $params = array()) 541 | { 542 | $this->query($sql, $params); 543 | 544 | return new SimpleDBIStatementIterator($this->st); 545 | } 546 | 547 | /** 548 | * @param \Closure $func The function to execute transactionally. 549 | * @return mixed The value returned by $func 550 | * @throws \Exception 551 | */ 552 | public function transactional(Closure $func) 553 | { 554 | $this->begin(); 555 | try { 556 | $res = $func($this); 557 | $this->commit(); 558 | return $res; 559 | } catch (Exception $e) { 560 | $this->rollback(); 561 | throw $e; 562 | } 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /src/SimpleDBIException.php: -------------------------------------------------------------------------------- 1 | exec_time = microtime(true) - $ts; 15 | 16 | return $r; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SimpleDBIStatementIterator.php: -------------------------------------------------------------------------------- 1 | stmt = $stmt; 17 | } 18 | 19 | public function rewind() 20 | { 21 | if ($this->rewinded == true) { 22 | throw new SimpleDBIException("Can only iterate a Result once."); 23 | } else { 24 | $this->rewinded = true; 25 | $this->current = false; 26 | $this->cursor = null; 27 | $this->next(); 28 | } 29 | } 30 | 31 | public function valid() 32 | { 33 | return (false !== $this->current); 34 | } 35 | 36 | public function current() 37 | { 38 | return $this->current; 39 | } 40 | 41 | public function key() 42 | { 43 | return $this->cursor; 44 | } 45 | 46 | public function next() 47 | { 48 | $row = $this->stmt->fetch(\PDO::FETCH_ASSOC); 49 | if ($row) { 50 | if (null === $this->cursor) { 51 | $this->cursor = 0; 52 | } else { 53 | $this->cursor++; 54 | } 55 | $this->current = $row; 56 | } else { 57 | $this->cursor = null; 58 | $this->current = false; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/SimpleDBITest.php: -------------------------------------------------------------------------------- 1 | assertEquals(DB_DSN, $dsn); 27 | $this->assertEquals(DB_USERNAME, $username); 28 | $this->assertEquals(DB_PASSWORD, $password); 29 | $this->assertEquals(array(), $driver_options); 30 | } 31 | 32 | public function test_conn() 33 | { 34 | $db = SimpleDBI::conn(); 35 | $this->assertInstanceOf('SimpleDBI', $db); 36 | $this->assertEquals(null, $db->getDestination()); 37 | $this->assertEquals(DB_DSN, $db->getDSN()); 38 | $this->assertEquals(DB_USERNAME, $db->getUserName()); 39 | $this->assertEquals(DB_PASSWORD, $db->getPassword()); 40 | $this->assertEquals(array(), $db->getDriverOptions()); 41 | } 42 | 43 | public function test_iterator() 44 | { 45 | $db = SimpleDBI::conn(); 46 | $iterator = $db->iterator('SELECT 1'); 47 | 48 | $this->assertInstanceOf('SimpleDBIStatementIterator', $iterator); 49 | $count = 0; 50 | foreach ($iterator as $row) { 51 | $this->assertEquals($row, array(1 => '1')); 52 | $count++; 53 | } 54 | $this->assertEquals(1, $count); 55 | } 56 | 57 | public function test_iterator_statement_intaface() 58 | { 59 | $db = SimpleDBI::conn(); 60 | $iterator = $db->iterator('SELECT 1'); 61 | 62 | $this->assertInstanceOf('SimpleDBIStatementIterator', $iterator); 63 | 64 | $this->assertFalse($iterator->valid()); 65 | $this->assertFalse($iterator->current()); 66 | $this->assertNull($iterator->key()); 67 | 68 | $iterator->rewind(); 69 | 70 | $this->assertTrue($iterator->valid()); 71 | $this->assertEquals($iterator->current(), array(1 => '1')); 72 | $this->assertEquals($iterator->key(), 0); 73 | 74 | $iterator = $db->iterator('SELECT 1'); 75 | 76 | $this->assertInstanceOf('SimpleDBIStatementIterator', $iterator); 77 | 78 | $this->assertFalse($iterator->valid()); 79 | $this->assertFalse($iterator->current()); 80 | $this->assertNull($iterator->key()); 81 | 82 | $iterator->next(); 83 | 84 | $this->assertTrue($iterator->valid()); 85 | $this->assertEquals($iterator->current(), array(1 => '1')); 86 | $this->assertEquals($iterator->key(), 0); 87 | 88 | $iterator->next(); 89 | 90 | $this->assertFalse($iterator->valid()); 91 | $this->assertFalse($iterator->current()); 92 | $this->assertNull($iterator->key()); 93 | 94 | $iterator = $db->iterator('SHOW STATUS'); 95 | 96 | $this->assertInstanceOf('SimpleDBIStatementIterator', $iterator); 97 | 98 | $this->assertFalse($iterator->valid()); 99 | $this->assertFalse($iterator->current()); 100 | $this->assertNull($iterator->key()); 101 | 102 | $iterator->next(); 103 | $iterator->next(); 104 | 105 | $this->assertTrue($iterator->valid()); 106 | } 107 | 108 | /** 109 | * @covers SimpleDBIStatementIterator::__construct 110 | */ 111 | public function test_iterator_constructor() 112 | { 113 | $statement = $this->getMockBuilder('PDOStatement')->getMock(); 114 | $iterator = new SimpleDBIStatementIterator($statement); 115 | $this->assertInstanceOf('SimpleDBIStatementIterator', $iterator); 116 | } 117 | 118 | public function test_iterator_no_match() 119 | { 120 | $db = SimpleDBI::conn(); 121 | 122 | $iterator = $db->iterator('SHOW STATUS LIKE "SIMPLE DBI TEST"'); 123 | 124 | $this->assertInstanceOf('SimpleDBIStatementIterator', $iterator); 125 | $this->assertFalse($iterator->valid()); 126 | $this->assertFalse($iterator->current()); 127 | $this->assertNull($iterator->key()); 128 | 129 | $iterator->rewind(); 130 | 131 | $this->assertFalse($iterator->valid()); 132 | $this->assertFalse($iterator->current()); 133 | $this->assertNull($iterator->key()); 134 | } 135 | 136 | public function test_iterator_twice_rewind() 137 | { 138 | $this->setExpectedException('SimpleDBIException', 'Can only iterate a Result once.'); 139 | 140 | $db = SimpleDBI::conn(); 141 | $iterator = $db->iterator('SELECT 1'); 142 | 143 | $iterator->rewind(); 144 | $iterator->rewind(); 145 | } 146 | 147 | /** 148 | * disconnect によって DB 接続を切断できることをテストする 149 | */ 150 | public function test_disconnect() 151 | { 152 | $db = SimpleDBI::conn(); 153 | 154 | $value = $db->value('SELECT 1'); 155 | $this->assertEquals(1, $value); 156 | 157 | $db->disconnect(); 158 | 159 | try { 160 | $db->value('SELECT 1'); 161 | $this->fail(); 162 | } catch (SimpleDBIException $e) { 163 | $this->assertEquals('Database not connected', $e->getMessage()); 164 | } 165 | 166 | try { 167 | $db->begin(); 168 | $this->fail(); 169 | } catch (SimpleDBIException $e) { 170 | $this->assertEquals('Database not connected', $e->getMessage()); 171 | } 172 | 173 | try { 174 | $db->lastInsertId(); 175 | $this->fail(); 176 | } catch (SimpleDBIException $e) { 177 | $this->assertEquals('Database not connected', $e->getMessage()); 178 | } 179 | } 180 | 181 | /** 182 | * トランザクション中は disconnect できないことをテストする 183 | */ 184 | public function test_disconnect_02() 185 | { 186 | $db = SimpleDBI::conn(); 187 | 188 | $db->begin(); 189 | 190 | try { 191 | $db->disconnect(); 192 | $this->fail(); 193 | } catch (SimpleDBIException $e) { 194 | $this->assertEquals('Cannot disconnect while a transaction is in progress', $e->getMessage()); 195 | } 196 | 197 | try { 198 | $db->rollback(); 199 | $db->disconnect(); 200 | } catch (SimpleDBIException $e) { 201 | $this->fail(); 202 | } 203 | } 204 | 205 | /** 206 | * disconnect によって接続が切断されることをテストする 207 | */ 208 | public function test_disconnect_03() 209 | { 210 | $db = SimpleDBI::conn(); 211 | 212 | $row = $db->row('SHOW STATUS LIKE "Threads_connected"'); 213 | $connected_a = $row['Value']; 214 | 215 | $this->assertGreaterThan(0, $connected_a); 216 | 217 | $db2 = SimpleDBI::conn('slave'); 218 | 219 | $row = $db->row('SHOW STATUS LIKE "Threads_connected"'); 220 | $connected_b = $row['Value']; 221 | 222 | $this->assertEquals($connected_a + 1, $connected_b); 223 | 224 | $db2->disconnect(); 225 | 226 | $retry = 3; 227 | $disconnected = false; 228 | for ($i = 0; $i <= $retry; $i++) { 229 | $row = $db->row('SHOW STATUS LIKE "Threads_connected"'); 230 | $connected_c = $row['Value']; 231 | if ($connected_a == $connected_c) { 232 | $disconnected = true; 233 | break; 234 | } 235 | // すぐに接続数に反映されないことがあるので少し待つ 236 | sleep(1); 237 | } 238 | 239 | $this->assertTrue($disconnected); 240 | } 241 | 242 | /** 243 | * conn/disconnect を繰り返せることをテストする 244 | */ 245 | public function test_disconnect_04() 246 | { 247 | $db = SimpleDBI::conn(); 248 | $value = $db->value('SELECT 1'); 249 | $this->assertEquals(1, $value); 250 | $db->disconnect(); 251 | 252 | $db = SimpleDBI::conn(); 253 | $value = $db->value('SELECT 1'); 254 | $this->assertEquals(1, $value); 255 | $db->disconnect(); 256 | } 257 | 258 | public function test_parseSQL() 259 | { 260 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test'); 261 | $this->assertEquals('SELECT * FROM test', $sql); 262 | $this->assertEquals(array(), $params); 263 | 264 | // 通常のクエリーの展開: 位置パラメータひとつ 265 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id = ?', array(10)); 266 | $this->assertEquals('SELECT * FROM test WHERE id = ?', $sql); 267 | $this->assertEquals(array(10), $params); 268 | 269 | // 通常のクエリーの展開: 名前付きパラメータひとつ 270 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id = :id', array('id' => 10)); 271 | $this->assertEquals('SELECT * FROM test WHERE id = :id', $sql); 272 | $this->assertEquals(array('id' => 10), $params); 273 | 274 | // IN 句の展開: ? がひとつだけのとき 275 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (?)', array(array(10, 20, 30))); 276 | $this->assertEquals('SELECT * FROM test WHERE id IN (?, ?, ?)', $sql); 277 | $this->assertEquals(array(10, 20, 30), $params); 278 | 279 | // IN 句の展開: ? がひとつだけのとき 280 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (?)', array(array(10))); 281 | $this->assertEquals('SELECT * FROM test WHERE id IN (?)', $sql); 282 | $this->assertEquals(array(10), $params); 283 | 284 | // IN 句の展開: ? が複数のとき 285 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (?) AND age > ?', array(array(10, 20, 30), 50)); 286 | $this->assertEquals('SELECT * FROM test WHERE id IN (?, ?, ?) AND age > ?', $sql); 287 | $this->assertEquals(array(10, 20, 30, 50), $params); 288 | 289 | // IN 句の展開: ? に空の配列を割り当てようとしているとき 290 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (?)', array(array())); 291 | $this->assertEquals('SELECT * FROM test WHERE id IN (?)', $sql); 292 | $this->assertEquals(array(null), $params); 293 | 294 | // IN 句の展開: IN 句が複数のとき 295 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (?) AND age IN (?)', array(array(10, 20, 30), array(50, 60))); 296 | $this->assertEquals('SELECT * FROM test WHERE id IN (?, ?, ?) AND age IN (?, ?)', $sql); 297 | $this->assertEquals(array(10, 20, 30, 50, 60), $params); 298 | 299 | // IN 句の展開: named パラメータのとき 300 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo)', array(':foo' => array(10, 20, 30))); 301 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_0, :foo_1, :foo_2)', $sql); 302 | $this->assertEquals(array(':foo_0' => 10, ':foo_1' => 20, ':foo_2' => 30), $params); 303 | 304 | // IN 句の展開: named パラメータのとき 305 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo)', array(':foo' => array(10))); 306 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_0)', $sql); 307 | $this->assertEquals(array(':foo_0' => 10), $params); 308 | 309 | // IN 句の展開: named パラメータのとき + パラメータが複数 310 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo) AND age > :age', array(':foo' => array(10, 20, 30), ':age' => 50)); 311 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_0, :foo_1, :foo_2) AND age > :age', $sql); 312 | $this->assertEquals(array(':foo_0' => 10, ':foo_1' => 20, ':foo_2' => 30, ':age' => 50), $params); 313 | 314 | // IN 句の展開: named パラメータのとき + IN 句が複数 315 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo) AND age IN (:age)', array(':foo' => array(10, 20, 30), ':age' => array(50, 60))); 316 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_0, :foo_1, :foo_2) AND age IN (:age_0, :age_1)', $sql); 317 | $this->assertEquals(array(':foo_0' => 10, ':foo_1' => 20, ':foo_2' => 30, ':age_0' => 50, ':age_1' => 60), $params); 318 | 319 | // IN 句の展開: named パラメータのとき + 同名の IN 句が複数 320 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo) UNION SELECT * FROM test2 WHERE id IN (:foo)', array(':foo' => array(10, 20, 30))); 321 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_0, :foo_1, :foo_2) UNION SELECT * FROM test2 WHERE id IN (:foo_0, :foo_1, :foo_2)', $sql); 322 | $this->assertEquals(array(':foo_0' => 10, ':foo_1' => 20, ':foo_2' => 30), $params); 323 | 324 | // IN 句の展開: named パラメータのとき + パラメータでコロンがついていないとき 325 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo)', array('foo' => array(10, 20, 30))); 326 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_0, :foo_1, :foo_2)', $sql); 327 | $this->assertEquals(array(':foo_0' => 10, ':foo_1' => 20, ':foo_2' => 30), $params); 328 | 329 | // IN 句の展開: named パラメータのとき + 数値添字配列 330 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo)', array('foo' => array(1 => 10, 4 => 40, 2 => 20))); 331 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_1, :foo_4, :foo_2)', $sql); 332 | $this->assertEquals(array(':foo_1' => 10, ':foo_4' => 40, ':foo_2' => 20), $params); 333 | 334 | // IN 句の展開: named パラメータのとき + 文字添字配列 335 | list($sql, $params) = SimpleDBI::parseSQL('SELECT * FROM test WHERE id IN (:foo)', array('foo' => array('bar' => 10, 'baz' => 20, 'qux' => 40))); 336 | $this->assertEquals('SELECT * FROM test WHERE id IN (:foo_bar, :foo_baz, :foo_qux)', $sql); 337 | $this->assertEquals(array(':foo_bar' => 10, ':foo_baz' => 20, ':foo_qux' => 40), $params); 338 | } 339 | 340 | public function test_parseSQL_02() 341 | { 342 | // 空白を含む named パラメータは展開しない 343 | try { 344 | SimpleDBI::parseSQL('SELECT * FROM test WHERE id = :foo', array('foo' => array('foo bar' => 10))); 345 | $this->fail(); 346 | } catch (SimpleDBIException $e) { 347 | $this->assertEquals("Invalid placeholder name: :foo_foo bar", $e->getMessage()); 348 | } 349 | 350 | // アルファベット以外の文字(セミコロン)を含む named パラメータは展開しない 351 | try { 352 | SimpleDBI::parseSQL('SELECT * FROM test WHERE id = :foo', array('foo' => array('foo;bar' => 10))); 353 | $this->fail(); 354 | } catch (SimpleDBIException $e) { 355 | $this->assertEquals("Invalid placeholder name: :foo_foo;bar", $e->getMessage()); 356 | } 357 | } 358 | 359 | public function test_parseSQL_03() 360 | { 361 | $db = SimpleDBI::conn(); 362 | $sql = 'SELECT 1 LIMIT :limit OFFSET :offset'; 363 | $pdo = $db->getPDO(); 364 | $stmt = $pdo->prepare($sql); 365 | $stmt->bindValue(':limit', 2, \PDO::PARAM_INT); 366 | $stmt->bindValue(':offset', 3, \PDO::PARAM_INT); 367 | $res = $stmt->execute(); 368 | $this->assertTrue($res); 369 | } 370 | 371 | public function test_transactional() 372 | { 373 | $value = SimpleDBI::conn()->transactional(function(SimpleDBI $db) { 374 | return $db->value('SELECT 1'); 375 | }); 376 | $this->assertEquals(1, $value); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |