1. Qiita
  2. Items
  3. PHP

PHPでデータベースに接続するときのまとめ

  • 1473
    Stock
  • 24
    Comment
Stocked
  • mAster_rAdio
  • yucchiy
  • yuku_t
  • nnabeyang
  • petitviolet
  • pogin503
  • WataruGodo
  • ukyo
  • okonomi
  • horitaku1124

初心者がやりがちなミス

この章はデータベースをPHPから扱ったことのある経験者を対象とします。全く触ったことのない方からすれば何を言っているかサッパリ分からないことばかりであると思われますが、そういった方にも軽く流し読み程度でもいいので読んでいただきたいと思います。

Mysql関数を利用している

mysql_connectmysql_query などの関数のことを指す。

マニュアルが公式に非推奨としている。
ss (2013-10-20 at 10.28.28).png

今から新しく書くコードでは理由を問わず使ってはならない。既存のコードも他のものに乗り換えるようにメンテナンスすべきである。

文字セット指定の方法を誤っている

文字セット指定すらしていない場合は 論外 。日本人ならマルチバイト文字を想定して当然。

誤った指定方法

  • SET NAMESSET CHARACTER SET を用いてはいけない。 これらはデータベース側の文字セットを操作するだけで、 ドライバ側の文字セットにはノータッチだからである。
  • 文字コードの性質上、 SET NAMES sjisSET CHARACTER SET sjis とした場合、顕著に脆弱性が現れる。
  • SET NAMES utf8SET CHARACTER SET utf8 は基本的には安全だが、例外もあるので注意。 MySQLにおいては、libmysqlclientのコンパイルオプションに --with-charset=cp932--with-charset=sjis を指定している場合が該当。

へぼい日記 - libmysqlclientを使うプログラムはset namesをutf8であっても使ってはいけない

正しい指定方法

PDOクラス

MySQL以外では下記のようにする。

  • PostgreSQL では、DSNに options='--client_encoding=UTF8'" を含める。
  • SQLite では特に指定する必要が無く、暗黙的にUTF-8が使用される。

Mysqliクラス(またはMysqli関数)

Mysql関数

ユーザー入力からクエリを組み立てる方法を誤っている

悪い例

$hoge = $_POST['hoge'];
$sql = "SELECT * FROM `mytable` WHERE `hoge` = '$hoge'";

Wikipedia - SQLインジェクション

これだと SQLインジェクション攻撃 の格好の標的になってしまうどころか、それを意図せずとも ' を含めたクエリで検索を行おうとしたときにも正常な処理が行えなくなってしまう。これを防ぐためにエスケープ処理が必要になる。

手動エスケープによる対処

Mysql関数では次に紹介するプリペアドステートメントがサポートされていないため、自前で下記の処理を行う必要があった。

  • 整数型カラムの場合は (int)sprintf 関数の "%d" 指定で確実に整数にキャストする。
  • 文字列型カラムの場合は mysql_real_escape_string 関数を用いてエスケープした後、自前で両側をシングルクオートで囲む必要がある

以下のような誤ったコーディングをしないように注意していただきたい。

  • htmlspecialchars 関数は 「HTMLの特殊文字」 をエスケープする関数であり、 「SQLの特殊文字」 をエスケープする関数ではない。全く目的が異なる上に、これを通してしまうとHTML特殊文字をデータベースにそのまま格納できないというバグも発生してしまう。
  • addslashes 関数は一応それっぽくSQL文に対してエスケープが行えるが、本当にそれがデータベースに合わせて正しくエスケープされているかどうかの保証は無い。マニュアルにもそういった注意書きがある。
  • 取り出してきたデータに対して自分で stripslashes 関数を通す必要はない。あくまでこの場合でのエスケープ処理はSQL文の中に記述するために行っているだけであり、データベースに格納される内容に対してまで加工を施しているわけではない。

プレースホルダを用いての対処

ひな形となる文をあからじめ プリペアドステートメント (直訳:「予約文」)として準備し、値を後から当てはめる部分を プレースホルダ (直訳:「場所取り」)として確保しておく手法である。プレースホルダに実際に値を割り当てる際のエスケープ処理は自動的に行われるため、ユーザー入力に対しては全てプレースホルダを使用するようにすれば、プログラマがエスケープをし忘れて脆弱性を作りこんでしまう可能性はゼロに出来る。

  • PDOクラスは PDO::prepare メソッドを用いて PDOStatement オブジェクトを生成する。
  • Mysqliクラス(またはMysqli関数)は Mysqli::prepare メソッド(またはmysqli_prepare関数)を用いて mysqli_stmt オブジェクトを生成する。
  • プレースホルダは多くの場合では ? として置かれる。
  • プレースホルダに値を割り当てる (バインドする) とき、自動的に適切なエスケープ処理が行われる。文字列としてバインドする際はエスケープされた後、自動的に両側がシングルクオートで囲まれる
Mysql関数での例
$sql = sprintf(
    "SELECT * FROM people WHERE pref = '%s' AND age = %d",
    mysql_real_escape_string($pref, $link),
    $age
);
PDOクラスでの例
$stmt = $pdo->prepare("SELECT * FROM people WHERE pref = ? AND age = ?");
$stmt->bindValue(1, $pref, PDO::PARAM_STR);
$stmt->bindValue(2, $age, PDO::PARAM_INT);
Mysqliクラスでの例
$stmt = $mysqli->prepare("SELECT * FROM people WHERE pref = ? AND age = ?");
$stmt->bind_param('si', $pref, $gender);
  • Mysqliクラス(またはMysqli関数)は直接MySQLドライバを操作する。一方、PDOクラスは直接ドライバを操作することも出来るし、プリペアドステートメントがサポートされていないデータベースを利用する際にも エミュレーション という機能を利用することによって、疑似的にそれを利用できるようになっている。
  • PHP5.2以降ではPDOクラスのエミュレーション機能がデフォルトで有効。
  • SQLサーバーと通信する必要が無くなるため、エミュレーションを行ったほうがパフォーマンスは向上する。
  • 存在しないテーブル名やカラム名をSQL文に持つプリペアドステートメントを発行したとき、エミュレーションOFFの場合はすぐにエラーが発生するが、エミュレーションONの場合は実際にクエリを実行するまでエラーが発生しない。
  • エミュレーションON/OFFのどちらを選択すべきかについてはこの後で順次比較をしていくので、それを参考にして決定して頂きたい。
PDOクラスのエミュレーションを無効にする
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

ユーザー入力が定義されているか・型が適切かどうかを判定していない

さっきの「悪い例」に関して、

$hoge = $_POST['hoge'];

としているところは

$hoge = (string)filter_input(INPUT_POST, 'hoge');

とすべき。詳細は以下を参照。

Qiita - $_GET, $_POSTなどを受け取る際の処理

LIKE検索用のエスケープを行っていない

LIKE検索で使われる特殊文字に

  • % … 任意の0文字以上の文字列
  • _ … 任意の1文字

があるため、これらの文字を普通に検索したい場合にはエスケープが必要となる。MySQLの場合はエスケープ用の文字を省略して \ にすることが出来るが、SQLiteなど他のデータベースとの互換性を考慮する場合、エスケープに使用する文字を明示すべきである。例えば ! を用いる場合は ESCAPE '!' とする。なお、エスケープ用の文字が文字列中に含まれているケースにも対応するために、そのエスケープ用の文字自体のエスケープも必要である。

悪い例

PDOクラスでの例
$stmt = $pdo->prepare("SELECT * FROM people WHERE name LIKE ?");
$stmt->bindValue(1, "%{$name}%", PDO::PARAM_STR);
Mysql関数での例
$sql = sprintf(
    "SELECT * FROM people WHERE name LIKE '%s'", 
    mysql_real_escape_string("%{$name}%", $link)
);

良い例

PDOクラスでの例(どんなデータベースにも広く使える方法)
$stmt = $pdo->prepare("SELECT * FROM people WHERE name LIKE ? ESCAPE '!'");
$stmt->bindValue(1, '%' . preg_replace('/(?=[!_%])/', '!', $name) . '%', PDO::PARAM_STR);
PDOクラスでの例(MySQL専用の方法)
$stmt = $pdo->prepare('SELECT * FROM people WHERE name LIKE ?');
$stmt->bindValue(1, '%' . addcslashes($name, '\_%') . '%', PDO::PARAM_STR);
Mysql関数での例
$sql = sprintf(
    "SELECT * FROM people WHERE name LIKE '%s'", 
    mysql_real_escape_string('%' . addcslashes($name, '\_%') . '%', $link)
);

exitdie で強制終了処理を記述している

or exit() or die() というエラーへの対応のしかたがあるが、これには大きな欠点がある。HTMLレンダリングを途中まで行ってしまっている場合、例えばMysql関数の例であれば…

Mysql関数での例
<!DOCTYPE html>
<html>
<body>
<ul>
<?php
$link = mysql_connect('localhost', 'root', '') or die(mysql_error());
mysql_set_charset('utf8', $link) or die(mysql_error($link));
mysql_select_db('testdb', $link) or die(mysql_error($link));
$result = mysql_query('SELECT name FROM people', $link) or die(mysql_error($link));
while ($row = mysql_fetch_assoc($result)) {
    $name = htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8');
    echo '<li>' . $name . '</li>' . PHP_EOL;
}
?>
</ul>
</body>
</html>

このように書けるが、もしここで接続に失敗してしまった場合に出力されるHTMLは酷いものになる。以下がその惨劇である。

<!DOCTYPE html>
<html>
<body>
<ul>
No such file or directory

こんなところでHTML文書をぶった切ってしまっていいとは言えないだろう。こうするぐらいならば正直、 「本番環境ではエラーを表示しないんだからいっそのこと or die() を書かなくていい」 という意見の方がまだマシなぐらいである。そもそもエラーを無視して突っ走るというスタイル自体がまともなプログラマの感覚からすればよくないものであり、私であれば以下のようなコーディングを採用するに違いない。

Mysql関数での例(改良版)
<?php
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
while (true) {
    if (!$link = mysql_connect('localhost', 'root', '')) {
        $error = mysql_error();
        break;
    }
    if (!mysql_set_charset('utf8', $link)) {
        $error = mysql_error($link);
        break;
    }
    if (!mysql_select_db('testdb', $link)) {
        $error = mysql_error($link);
        break;
    }
    if (!$result = mysql_query('SELECT name FROM people', $link)) {
        $error = mysql_error($link);
        break;
    }
}
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<body>
<?php if (isset($error)): ?>
  <p><?=h($error)?></p>
<?php else: ?>
  <ul>
<?php while ($row = mysql_fetch_assoc($result)): ?>
    <li><?=h($row['name'])?></li>
<?php endwhile; ?>
  </ul>
<?php endif; ?>
</body>
</html>

こう書けばエラーが発生してもHTMLを正しい構造でレンダリングすることが出来る、しかしやはり エラーを毎回チェックする のは非常に面倒だ。そこで、可能であればエラーではなく 例外オブジェクト として扱わせたいところであろう。オブジェクト指向にまだ触れたことのない駆け出しプログラマの方にも、 避けて通れない道 だと思ってこちらの書き方を選択していただきたい。

Qiita - PHPオブジェクト指向入門

Mysqliクラスでの例
<?php
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
    $mysqli = new mysqli('localhost', 'root', '', 'testdb');
    $mysqli->set_charset('utf8');
    $rows = $mysqli->query('SELECT name FROM people')->fetch_all(MYSQLI_ASSOC);
} catch (mysqli_sql_exception $e) {
    $error = $e->getMessage();
}
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<body>
<?php if (isset($error)): ?>
  <p><?=h($error)?></p>
<?php else: ?>
  <ul>
<?php foreach ($rows as $row): ?>
    <li><?=h($row['name'])?></li>
<?php endforeach; ?>
  </ul>
<?php endif; ?>
</body>
</html>
PDOクラスでの例
<?php
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
try {
    $pdo = new PDO(
        'mysql:dbname=testdb;host=localhost;charset=utf8',
        'root',
        '',
        array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
    );
    $rows = $pdo->query('SELECT name FROM people')->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    $error = $e->getMessage();
}
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<body>
<?php if (isset($error)): ?>
  <p><?=h($error)?></p>
<?php else: ?>
  <ul>
<?php foreach ($rows as $row): ?>
    <li><?=h($row['name'])?></li>
<?php endforeach; ?>
  </ul>
<?php endif; ?>
</body>
</html>

PHPでMySQLに接続するには

いろいろ飛ばしてきたが、ここから本格的に使い方について触れていく。PHPに標準で実装されているものに限定すれば、以下の3種類がある。

Mysql関数

PHP4時代にはよく使われていた関数。

メリット

  • 動作が速い。
  • どんな環境でもたいてい使える。
  • シンプルな手続き型のため、初心者が一番理解しやすい。

デメリット

  • PHP5.5以降は 非推奨 とされている。
  • 例外処理が出来ないため、自分で毎回 mysql_error 関数をコールするなどしてエラーをチェックする必要がある。
  • プリペアドステートメントとプレースホルダが使えないため、必要なときはユーザー入力に対して自前でエスケープ処理などを行う必要があり、脆弱性を作りこんでしまうリスクがある
  • 初心者は理解しやすいが、慣れた人からすれば書きにくいことこの上無し。

Mysqliクラス(またはMysqli関数)

手続き型(関数)とオブジェクト指向型(クラス)を両方ともサポートしている。

メリット

  • 動作が 非常に 速い。
  • (手続き型の場合は) Mysql関数からの書き換えが容易。
  • プリペアドステートメントで型を指定しながら複数のバインドが同時に 出来る
  • マルチクエリ を使用できる。1回の関数コールで複数のクエリが実行できるため、普通に2回コールする場合に比べてパフォーマンスは向上する。
  • エラーを例外としてスローさせることが出来る。2種類の設定方法があるが、これに関しては他の記述がオブジェクト指向型でも手続き型を採用すべきであると言えるであろう。明らかに手続き型の方がスッキリと書けている。
オブジェクト指向型
$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;
手続き型
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
  • 不適切なインデックスを含むクエリに対しても、例外をスローさせることが出来る。開発段階でデータベースに負荷をかけてしまうクエリを潰すことが出来る素晴らしい機能である。これを有効化するためには、下記のようにする。 MYSQLI_REPORT_ALL でまとめてしまったほうがいいだろう。
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT | MYSQLI_REPORT_INDEX);
mysqli_report(MYSQLI_REPORT_ALL);

久保清隆の成長ノート - MySQLパフォーマンスチューニングのためのインデックスの基礎知識

デメリット

  • プリペアドステートメントで名前付きプレースホルダが 使えない
  • 「プリペアドステートメント ≠ 結果セット」 であり、SQL実行後にプリペアドステートメントから結果セットを取り出す必要がある。この点は次に紹介するPDOクラスと大きく異なる。
  • その他の点でも多機能すぎる為、初心者が混乱しやすい。

PDOクラス

オブジェクト指向型のみをサポート。おそらく現在最も使われているであろうもの。

メリット

  • 動作が速い。
  • エラーを例外としてスローさせることが出来る。但し、一部の特例を除き、コンストラクタは常に例外をスローする
PDOクラスで例外をスローさせる
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  • 「プリペアドステートメント = 結果セット」 であり、Mysqliクラスに比べると初心者にも分かりやすい。
  • プリペアドステートメントで名前付きプレースホルダが 使える
  • プリペアドステートメントのエミュレーションを行う場合は 同名の名前付きプレースホルダを複数回使える 。但し、エミュレーションを設定していないと SQLSTATE[HY093]: Invalid parameter number が発生する。
  • 他のデータベースへ乗り換える際、書き換えが容易。PDOクラスの使い方だけを覚えておけばドライバが存在するあらゆるデータベースに対応が出来るようになる。

デメリット

  • プリペアドステートメントで型を指定しながら複数のバインドが同時に 出来ない
  • SET NAMES sjisSET CHARACTER SET sjis を設定している場合、プリペアドステートメントのエミュレーションを無効にしておかないと脆弱性が発生する。
  • プリペアドステートメントのエミュレーションを行う場合は PDOStatement::bindValue, PDOStatement::bindParam メソッドで、指定した型と実際の型が一致しない場合、全て文字列としてバインドされる。文字列以外の型に対しては正しくキャストしてくれないバグとしか思えない挙動だが、どうやらこれは仕様であるらしい。
エラーになる例
$stmt = $pdo->prepare('SELECT * FROM people LIMIT ?, 10');
$from = isset($_POST['page']) ? $_POST['page'] : '0';
$stmt->bindValue(1, $from, PDO::PARAM_INT);
明示的に整数にキャストすることで解決する例
$stmt = $pdo->prepare('SELECT * FROM people LIMIT ?, 10');
$from = isset($_POST['page']) ? (int)$_POST['page'] : 0;
$stmt->bindValue(1, $from, PDO::PARAM_INT);

PEAR::DBPEAR::MDB2 に関してはPHP5の今使うメリットが皆無なので紹介は割愛。「PHP4時代からこれ使ってて慣れてるから」 ぐらいしか適当な理由が思いつかない。

[Z]ZAPAブロ~グ2.0 - PDO、PEAR::DB、Mysql関数の速度比較

PDOの基本

ここからは使用するクラスをPDOに限定し、使い方の基本を押さえていくこととする。

データベースに接続

PDO::__construct メソッドを使用してインスタンスを生成する。コンストラクタなので、実際には new演算子 を用いて生成を行う。

$pdo = new PDO($dsn, $username, $password, $driver_options);

引数は以下の通りである。

$dsn

データベースに接続するために必要な情報。

PHP Manual - PDOクラスのデータベース別DSN一覧

ここではMySQLを例として記載する。原則的には「ドライバ呼び出し」を用いて以下のように書く。

mysql:dbname=testdb;host=localhost;charset=utf8

  • 頭にデータベースの種類を指定して : で区切る。
  • 各項目は 項目名=値 とし、 ; で区切る。

dbname

データベース名を指定する。

  • データベースを後で USE testdb のように選択する場合、ここでの記述は省略することが可能。

host

ホスト名またはIPアドレスを指定する。

charset

文字セットを指定する。

  • UTF-8の場合は utf8 となる。 UTF-8 ではないことに注意する。
  • SET NAMES で後から設定することは、最初の章で示している理由のとおり避けるべきである。

$username

ユーザー名。何もいじっていなければ root

$password

パスワード。何もいじっていなければ空白。

$driver_options

接続時のオプションを連想配列で渡す。

  • キーはあらかじめ用意されている定数を取る。
  • 値はあらかじめ用意されている定数以外に、論理値・文字列などの一般的な値も取り得る。
array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
)

接続後にオプションを指定

PDO::setAttribute メソッドを使用する。コンストラクタの $driver_options で指定してもこちらで指定しても大差はないが、 PDO::MYSQL_ATTR_INIT_COMMAND PDO::ATTR_PERSISTENT など、コンストラクタでしか扱うことが出来ないものもあるので注意する。

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

よく使われるドライバオプションとその値

PDO::ATTR_ERRMODE

SQL実行でエラーが起こった際にどう処理するかを指定する。

  • PDO::ERRMODE_EXCEPTION を設定すると例外をスローしてくれる。これを選択しておくのが一番無難で扱いやすい。さもなくば、 PDOStatement::execute メソッドの返り値が False でないかを毎回のように確認する必要がある。
  • PDO::ERRMODE_WARNING はSQLで発生したエラーをPHPのWarningとして報告する。せっかくオブジェクト指向で書けるようにPDOクラスを選んだのに、わざわざこれを選択する必要性は全く無い。
  • PDO::ERRMODE_SILENT は何も報告しない。これがデフォルトとなっているが変更することを強く推奨する。

PDO::ATTR_DEFAULT_FETCH_MODE

PDOStatement::fetch メソッドや PDOStatement::fetchAll メソッドで引数が省略された場合や、ステートメントがforeach文に直接かけられた場合のフェッチスタイルを設定する。先ほどの章でも述べたが、PDOStatementはmysqli_stmtと異なり、プリペアドステートメントと結果セットの役割を兼ねている。

  • PDO::FETCH_BOTH
    カラム番号とカラム名の両方をキーとする連想配列で取得する。デフォルトとなっているがこれは非推奨。
  • PDO::FETCH_NUM
    カラム番号をキーとする配列で取得する。
  • PDO::FETCH_ASSOC
    カラム名をキーとする連想配列で取得する。
  • PDO::FETCH_OBJ
    カラム名をプロパティとする基本オブジェクトで取得する。

最も一般的なのは PDO::FETCH_ASSOC だろう。 mysql_fetch_assoc 関数互換である。しかし PHP5.3以前では $stmt->fetch()['hoge'] のような添え字アクセスができない ので、PDO::FETCH_OBJ にして $stmt->fetch()->hoge のようにした方がメリットがある場合もある。

PDO::ATTR_EMULATE_PREPARES

さっきから述べているプリペアドステートメントのエミュレーションについて。 TrueFalse で指定する。

PDO::ATTR_PERSISTENT (コンストラクタ専用)

True を設定すると、スクリプトが終了してもデータベースへの接続を維持し、次回に再利用する。データベースサーバーがスクリプトを動かすメインサーバーと分かれて存在している大規模開発時において、パフォーマンスの向上に非常に大きく貢献すると思われる。

PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (MySQL専用)

True のとき、 バッファクエリを使用する。バッファクエリは全ての情報をSQLサーバーから取得してきておいてPHPに1件ずつ取り出させるようになっているが、非バッファクエリは1件ごとにSQLサーバーと通信を行ってPHPに取り出させる。取得してくる情報がメモリに収まりきらない莫大な量である、といった非常に特殊なケースを除けば、バッファクエリを選択しておいた方が無難であろう。サーバー負荷も軽減され、途中までフェッチしたところで突然例外が発生するような事態も避けられる。但し、この値がデフォルトでどうなっているかはバージョンによって異なるようで不明。要調査。

  • MySQL では副作用的恩恵として、 SELECT に対しても PDOStatement::rowCount メソッドで行数を取得できるようになる。
  • PostgreSQL では全てがバッファクエリの扱いを受け、同様に PDOStatement::rowCount メソッドで行数を取得することが可能である。
  • SQLite は構造的にバッファクエリであるとしか考えられないが、何故か行数をこの方法では取得することが出来なかった。

PDO::MYSQL_ATTR_INIT_COMMAND (MySQL専用) (コンストラクタ専用)

接続した直後に実行されるクエリをここに書く。自前で PDO::query メソッドで実行しても特に問題は無い。先ほど述べたように「やむを得ない場合」において文字コード指定のために

"SET NAMES utf8"

としたり、MySQL側による自動変換を防ぐために

"SET SESSION sql_mode='TRADITIONAL'"

としたりする用例が考えられるだろう。後者のようにした場合、格納できない形式の値が渡されたときには例外が発生する。

Qiita - MySQLの自動変換が厄介な時の設定

基本コーディング

TryブロックでPDOクラスに関連するコーディングを全て行い、そこで発生した例外をCatchブロックで捕まえて適切に処理する。以下に紹介するコードはテンプレのように扱っていただいて構わない。オプションも最低限この2つは必ず指定するようにしておきたい。

<?php

try {

    /* リクエストされたスーパーグローバル変数をチェックするなどの処理 */

    // データベースに接続
    $pdo = new PDO(
        'mysql:dbname=testdb;host=localhost;charset=utf8',
        'root',
        '',
        array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        )
    );

    /* データベースから値を取ってきたり、データを挿入したりする処理 */

} catch (Exception $e) {

    // 例外メッセージを後で表示するために変数にセット
    $error = $e->getMessage();

}

// Webブラウザにこれから表示するものがUTF-8で書かれたHTMLであることを伝える
header('Content-Type: text/html; charset=utf-8');

?>
<!DOCTYPE html>
<html>
...
</html>

デバッグモード以外でPDOクラスによって自動的にスローされた例外メッセージを表示したくなければ

<?php

// デバッグモード
define('DEBUG', false);

try {

    /* 省略 */

} catch (PDOException $e) {

    // デバッグモードではないときは例外メッセージを隠す
    $error = DEBUG ? $e->getMessage() : 'ただ今システム障害が発生しております。';

} catch (Exception $e) {

    // 他の例外メッセージは必ず表示する
    $error = $e->getMessage();

}

/* 省略 */

または

<?php

// デバッグモード
define('DEBUG', false);

try {

    /* 省略 */

} catch (Exception $e) {

    // デバッグモードではないときは例外メッセージを隠す
    $error =
        DEBUG || !($e instanceof PDOException) ?
        $e->getMessage() :
        'ただ今システム障害が発生しております。'
    ;

}

/* 省略 */

としてもいいだろう。

参考: コンストラクタでのエラーモードってどうなるの?

PDOのコンストラクタは PDO::ERRMODE_EXCEPTION の有無に関わらず PDOException をスローするが、 ホストに接続できなかった場合などに Warning を発生する。これが先ほど軽く触れた「特例」に該当する。このことに関してマニュアルに記述はない。普通は気に留める必要はないが、ホストを動的に指定させるようなプログラムを書いているならば set_error_handler 関数を使って ErrorException に変換しなければこれを捕まえることができない。

PDO::query メソッドで直接クエリを実行する

ユーザー入力を伴わないクエリに関しては単に PDO::query メソッドを実行すればいい。返り値は PDOStatement となる。

全員を取得する
$stmt = $pdo->query('SELECT * FROM people');

PDO::exec メソッドで直接クエリを実行する

ユーザー入力を伴わないクエリで、 INSERTUPDATE 等で作用した件数を直接返り値に欲しい場合は PDO::exec メソッドを代わりに使う。特に結果を必要としない場合においてもこちらを使用すべきである。後に登場する PDOStatement::execute と紛らわしいので注意。

全員の年齢を+1し、その対象となった人数を返り値として取得する
$count = $pdo->exec('UPDATE people SET age = age + 1');

PDO::preparePDOStatement::bindValuePDOStatement::execute の3ステップでクエリを実行する

ユーザー入力を受け取ってSQL文を動的に生成する場合は プリペアドステートメントプレースホルダ を使わなければならない。

疑問符プレースホルダ を使う方法と、 名前付きプレースホルダ を使う方法がある。併用しようとすると

SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters

が発生するのでどちらかを選択する。

疑問符プレースホルダ

  • ? の「番目」は 1 から始まる。
  • PDO::PARAM_STR は省略することが出来る。
  • エミュレーションがONの場合には正しくキャストしてくれないバグのようなものが仕様として存在するため、文字列以外のものを扱う際に明示的なキャストが必要
  • NULL 値に関しては PDO::PARAM_NULL が暗黙的に使用される。
エミュレーションがOFFの場合はこれでもOK
$stmt = $pdo->prepare('SELECT * FROM people WHERE gender = ? AND age = ?');
$stmt->bindValue(1, $gender);
$stmt->bindValue(2, $age, PDO::PARAM_INT);
$stmt->execute();
エミュレーションがONの場合はこうする必要がある
$stmt = $pdo->prepare('SELECT * FROM people WHERE gender = ? AND age = ?');
$stmt->bindValue(1, $gender);
$stmt->bindValue(2, (int)$age, PDO::PARAM_INT);
$stmt->execute();
  • 番目で整数以外を指定した場合、適当な変換が行われる。
良い例
$stmt->bindValue('2', $age, PDO::PARAM_INT);
$stmt->bindValue('02', $age, PDO::PARAM_INT);
$stmt->bindValue('+02', $age, PDO::PARAM_INT);
$stmt->bindValue('2e0', $age, PDO::PARAM_INT);
$stmt->bindValue('00002.9999', $age, PDO::PARAM_INT);
悪い例
// SQLSTATE[HY093]: Invalid parameter number: Columns/Parameters are 1-based
$stmt->bindValue(-2, $age, PDO::PARAM_INT);

// オーバーフローして3になる
// SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
$stmt->bindValue('00002.999999999999999999999999999', $age, PDO::PARAM_INT);

// Notice: A non well formed numeric value encountered
$stmt->bindValue('2a', $age, PDO::PARAM_INT);

名前付きプレースホルダ

  • : を頭につけ、半角英数字とアンダースコアにて構成する。
  • バインド時の頭の : は省略することが出来る。
こちらも同様にエミュレーションがONならば明示的なキャストを忘れない
$stmt = $pdo->prepare('SELECT * FROM people WHERE age = :age AND gender = :gender');
$stmt->bindValue(':age', (int)$age, PDO::PARAM_INT);
$stmt->bindValue(':gender', $gender);
頭のコロンを省略したケース
$stmt->bindValue('gender', $gender);
  • エミュレーションがONの場合のみ、同名のプレースホルダを複数使うことが出来る。
IDが20で年齢も20歳の人を取得
$n = 20;
$stmt = $pdo->prepare('SELECT * FROM people WHERE age = :n AND id = :n');
$stmt->bindValue(':n', (int)$age, PDO::PARAM_INT);
$stmt->execute();

なお、 値を即時にバインドするのではなく、変数を参照的にバインドしておき、実際に値をバインドするのは実行時になる PDOStatement::bindParam メソッドもあるが、原則的にこちらを使う必要は無い。実行後にバインドした変数が文字列型にされる仕様もあるので注意する。但しこのケースはエミュレーションがONの場合のみ該当する。

Qiita - 最近PDOを使っていてハマったこと2つ。

PDO::preparePDOStatement::execute の2ステップでクエリを実行する

PDOStatement::execute メソッドの引数に配列を渡すと、それらを全てバインドしたあとそのままSQLを実行してくれる。但し、以下の条件に注意する。

  • NULL 値以外は全て PDO::PARAM_STR 扱いになる 。もし間違った型でバインドをしても、データベース側で自動的にキャストし直してくれるが、パフォーマンスの低下につながるので可能な限り避けたほうがいい。
  • また、 既に PDOStatement::bindValue メソッドで値がバインドされていた場合でも、それらは全て無視される 。これを用いる場合、全てのバインドをこの引数で行わなければならない。

疑問符プレースホルダ

PDOStatement::bindValue メソッドを用いたときと異なり、? の「番目」が 0 から始まることに要注意。

$stmt = $pdo->prepare('SELECT * FROM people WHERE city = ? AND gender = ?');
$stmt->execute(array($city, $gender));
キーを適切に設定すれば順番を変えて指定することも可能
$stmt->execute(array(1 => $gender, 0 => $city));

名前付きプレースホルダ

$stmt = $pdo->prepare('SELECT * FROM people WHERE city = :city AND gender = :gender');
$stmt->execute(array(':city' => $city, ':gender' => $gender));
頭のコロンは省略することが出来る
$stmt->execute(array('city' => $city, 'gender' => $gender));
compact関数を活用する例
$stmt->execute(compact('city', 'gender'));

SELECT の結果を取得する

結果の型

MySQL

データベース PHP
(エミュレーション無しmysqlnd)
PHP
(libmysqlclient)
(エミュレーション有りmysqlnd)
NULL NULL NULL
文字列 String String
日付 String String
タイムスタンプ Integer String
論理値 Integer String
PHPで扱える値の整数 Integer String
PHPで扱えない値の整数 String String
浮動小数点数 Float String
  • エミュレーションに関するオプションは PDO::ATTR_EMULATE_PREPARES という命名ではあるが、プリペアドステートメントを使用しない場合にも影響が及ぶことに注意されたい。

PostgreSQL

データベース PHP
NULL NULL
文字列 String
日付 String
タイムスタンプ Integer
論理値 Boolean
PHPで扱える型の整数 Integer
PHPで扱えない型の整数 String
浮動小数点数 String

SQLite

データベース PHP
NULL NULL
その他 String

PDO::setAttribute で取得する型を変更できるケース

PDO::ATTR_ORACLE_NULLS

データベース上 PDO::NULL_EMPTY_STRING PDO::NULL_TO_STRING
NULL NULL ""
"" NULL ""
  • オプション名に ORACLE という名前が入っているが、Oracle以外のデータベースでも利用することが出来る。

PDO::ATTR_STRINGIFY_FETCHES

  • True に設定すると、エミュレーションがOFFの場合に数値を文字列化する。
  • エミュレーションがONの場合はこの設定に関わらず常に文字列化される。

PDOStatement::fetch

カーソルをずらしながら、指定したフェッチモードで1行ずつ取得していく。

  • 引数を省略した場合はデフォルトフェッチモードが使用される。
  • 全ての取得が終わると常に False を返す。

この例ではデフォルトフェッチモードを PDO::FETCH_ASSOC に設定しているとする。

一番基本的な方法
while ($row = $stmt->fetch()) {
    printf("%s lives in %s<br />\n", $row['name'], $row['city']);
}
vprintf()関数を使った応用
while ($row = $stmt->fetch()) {
    vprintf("%s lives in %s<br />\n", $row);
}

PDOStatementクラスはTraversableインターフェースを実装しているため、デフォルトフェッチモードを使う場合、while文の代わりにforeach文でもっとスマートに書ける。

foreach ($stmt as $row) {
    printf("%s lives in %s<br />\n", $row['name'], $row['city']);
}
0から始まるオフセットを取り出すことも出来る
foreach ($stmt as $i => $row) {
    printf("[%d] %s lives in %s<br />\n", $i, $row['name'], $row['city']);
}

PDOStatement::fetchObject

  • PDOStatement::fetchPDO::FETCH_OBJ を指定するケースと等価であるが、こちらの方が短く書くことが出来る。
while ($row = $stmt->fetchObject()) {
    printf("%s lives in %s<br />\n", $row->name, $row->city);
}

PDOStatement::fetchColumn

  • 先頭から数えてそのカラムが何番目にあるかを第1引数として渡す。「番目」は 0 から始まる。省略した場合は 0 を指定したとみなされる。
  • 値に 0 が含まれる可能性がある場合は、 false !== の条件判定をしなければならない。
  • PDOStatement::fetchPDO::FETCH_COLUMN を指定するケースと等価であるが、こちらの方が短く書くことが出来る。
while (false !== $value = $stmt->fetchColumn()) {
    echo "{$value}<br />\n";
}

PDOStatement::fetchAll

  • 一気に全件取得して2次元配列とする。
  • 引数を省略した場合はデフォルトフェッチモードが使用される。
$rows = $stmt->fetchAll();
var_dump($rows);
  • 特定のカラムだけ一気に全件取得して1次元配列としたい場合、第1引数に PDO::FETCH_COLUMN を指定し、第2引数にカラムの「番目」を渡す。「番目」は 0 から始まる。省略した場合は 0 を指定したとみなされる。
$values = $stmt->fetchAll(PDO::FETCH_COLUMN);
var_dump($values);

PDOStatement::setFetchMode

PDOオブジェクト自体にデフォルトフェッチモードを指定する方法を紹介したが、このメソッドを利用すれば個別に発行されたPDOStatementオブジェクトに対して後からフェッチモードを指定することが出来る。引数の渡し方がモードによって異なるので、詳しくはマニュアルを参照して頂きたい。

0番目のカラムをforeachで取得していくケース
$stmt->setFetchMode(PDO::FETCH_COLUMN, 0);
foreach ($stmt as $i => $name) { ... }

実は… PDO::query を用いる場合にも同じ形式でフェッチモードを指定することが出来る。

0番目のカラムをforeachで取得していくケース
foreach ($pdo->query($sql, PDO::FETCH_COLUMN, 0) as $i => $name) { ... }

UPDATE, INSERT で作用した行数を取得する

こちらの結果に対してフェッチしようとした場合、SQLSTATE[HY000]: General error が発生する。こちらに対して用いることができるのは PDOStatement::rowCount メソッドのみ。

printf("%d行に作用しました<br >\n", $stmt->rowCount());

SELECT で該当した行数を取得する

既にバッファクエリについての説明で述べているが、 MySQLPostgreSQL の場合は以下のようになる。

バッファクエリ使用時

非バッファクエリ使用時

  • PDOStatement::fetchAll メソッドを実行した後、つまり全てのフェッチが終わった後であれば SELECT に対しても PDOStatement::rowCount メソッドを使うことが出来る。もしくは取得結果にPHPの count 関数を使う。
  • 最初から件数だけを取得したい場合は SELECT COUNT(id) WHERE ... といったクエリを発行し、その結果を PDOStatement::fetchColumn メソッドで得るべきである。
  • 結果のフェッチを途中で中断したまま次のクエリ実行に移行するときは、 PDOStatement::closeCursor メソッドを使ってカーソルを閉じる必要がある。1つ目の結果セットをPDOStatementに保持したまま2つ目のSQLを実行することはできないので、その場合はあらかじめ PDOStatement::fetchAll メソッドでデータを退避させておく必要がある。

データベース接続の切断

データベース処理の最後に

$pdo = null;

と書いているコードが散見されるが、ほとんどの場合ではこの記述は不要となる。スクリプト終了時に起こる変数の ガベージコレクション により、自動的に切断が行われる。これが必要とされるのは、データベース処理が終わった後、HTMLレンダリングの他にまだやることがあり、それに膨大な時間を要するときのみである。

エミュレーションに関するまとめ

項目 エミュレーションON エミュレーションOFF
パフォーマンス
SET NAMES による安全性
NULL値をそのままの型で受け取る
数値をそのままの型で受け取る
( mysqlnd のみが対象)
×
複数の同名プレースホルダ ×
PDO::PARAM_* 定数による正しいキャスト ×
PDOStatement::bindParam メソッドによる副作用問題 ×
複文の実行

PDOの応用

トランザクション処理

トランザクションの基本的な利用法

使用されるメソッド

ポイント

  • Tryブロックを 2重 に設け、例外発生時にはロールバックを実行し、捕捉した例外を必要に応じて外側のTryブロックに向けてスローする。
  • 同じ形式のプリペアドステートメントの生成は1回だけにして、それを使いまわすようにした方がパフォーマンスは向上する。
try {

    // データベースに接続
    $pdo = new PDO(
        'mysql:dbname=testdb;host=localhost;charset=utf8',
        'root',
        '',
        array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        )
    );

    // パラメータ
    $from = 'A';
    $to = 'B';
    $transfer_amount = 12000;

    // プリペアドステートメントを用意
    $stmt = $pdo->prepare('UPDATE account SET credit = credit + (?) WHERE id = ?');

    // トランザクション処理を開始
    $pdo->beginTransaction();
    try {
        // Aの預金残高を減らす
        $stmt->bindValue(1, -1 * $transfer_amount, PDO::PARAM_INT);
        $stmt->bindValue(2, $from);
        $stmt->execute();
        // Bの預金残高を増やす
        $stmt->bindValue(1, +1 * $transfer_amount, PDO::PARAM_INT);
        $stmt->bindValue(2, $to);
        $stmt->execute();
        // コミット
        $pdo->commit();
    } catch (PDOException $e) {
        // ロールバック
        $pdo->rollBack();
        // 外側のTryブロックに対してスロー
        throw $e;
    }

} catch (PDOException $e) {

    // 例外メッセージを表示
    echo $e->getMessage();

}

トランザクションの種類

秀逸な記事

参考になる記事

推奨場面まとめ

ss (2014-03-26 at 01.47.23).png

各データベース製品のデフォルト

  • MySQL のInnoDBのデフォルトは REPEATABLE READ である。
  • PostgreSQL のデフォルトは READ COMMITTED である。
  • SQLite のデフォルトは DEFERRED であり、これは READ COMMITTED に相当する。

分離レベルを設定するSQLの実行

MySQLでの例
$pdo->exec('SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE');
PostgreSQLでの例
$pdo->exec('SET SESSION CHARACTERISTICS AS TRANSACTION SERIALIZABLE');

可変長プレースホルダ

固定長プレースホルダではどうなるか

例えば、検索キーワードを複数受け取って特定のカラムがそれらすべてに部分一致するかどうかの検索を行うとする。キーワードの数が2個の場合、実行の流れは次のようになるはずだ。最初の章で説明したとおり、LIKE検索ではプレースホルダでのSQLインジェクション防止のためのエスケープとは別に、LIKE検索に限定して使われるメタ文字のエスケープも行わなければならない。

$keywords = array('foo', 'bar');
foreach ($keywords as $keyword) {
    // LIKE検索のために「%キーワード%」の形式にする
    $values[] = '%' . preg_replace('/(?=[!_%])/', '!', $keyword) . '%';
}
$sql = "SELECT * FROM books WHERE ((summary LIKE ? ESCAPE '!') AND (summary LIKE ? ESCAPE '!'))";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);

可変長プレースホルダの導入

ところが、実際は検索キーワードの数が不定個数となるのが一般的である。このような場合にはどう対応するか?そこで必要になってくるのが、動的にプレースホルダを生成することである。具体例を下に示す。

if ($keywords) {
    /* キーワードが1つ以上のときだけ実行 */
    foreach ($keywords as $keyword) {
        // プレースホルダのLIKE部分を用意
        $holders[] = "(summary LIKE ? ESCAPE '!')";
        // LIKE検索のために「%キーワード%」の形式にする
        $values[] = '%' . preg_replace('/(?=[!_%])/', '!', $keyword) . '%';
    }
    // AND条件で結合する
    $sql = 'SELECT * FROM books WHERE (' . implode(' AND ', $holders) . ')';
    // 実行
    $stmt = $pdo->prepare($sql);
    $stmt->execute($values);
}

検索対象のカラムが2つあり、 summary1summary2どちらかに一致すればOKと見なす場合は次のようにする。OR条件とAND条件を両方用いていることに注意する。

if ($keywords) {
    /* キーワードが1つ以上のときだけ実行 */
    foreach ($keywords as $keyword) {
        // プレースホルダのLIKE部分を用意
        $holders[] = "((summary1 LIKE ? ESCAPE '!') OR (summary2 LIKE ? ESCAPE '!'))";
        // LIKE検索のために「%キーワード%」の形式にする
        $values[] = $values[] = '%' . preg_replace('/(?=[!_%])/', '!', $keyword) . '%';
    }
    // AND条件で結合する
    $sql = 'SELECT * FROM books WHERE (' . implode(' AND ', $holders) . ')';
    // 実行
    $stmt = $pdo->prepare($sql);
    $stmt->execute($values);
}

検索キーワードを単語単位に分解する

実際に検索システムを実装するときに、どのようにして検索キーワードを $keywords に配列でセットするかどうかが問題であるが、これは半角スペースで分割させることが一般的であろう。しかし、ここでは半角スペースだけでなく

  • 全てのASCII制御文字
  • 全角スペース
  • ハードスペース(&nbsp;)

正規表現を活用して、これらすべてを対象にしてみる。実装例を示す。

$q = (string)filter_input(INPUT_GET, 'q');
$regex = "/[\\x0-\x20\x7f\xc2\xa0\xe3\x80\x80]++/u";
$keywords = preg_split($regex, $q, -1, PREG_SPLIT_NO_EMPTY);

PREG_SPLIT_NO_EMPTY フラグを用いることで、空文字列の要素を自動的に除外できるのも preg_split 関数の強みである。

テーブル名・カラム名のエスケープ処理

設計としてはあまり宜しくはないが、 「ユーザーに任意のテーブル名やカラム名を指定させたい」 というケースも考えられる。PDOクラスにこういったものをエスケープする機能は含まれていないので、自前でエスケープを行わなければならない。以下の処理を行う。

  • NULLバイトを除外する。
  • バッククオートを2つ連ねてエスケープさせる。
  • 最終的にバッククオートでくくる。
テーブル名をエスケープして動的に指定する例
$sql = sprintf(
    "CREATE TABLE `%s`(id int, name text)",
    str_replace(array("\0", "`"), array("", "``"), $table_name)
);

手元の MySQLSQLite で動作確認を行った。しかし調べてみたところMySQL5.1.6より前のバージョンでは.を使用することが出来ないという制約があるらしい。

PDOクラスの継承

Qiita - PDOでオブジェクトをフェッチ&JSONとCSVファイル出力

k-holyさんによる記事です。こちらの記事よりももっと詳しい情報やその活用例が記載されているので、PDOを隅々まで使いこなしたい中級者~上級者の方にはぜひオススメしたい内容ですね。

Send an edit requestYou can propose improvements about the article to the author 💪
2913 contribution

PHP 5.3.5 以前の PDO で charset を指定する方法として次のようなものもありました(MySQL限定)。

10618 contribution

参考になります!追記しておきます。

14936 contribution

awesome :sparkles:

昔PHPを触ってたときにこの記事を読みたかった

10618 contribution

ありがとうございます!

1172 contribution

レベルの違いを感じるわ(^^;
勉強させて頂きます。

10618 contribution

たぼさんお久しぶりです!
いやいや私もまだまだ^^;

1172 contribution

遅れましたm(_ _)m
お久しぶりです(^^)

ちなみに「tabo」って書いてますが、あだ名が「たー坊」なんです。
サンリオではありません★
(笑)

10618 contribution

この記事なんとかならないかなぁ・・・
何が「スパッと解決できて大変助かりました」「めっちゃ助かりました」だよっていう・・・
http://me.beginsprite.com/archives/889

0 contribution

SET NAMES utf8 、 SET CHARACTER SET utf8 が問題になるケース↓もあるようですが、

libmysqlclientのコンパイルオプションに --with-charset=cp932 や --with-charset=sjis を指定している場合

デフォルトでこのような環境があったりするのでしょうか。
なければSET ~ utf8を使う場合は手動コンパイルしていなければ問題ないという言い方もありかなと思いました。

10618 contribution

参考にさせていただいたリンク先を見ると

しかし、これはlatin-1がlibmysqlclientのデフォルトキャラクタセットの場合だけであって (実際コンパイルのデフォルトはそうなっている) libmysqlclientを–with-charset=cp932オプション付きでコンパイルしていた場合なんかだとその限りではない。

と書かれていますね。自分が管理者の場合は無いと断言してもいいぐらいでしょう。サーバー管理者が自分以外の場合はその方に聞いてみないことには分かりません。

295 contribution

はじめまして。
pdoの実際の使い方をコードを交えて解説してあって、とても参考になりました!

ちなみに一点確認なのですが、

<?php
$hoge = isset($_POST['hoge']) : $_POST['hoge'] : '';
<?php
$hoge = isset($_POST['hoge']) && is_string($_POST['hoge']) : $_POST['hoge'] : '';

は両方ともコロンになってますけど三項演算子って事で良いのですよね?

これからも参考にさせていただきます^^

10618 contribution

ご指摘感謝します!(今まで全く気付かなかった)

10618 contribution

誤った記事の犠牲者を発見
http://otukutun.hatenablog.com/entry/2013/02/06/171150

こっちなんかつい最近だし・・・
http://none.3rin.net/php/php-mysql%20pdo%E3%81%AE%E3%80%8Cset%20names%E3%80%8D%E3%81%AE%E4%BB%A3%E6%9B%BF%E7%AD%96

何とかしてくださいよ「beginsprite log」の管理人さん。。。

6 contribution

host にはホストを指定。127.0.0.1 はローカル環境を指す。
localhostとしてもいいが、Windows環境だと遅くなることがあるので注意。

PHPに限りませんが、MySQL では localhost127.0.0.1 で接続方法が変わるため注意が必要です(Unix系のみ?)。前者は Unix ソケットを使用して接続、後者は TCP/IP を使用して接続されます。そのため、my.cnf に skip-networking (TCPポートを無効化) を設定している環境では localhost でないと接続できません。引っ掛かると原因に気付きにくい所なのでご注意を。
とはいえ最近は、skip-networking を指定しない代わりに bind-address = 127.0.0.1 をデフォルトとしてローカルからのみTCP接続を受け付ける設定にするのが主流なようなので、そういった環境では 127.0.0.1 でも特に問題なく動くと思います。

10618 contribution

@hiroblo はい、提示していただければ...(あんまり大量のファイルとかになるとアレですが

10618 contribution

@hiroblo メールでお願いします。

374 contribution

はじめまして。
php初心者なので、すごく参考になります。

質問なのですが、executeでsql実行に失敗した時のエラー処理も書いたほうがいいですか?
(mysql側でのエラー、例えばout of memoryが発生した場合とかを想定してます)
executeは、例外を投げてくれないので戻り値で成否判断しないといけなさそうだったので…

10618 contribution

@re-24 ご質問ありがとうございます。

PDOStatement::executePDO::ERRMODE_EXCEPTION をエラーモードに設定すれば例外投げてくれるはずですが…

記事中で紹介している

"SET SESSION sql_mode='TRADITIONAL'"

を利用すると PDOStatement::execute 実行時にMySQL側でエラーを発生させることが容易に出来ますので、試してみてください。

基本的にPDOは エラー処理 を書かなくても 例外処理 だけで済むように設計されているはずです。

374 contribution

回答ありがとう御座います。

見落としてましたorz
(見落としちゃいけないところ見てなかった気がするけど・・・)

かなり参考になる情報がまとまってるので、定期的にも読ませてもらいますm(_ _)m

39 contribution

参考にさせてもらってます、ありがとうございます
すごい細かい話ですが、「exit や die で強制終了処理を記述している」の中のPDOクラスの場合の10行目のカンマがたぶん余計だと思います

0 contribution

はじめまして。とても良くまとまっていて有用な記事ですね。あちこちのドキュメントを参照する頻度が減ってきて、便利に利用させていただいています。

ところで些細なことなのですが、トランザクション処理のサンプルコード中に $stmt->commit(); という箇所がありますが、これは $pdo->commit(); の typo ですかね? (rollBack() の箇所も同様)

ご確認いただければ幸いです。

842 contribution

PostgreSQL のクライアントエンコーディングは pg_connect と同じく options で指定できます。

connection.php
$dsn = "pgsql:dbname=exampledb host=example.com options='--client_encoding=UTF8'";

PEAR::MDB2 の代わりを挙げるとすれば、MDB2 のコードの一部を取り込んでいる Doctrine DBAL が妥当だと思います。Doctrine DBAL は PDO の薄いラッパーで、プリペアドステートメントがデフォルトで使われるので、うっかり忘れを防ぐことができます。Doctrine は Symfony のプロジェクトに採用されており、コミュニティーの規模と開発者のスキルの点で、開発の継続性が期待できます。

119 contribution

大分昔の特殊な組み合わせのバグに関するトピックですが。
まとめ的に投稿しておきます。

PDO/MySQL(Windows版)の文字エンコーディング指定の不具合原因
http://www.tokumaru.org/d/20110329.html#p01

windows環境のphp5.3.6までは、charset=sjisを指定した場合にのみ
mysqlndドライバのエスケープ処理にバグが有りました。(utf8やcp932は問題なし)

公式配布されていたwindows用php5.3バイナリでは、mysql, mysqli, pdo_mysql拡張モジュールすべてで、mysqlndドライバを使うようにコンパイルされており影響を受けていました。
http://php.net/manual/ja/mysql.installation.php
http://php.net/manual/ja/mysqli.installation.php
http://php.net/manual/ja/ref.pdo-mysql.php

0 contribution

ではみなさん、この歌を聞いてみましょう。
:headphones: https://youtu.be/d2Kj7YybM5o

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.