📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

 
(同じ利用者による、間の27版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
PDO(PHP Data Objects)拡張モジュールは、PHPからデータベースにアクセスするための軽量で高性能なインターフェイスである。<br>
データベースの種類により、使用する関数を区別していた。(例. MySQL : mysqli_connect関数、PostgreSQL : pg_connect関数)<br>
もし、異なるデータベースへ変更・移植する場合は、全て書き換えなくてはならない。<br>
<br>
PDO(PHP Data Objects)を使用することで、データベースの種類を問わず同一のクラスやメソッドを使用することができる。<br>
<br>
PDOは、PHPからデータベースにアクセスするための軽量で高性能なインターフェイスである。<br>
PDOインターフェイスを実装する各データベースドライバは、正規表現のようなデータベース固有の機能を提供する。<br>
PDOインターフェイスを実装する各データベースドライバは、正規表現のようなデータベース固有の機能を提供する。<br>
ただし、PDO拡張モジュールは、データベースのあらゆる関数を実行できるわけではない。<br>
<br>
<br>
データベースサーバにアクセスするには、データベース固有のPDOドライバを使用する必要がある。<br>
ただし、PDOは、データベースのあらゆる関数を実行できるわけではない。<br>
例えば、データベースにアクセスする場合は、データベース固有のPDOドライバを使用する必要がある。<br>
<br>
<br>
PDOは、データアクセスの抽象化レイヤを提供する。<br>
PDOは、データアクセスの抽象化レイヤを提供する。<br>
つまり、使用しているデータベースの種類に関わらず、同じ関数を使用してクエリの発行やデータの取得が行うことができる。<br>
つまり、使用しているデータベースの種類に関わらず、同じ関数を使用してクエリの発行やデータの取得が行うことができる。<br>
<br>
<br>
PDOは、PHP 5.1以降にバンドルされており、PHP 5.0ではPECL拡張モジュールとして使用可能である。<br>
<u>PDOは、PHP 5.1以降にバンドルされており、PHP 5.0ではPECL拡張モジュールとして使用可能である。</u><br>
PHP 5の新機能であるオブジェクト指向で設計されているため、それより前のPHPでは動作しない。<br>
<u>PHP 5の新機能であるオブジェクト指向で設計されているため、それより前のPHPでは動作しない。</u><br>
<br>
ここでは、PDOを使用して、MySQLへ接続して操作する手順を記載する。<br>
<br><br>
<br><br>


60行目: 67行目:
*: <code>mysql_</code>で始まる関数は、PHP5.5で非推奨となっている。
*: <code>mysql_</code>で始まる関数は、PHP5.5で非推奨となっている。
*: <br>
*: <br>
* <code>SET NAMES</code>あるいは<code>SET CHARACTER SET</code>等で文字コードを指定している。<br>または、データベースで使用する文字コードが未指定である。
* MySQLを使用している場合、<code>SET NAMES</code>あるいは<code>SET CHARACTER SET</code>等で文字コードを指定している。<br>または、データベースで使用する文字コードが未指定である。
*: <code>SET NAMES</code>等の使用は避けるべきである。
*: <code>SET NAMES</code>等の使用は避けるべきである。
*: これは、データベース側の文字セットを変更するだけであり、PDOドライバの文字セットは無関係なため、
*: これは、データベース側の文字セットを変更するだけであり、PDOドライバの文字セットは無関係なため、
125行目: 132行目:
<br><br>
<br><br>


== データベースの接続と管理 ==
== データベースの接続 ==
==== 接続文字列 ====
PDOクラスのインスタンスを作成することにより、接続が確立される。<br>
PDOクラスのインスタンスを作成することにより、接続が確立される。<br>
コンストラクタには、データソース(DSN)、ユーザ名、パスワードを指定する。<br>
コンストラクタには、データソース(DSN)、ユーザ名、パスワードを指定する。<br>
<br>
<br>
==== データベースへの接続 ====
データベース接続時になんらかのエラーが発生した場合、<code>PDOException</code>クラスがスローされる。<br>
データベース接続時になんらかのエラーが発生した場合、<code>PDOException</code>クラスがスローされる。<br>
エラー処理を行う場合はこの例外をキャッチする。<br>
エラー処理を行う場合はこの例外をキャッチする。<br>
150行目: 157行目:
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
  $<PDOクラスのインスタンス名> = new PDO(<データソース名>, <データベースユーザ名>, <パスワード>, <ドライバオプション>);
  $<PDOクラスのインスタンス名> = new PDO(<データソース名>, <データベースユーザ名>, <パスワード>, <ドライバオプション>);
PDO PDO::__construct ( string dsn
                        [, string username
                        [, string password
                        [, array driver_options]]] )
指定されたデータベースへの接続を表すPDOインスタンスを生成します。
引数:
  dsn
    データソース名(Data Source Name)またはDSN。データベースに接続するために必要な情報が含まれる。
  username
    DSN文字列のユーザ名。このパラメータは、いくつかのPDOドライバではオプションである。
  password
    パスワード。
    DSN文字列で必要とされる場合に指定。
  driver_options
    ドライバ固有の接続オプションを指定するキー=> 値の配列
戻り値:
  成功時にPDOオブジェクトを返す。
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
* データソース名
* データソース名(Data Source Name)
*: データベースに接続するために必要な情報です. (Data Source Name)
*: データベースに接続するために必要な情報。
*: PHP Manualに各データベースに応じたDSNの記述方法が掲載されている。
*: PHP Manualに各データベースに応じたDSNの記述方法が掲載されている。
*: 以下の例は、MySQLにおけるデータソース名の記述方法である。
*: <code>mysql:dbname=<データベース名>;host=<ホスト名またはIPアドレス>;charset=<例: utf8mb4等></code>
*: 先頭にデータベースの種類を指定して、<code>:</code>(コロン)で区切る。
*: 先頭にデータベースの種類を指定して、<code>:</code>(コロン)で区切る。
*: 各項目は、<code>項目名=値</code>として、<code>;</code>(セミコロン)で区切る。
*: 各項目は、<code>項目名=値</code>として、<code>;</code>(セミコロン)で区切る。
*: <br>
*: 以下の例は、PHP 5.3.6以降のMySQLにおけるデータソース名の記述方法である。(ポート番号は省略可能)
*: PHP 5.3.5以前では、charsetは使用できない。
*: <code>mysql:dbname=<データベース名>;host=<ホスト名またはIPアドレス>:port=<ポート番号>;charset=<文字コード></code>
*: 例. <code>mysql:dbname=SampleDB;host=localhost:port=3306;charset=utf8mb4</code>
*: <br>
*: <br>
** dbname
** dbname
172行目: 206行目:
**: <br>
**: <br>
** charset
** charset
**: 文字コードを指定する。
**: 文字コードを指定する。(<code>SET NAMES</code>は使用しないこと)
**: 文字コードは、ここで指定する。(<code>SET NAMES</code>は使用しないこと)
**: <u>UTF-8を指定する場合、<code>utf8</code>であることに注意すること。</u>
**: <u>UTF-8を指定する場合、<code>utf8</code>であることに注意すること。</u>
**: <u>MySQL5.5.3以降を使用する場合、4バイトからなる絵文字等も取り扱える<code>utf8mb4</code>を使用することを強く推奨する。</u>
**: <u>MySQL5.5.3以降を使用する場合、4バイトからなる絵文字等も取り扱える<code>utf8mb4</code>を使用することを強く推奨する。</u>
188行目: 221行目:
*: 値は予め用意されている定数以外に、論理値や文字列等の一般的な値でもよい。
*: 値は予め用意されている定数以外に、論理値や文字列等の一般的な値でもよい。
<br>
<br>
以下に、よく使用されるドライバオプションとその値を示す。<br>
==== ドライバオプション ====
ドライバオプションとその値を以下に示す。<br>
<br>
<syntaxhighlight lang="php">
// ドライバオプションの使用例
$options = [
    // エラー発生時に例外をスロー
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    // フェッチモードをデフォルトで連想配列に設定
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    // プリペアドステートメントを有効化
    PDO::ATTR_EMULATE_PREPARES => false,
    // 持続的な接続を無効化
    PDO::ATTR_PERSISTENT => false,
    // カラム名を小文字に変換
    PDO::ATTR_CASE => PDO::CASE_LOWER
];
$db = new PDO(
    'mysql:host=localhost;dbname=sampledb;charset=utf8mb4',
    'root',
    'root',
    $options
);
</syntaxhighlight>
<br>
* PDO::ATTR_ERRMODE
* PDO::ATTR_ERRMODE
*: クエリの実行でエラーが起こった場合、どのように処理をするかを指定する。
*: エラーレポートの方法を設定するための属性である。
*: クエリの実行でエラーが起きた場合、どのように処理をするかを指定する。
*: 初期値は、<code>PDO::ERRMODE_SILENT</code>である。
*: 初期値は、<code>PDO::ERRMODE_SILENT</code>である。
** PDO::ERRMODE_EXCEPTION
** PDO::ERRMODE_EXCEPTION
**: 例外をスローする。
**: エラーが発生した時に例外 (PDOException例外クラス) をスローする。
**: エラーを例外としてキャッチできるようになり、デバッグが容易になる。
** PDO::ERRMODE_WARNING
** PDO::ERRMODE_WARNING
**: クエリの実行Lで発生したエラーをPHPのWarningとして報告する。
**: クエリの実行Lで発生したエラーをPHPのWarningとして報告する。
202行目: 267行目:
*: <br>
*: <br>
* PDO::ATTR_DEFAULT_FETCH_MODE
* PDO::ATTR_DEFAULT_FETCH_MODE
*: <code>PDOStatement::fetch</code>メソッドや<code>PDOStatement::fetchAll</code>メソッドで引数が省略された場合、または、
*: SELECT結果を連想配列で取得できる。
*: ステートメントが<code>foreach</code>文に直接かけられた場合のフェッチスタイルを設定する。
*: キーがカラム名になるため、データアクセスが直観的になる。
*: <br>
*: <code>PDOStatement::fetch</code>メソッドや<code>PDOStatement::fetchAll</code>メソッドで引数が省略された場合、
*: または、ステートメントが<code>foreach</code>文に直接かけられた場合のフェッチスタイルを設定する。
*: 初期値は、<code>PDO::FETCH_BOTH</code>である。
*: 初期値は、<code>PDO::FETCH_BOTH</code>である。
** PDO::FETCH_BOTH
** PDO::FETCH_BOTH
217行目: 285行目:
*: データベース側が持つプリペアドステートメント機能のエミュレーションをPDO側で行うかどうかを設定する。
*: データベース側が持つプリペアドステートメント機能のエミュレーションをPDO側で行うかどうかを設定する。
*: PHP 5.2以降の初期値は<code>true</code>である。
*: PHP 5.2以降の初期値は<code>true</code>である。
*: <br>
*: <code>false</code>の場合、ネイティブのプリペアドステートメントを使用する。
*: これは、SQLインジェクション対策として有効であり、また、クエリのパフォーマンスが向上する。
*: <br>
*: この設定は、いくつかPDOの挙動に違いが現れる。
*: この設定は、いくつかPDOの挙動に違いが現れる。
** プリペアドステートメントのためにデータベースと通信する必要が無くなるため、エミュレーションを行う方がパフォーマンスは向上する。
*: エミュレーションを行う場合、プリペアドステートメントのためにデータベースと通信する必要が無くなるため、パフォーマンスは向上する。
** 存在しないテーブル名やカラム名をクエリに持つプリペアドステートメントを発行する場合、<br>エミュレーションを行わない場合はすぐにエラーが発生するが、エミュレーションを行う場合はクエリを実行するまでエラーが発生するかどうかわからない。
*: 存在しないテーブル名 / カラム名をクエリに持つプリペアドステートメントを発行する場合、
*: エミュレーションを行わない場合は即エラーが発生するが、エミュレーションを行う場合はクエリを実行するまでエラーが発生するかどうかわからない。
** エミュレーションを行う場合のみ、<code>;</code>(セミコロン)区切りで複数のクエリを1つのクエリで実行することができる。
** エミュレーションを行う場合のみ、<code>;</code>(セミコロン)区切りで複数のクエリを1つのクエリで実行することができる。
*: <br>
*: <br>
* PDO::ATTR_PERSISTENT(コンストラクタでの指定のみ)
* PDO::ATTR_PERSISTENT (コンストラクタでの指定のみ)
*: <code>true</code>の場合、PHPスクリプトが終了してもデータベースへの接続を維持して、次回に再利用する。
*: <code>true</code>の場合、PHPスクリプトが終了してもデータベースへの接続を維持して、次回に再利用する。
*: 特に、大規模システムでは恩恵が大きい。
*: 特に、大規模システムでは恩恵が大きい。
*: <br>
*: <code>false</code>の場合は、持続的な接続を無効化する。
*: メモリ管理の観点から、通常は無効にすることを推奨する。
*: <br>
*: <br>
* PDO::MYSQL_ATTR_USE_BUFFERED_QUERY(MySQL専用)
* PDO::MYSQL_ATTR_USE_BUFFERED_QUERY(MySQL専用)
239行目: 315行目:
* PDO::MYSQL_ATTR_INIT_COMMAND(MySQL専用、コンストラクタでの指定のみ)
* PDO::MYSQL_ATTR_INIT_COMMAND(MySQL専用、コンストラクタでの指定のみ)
*: データベースに接続した直後に実行されるクエリをここに記述する。
*: データベースに接続した直後に実行されるクエリをここに記述する。
*: <br>
* PDO::ATTR_CASE
*: <code>PDO::CASE_LOWER</code>を指定する場合、カラム名を小文字に統一する。
*: そのため、一貫性のあるコーディングが可能になる。
<br>
<br>
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
244行目: 324行目:
     try
     try
     {
     {
       $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass,  
      // PHP 5.3.6以降
       $dbh = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', $user, $pass,  
                       [
                       [
                         PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                         PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                         PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                         PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                        PDO::ATTR_EMULATE_PREPARES => false,
                       ]);
                       ]);
      // PHP 5.3.5以前
      //$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass,
      //              [
      //                  PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      //                  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
      //                  PDO::ATTR_EMULATE_PREPARES => false,
      //              ]);
      //$dbh->exec("SET NAMES utf8mb4");
       foreach($dbh->query('SELECT * from FOO') as $row)
       foreach($dbh->query('SELECT * from FOO') as $row)
       {
       {
258行目: 350行目:
     {
     {
       // エラーが発生した場合、"500 Internal Server Error"を表示して終了する
       // エラーが発生した場合、"500 Internal Server Error"を表示して終了する
       // もし、エラー画面を表示する場合、HTMLの表示を継続する
       // もし、任意のエラー画面を表示する場合、HTMLの表示を継続する
       // ここではエラー内容を表示しているが、実際の商用環境ではログファイルに記録してWebブラウザには表示しない方が望ましい
       // ここではエラー内容を表示しているが、実際の商用環境ではログファイルに記録してWebブラウザには表示しない方が望ましい
       header('Content-Type: text/plain; charset=UTF-8', true, 500);
       header('Content-Type: text/plain; charset=UTF-8', true, 500);
266行目: 358行目:
  ?>
  ?>
   
   
  // Webブラウザにこれから表示するものがUTF-8で書かれたHTMLであることを伝える
  // Webブラウザにheader関数以降に表示するHTMLがUTF-8で記述されていることを認識させる
  // (これか <meta charset="utf-8"> の最低限どちらか1つがあればいい. 両方あっても良い.)
  // または、header関数の代わりに<meta charset="utf-8">を記述する
// 両方記述してもよい
  header('Content-Type: text/html; charset=utf-8');
  header('Content-Type: text/html; charset=utf-8');
   
   
287行目: 380行目:
<br>
<br>


==== データベースの接続を閉じる ====
==== 文字コードの設定 ====
データベースの接続は、PHPスクリプトが終了しても閉じられずにキャッシュされ、他のPHPスクリプトが同じ内容の接続を要求する時に再利用される。<br>
<u>PDOでMySQLに接続する場合は、文字コードの設定 (例: charset=utf8mb4) を指定することを強く推奨する。</u><br>
このキャッシュにより、新しい接続を確立するオーバーヘッドを避けることができるため、Webアプリケーションを高速化できるようになる。<br>
<br>
もし、明示的に指定しない場合、MySQLのデフォルト設定に依存することになり、環境によって動作が異なる可能性がある。<br>
そのため、確実な文字エンコーディングの制御のために、charsetを明示的に指定することを推奨する。<br>
<br>
* 絵文字や特殊文字のサポート
*: utf8mb4は絵文字やその他の4バイト文字を完全にサポートする。
*: 通常のutf8では絵文字などが正しく保存できない可能性がある。
*: <br>
* 文字化け防止
*: 日本語を含む多言語対応において、文字化けを防ぐことができる。
*: データベースとアプリケーション間で一貫した文字エンコーディングを保証する。
*: <br>
* 将来の互換性
*: 現代のWebアプリケーションでは、utf8mb4が事実上の標準となっている。
*: 後々データベースに絵文字等を格納する可能性を考慮すると、最初から設定しておくのが無難である。
<br>
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
  <?php
  // 使用例
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
   
    $sth = $dbh->query('SELECT * FROM foo');
   
   
    // 使用を終了したので、閉じます
$db = new PDO('mysql:host=<IPアドレスまたはホスト名>;dbname=<データベース名>;charset=utf8mb4', '<ユーザ名>', '<パスワード>');
    $sth = null;
    $dbh = null;
?>
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br><br>
==== 持続的な接続 ====
 
== データベースの持続的な接続 ==
<code>PDO::ATTR_PERSISTENT</code>オプションの値は、string型の値が設定されない限り、bool型(持続的な接続の有無を示す)に変換される。<br>
<code>PDO::ATTR_PERSISTENT</code>オプションの値は、string型の値が設定されない限り、bool型(持続的な接続の有無を示す)に変換される。<br>
string型の値を設定する場合、複数の接続プールを使用することができる。<br>
string型の値を設定する場合、複数の接続プールを使用することができる。<br>
323行目: 426行目:
     $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(PDO::ATTR_PERSISTENT => true));
     $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(PDO::ATTR_PERSISTENT => true));
  ?>
  ?>
</syntaxhighlight>
<br><br>
== データベースの切断 ==
データベースの接続は、PHPスクリプトが終了しても閉じられずにキャッシュされ、他のPHPスクリプトが同じ内容の接続を要求する時に再利用される。<br>
このキャッシュにより、新しい接続を確立するオーバーヘッドを避けることができるため、Webアプリケーションを高速化できるようになる。<br>
<br>
もし、明示的に閉じる場合、作成したPDOクラスのオブジェクトにNULLを代入する。<br>
<syntaxhighlight lang="php">
<?php
    try
    {
      $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass,
                      [
                        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
                      ]);
      foreach($dbh->query('SELECT * from FOO') as $row)
      {
          print_r($row);
      }   
    }
    catch (PDOException $e)
    {
      print('Connection failed:'.$e->getMessage());
      die();
    }
    // データベースの切断
    $dbh = null;
?>
</syntaxhighlight>
<br><br>
== クエリの実行 ==
==== PDO::queryメソッド ====
ユーザ入力を伴わないクエリは、PDO::queryメソッドを実行する。<br>
戻り値は、PDOStatementクラスである。<br>
<syntaxhighlight lang="php">
$stmt = $pdo->query('SELECT * FROM users');
</syntaxhighlight>
<br>
==== PDO::execメソッド ====
ユーザ入力を伴わないクエリにおいて、INSERTやUPDATE等を使用してレコードを直接取得する場合、PDO::execメソッドを使用する。<br>
また、結果を必要としない場合においても、PDO::execメソッドを使用すべきである。<br>
<br>
以下の例では、テーブルに存在する全てのレコードを更新して、更新した全てのレコード数を取得している。<br>
<syntaxhighlight lang="php">
$count = $pdo->exec('UPDATE users SET age = age + 1');
</syntaxhighlight>
<br>
==== PDO::executeメソッド(3ステップ) ====
<code>PDO::prepare</code>メソッド → <code>PDOStatement::bindValue</code>メソッド → <code>PDOStatement::execute</code>メソッドの3ステップでクエリを実行する。<br>
ユーザ入力からクエリを動的に作成する場合、プリペアドステートメントとプレースホルダを使用する。<br>
* プレースホルダ
*: ユーザ入力を代入する場所として、予め確保するためのもの。
*: プレースホルダには2種類あり、<u>疑問符プレースホルダを使用する方法</u>と<u>名前付きプレースホルダ使用する方法</u>がある。
*: これらを混ぜて使用する場合はエラーとなる。
* プリペアドステートメント
*: プレースホルダを使用するために作成する。
<br>
* 疑問符プレースホルダ<br>
*: ?の要素は、1から始まる。<br>
*: <code>PDO::PARAM_STR</code>は省略することができる。<br>
*: エミュレーションが有効の場合、正しくキャストできない場合があるため、文字列以外を扱う時は明示的なキャストが必要である。<br>
*: NULL値に関しては、<code>PDO::PARAM_NULL</code>が暗黙的に使用される。<br>
<syntaxhighlight lang="php">
// エミュレーションが有効の場合
$stmt = $pdo->prepare('SELECT * FROM users WHERE gender = ? AND age = ?');
$stmt->bindValue(1, $gender);
$stmt->bindValue(2, (int)$age, PDO::PARAM_INT);
$stmt->execute();
// エミュレーションが無効の場合、上記または下記のいずれの記述も可能
<syntaxhighlight lang="php">
$stmt = $pdo->prepare('SELECT * FROM users WHERE gender = ? AND age = ?');
$stmt->bindValue(1, $gender);
$stmt->bindValue(2, $age, PDO::PARAM_INT);
$stmt->execute();
</syntaxhighlight>
<br>
* 名前付きプレースホルダ
*: <code>:</code>(コロン)を先頭に付加して、半角英数字とアンダースコアにて構成する。
*: バインド時の先頭は、<code>:</code>(コロン)は省略できる。
*: エミュレーションが有効の場合、明示的なキャストが必要である。
<syntaxhighlight lang="php">
// エミュレーションが有効の場合
$stmt = $pdo->prepare('SELECT * FROM users WHERE age = :age AND gender = :gender');
$stmt->bindValue(':age', (int)$age, PDO::PARAM_INT);
$stmt->bindValue(':gender', $gender);
</syntaxhighlight>
<br>
<syntaxhighlight lang="php">
// バインド時において、先頭の:(コロン)を省略する場合
$stmt->bindValue('gender', $gender);
</syntaxhighlight>
<br>
<syntaxhighlight lang="php">
// IDが20で年齢も20歳の人を取得
$n = 20;
$stmt = $pdo->prepare('SELECT * FROM users WHERE age = :n AND id = :n');  // エミュレーションが有効の場合のみ、同名のプレースホルダを複数使用できる
$stmt->bindValue(':n', (int)$age, PDO::PARAM_INT);
$stmt->execute();
</syntaxhighlight>
<br>
なお、 値を即時にバインドせずに、変数を参照的にバインドしておき、実行時に値をバインドする<code>PDOStatement::bindParam</code>メソッドも存在するが、<br>
<code>PDOStatement::bindParam</code>メソッドを使用する必要はない。<br>
エミュレーションが有効の場合、実行後にバインドした変数が文字列型に変換する仕様もあるので、注意すること。<br>
<br>
==== PDO::executeメソッド(2ステップ) ====
<code>PDO::prepare</code>メソッド → <code>PDOStatement::execute</code>メソッドの2ステップでクエリを実行する。<br>
<code>PDOStatement::execute</code>メソッドの引数に配列を渡す場合、それらを全てバインドした後そのままクエリが実行される。<br>
<br>
ただし、以下の条件に注意すること。<br>
<u>NULL値以外は全て<code>PDO::PARAM_STR</code>扱いになる。</u><br>
もし、間違った型でバインドする場合はMySQL / SQLiteはデータベース側で自動的にキャストし直すが、<br>
パフォーマンスの低下やバグの原因になるため、可能な限り避けること。(PostgreSQLの場合はエラーになる)<br>
<br>
また、既に<code>PDOStatement::bindValue</code>メソッドで値がバインドされている場合でも、それらは全て無視される。<br>
これを用いる場合、全てのバインドをこの引数で行わなければならない。<br>
<br>
* 疑問符プレースホルダ
*: <code>PDOStatement::bindValue</code>メソッドとは異なり、<code>?</code>のインデックスは0から始まる。
<syntaxhighlight lang="php">
$stmt = $pdo->prepare('SELECT * FROM users WHERE city = ? AND gender = ?');
$stmt->execute([$city, $gender]);
// キーを設定して順番を変えて指定することもできる
$stmt->execute([1 => $gender, 0 => $city]);
</syntaxhighlight>
<br>
* 名前付きプレースホルダ
<syntaxhighlight lang="php">
$stmt = $pdo->prepare('SELECT * FROM users WHERE city = :city AND gender = :gender');
$stmt->execute([':city' => $city, ':gender' => $gender]);
// 先頭のコロンは省略できる
$stmt->execute(['city' => $city, 'gender' => $gender]);
// compact関数を使用する場合
$stmt->execute(compact('city', 'gender'));
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>
359行目: 606行目:
       $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
       $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
   
   
      // トランザクション開始
       $dbh->beginTransaction();
       $dbh->beginTransaction();
      // SQLクエリの実行
       $dbh->exec("insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')");
       $dbh->exec("insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')");
       $dbh->exec("insert into salarychange (id, amount, changedate) values (23, 50000, NOW())");
       $dbh->exec("insert into salarychange (id, amount, changedate) values (23, 50000, NOW())");
      // 正常に実行できた場合はコミット
       $dbh->commit();
       $dbh->commit();
     }
     }
     catch(Exception $e)
     catch(Exception $e)
     {
     {
       $dbh->rollBack();
       // エラーが発生した場合はロールバック
      if ($dbh->inTransaction()) {
          $dbh->rollBack();
      }
       echo "失敗しました。" . $e->getMessage();
       echo "失敗しました。" . $e->getMessage();
     }
     }
381行目: 636行目:
<br><br>
<br><br>


== プリペアドステートメントおよびストアドプロシージャ ==
== プリペアドステートメント ==
プリペアドステートメントとは、実行するSQLをコンパイルしたテンプレートのようなものである。<br>
プリペアドステートメントとは、実行するSQLをコンパイルしたテンプレートのようなものである。<br>
パラメータ変数を使用することで、SQLをカスタマイズすることができる。<br>
パラメータ変数を使用することで、SQLをカスタマイズすることができる。<br>
390行目: 645行目:
<br>
<br>
プリペアドステートメントは、データベースの種類や機能に関わらず同じ仕組みで データベースへのアクセスができる。<br>
プリペアドステートメントは、データベースの種類や機能に関わらず同じ仕組みで データベースへのアクセスができる。<br>
<br><br>
== プリペアドステートメントの種類 ==
==== bindValueメソッド (値渡し) ====
* メリット
*: バインドした後に元の変数が変更されても影響を受けない。
*: 直接リテラル値をバインドできる。
*: メモリ効率が良い。 (参照を保持しない)
* デメリット
*: ループ内で変数を変更して再実行する場合は、その都度、bindValueメソッドが必要となる。
<br>
<syntaxhighlight lang="php">
$value = 100;
$stmt->bindValue(1, $value, PDO::PARAM_INT);
$value = 200;      // この変更はSQLに影響しない
$stmt->execute();  // 100 が使用される
</syntaxhighlight>
<br>
==== bindParamメソッド (参照渡し) ====
* メリット
*: 変数の参照を保持するので、値を変更して再実行が容易
*: ストアドプロシージャのOUTパラメータで使用可能
* デメリット
*: リテラル値を直接バインドできない。
*: メモリを若干多く使用する。 (参照を保持するため)
<br>
<syntaxhighlight lang="php">
$value = 100;
$stmt->bindParam(1, $value, PDO::PARAM_INT);
$value = 200;      // この変更がSQLに反映される
$stmt->execute();  // 200 が使用される
</syntaxhighlight>
<br>
==== executeメソッドで直接記述 ====
* メリット
*: コードが簡潔
*: 1回のみ使用する場合は効率的
*: <br>
* デメリット
*: 変数の値を後から変更して再実行する場合は、その都度配列を作る必要がある。
*: 参照渡しができない。
<br>
<syntaxhighlight lang="php">
$stmt = $pdo->prepare("UPDATE student SET id = ?, name = ?, grade = ? WHERE id = ?");
$stmt->execute([$id, $name, $grade, $old_id]);  // シンプル!
## 1回限りのクエリの場合、executeメソッド
$stmt->execute([$id, $name, $grade]);
</syntaxhighlight>
<br>
==== 使い分け ====
<syntaxhighlight lang="php">
# 使い分け
## 一般的なケースでは、bindValueメソッドを使用
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bindValue(1, $id, PDO::PARAM_INT);
$stmt->execute();
## 以下に示す場合は、bindParamメソッドを使用
## ループ内で値を変更しながら実行する場合
## ループ内で変数を変更しながら再実行する場合
## ストアドプロシージャのOUTパラメータを使用する場合
## 特別な理由で参照渡しが必要な場合
$stmt = $pdo->prepare("INSERT INTO logs (user_id, action) VALUES (?, ?)");
$stmt->bindParam(1, $user_id, PDO::PARAM_INT);
$stmt->bindParam(2, $action, PDO::PARAM_STR);
foreach($actions as $action) {
    $stmt->execute();  // $actionの値が自動的に使用される
}
</syntaxhighlight>
<br>
<u>セキュリティが重要な場合は、型を明示的に指定できるbindValueメソッド、または、bindParamメソッドを使用する。</u><br>
<br>
<u>※注意</u><br>
<u>ただし、実務では一貫性を保つため、特別な理由がない限りどちらか一方に統一することが推奨される。</u><br>
<br><br>
== レコードの取得(プリペアドステートメント) ==
<center>
{| class="wikitable" style="background-color:#fefefe;"
|-
! style="background-color:#00ffff;" | MySQL
! style="background-color:#00ffff;" | PHPのデータ型<br>(エミュレーション無しmysqlnd)
! style="background-color:#00ffff;" | PHPのデータ型(libmysqlclient)<br>(エミュレーション有りmysqlnd)
|-
| NULL || NULL || NULL
|-
| 文字列 || String || String
|-
| 日付 || String || String
|-
| タイムスタンプ || Integer || String
|-
| 論理値 || Integer || String
|-
| PHPで扱える値の整数 || Integer || String
|-
| PHPで扱えない値の整数 || String || String
|-
| 浮動小数点 || String || String
|}
</center>
<u>※注意</u><br>
<u>エミュレーションに関するオプションは、PDO::ATTR_EMULATE_PREPARESという命名ではあるが、</u><br>
<u>プリペアドステートメントを使用しない場合にも影響が及ぶことに注意すること。</u><br>
<br>
下表に、<code>PDO::setAttribute</code>で取得するデータ型を変更できるものを示す。<br>
<code>PDO::ATTR_ORACLE_NULLS</code>オプションは、Oracle以外のデータベースでも使用できる。<br>
<center>
{| class="wikitable" style="background-color:#fefefe;"
|-
! style="background-color:#00ffff;" | データベース
! style="background-color:#00ffff;" | PDO::NULL_EMPTY_STRING
! style="background-color:#00ffff;" | PDO::NULL_TO_STRING
|-
| NULL || NULL || ""
|-
| "" || NULL || ""
|}
</center>
<br>
また、PDO::ATTR_STRINGIFY_FETCHESオプションをtrueに指定する時、エミュレーションが無効の場合は数値が文字列に変換される。<br>
エミュレーションが有効の場合、設定に関わらず常に数値が文字列に変換される。<br>
<br>
==== PDOStatement::fetchメソッド ====
カーソルを移動して、指定したフェッチモードで1行ずつ取得する。<br>
* 引数を省略する場合、デフォルトフェッチモードが使用される。
* 全てのレコードを取得した場合、falseを返す。
<br>
以下の例では、フェッチモードを<code>PDO::FETCH_ASSOC</code>に設定している。<br>
<syntaxhighlight lang="php">
// 基本的な構文
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);
}
</syntaxhighlight>
<br>
デフォルトフェッチモードの場合、<code>PDOStatement</code>クラスは<code>Traversable</code>インターフェースを実装しているため、foreach文で記述することができる。<br>
ただし、HTMLのための変数を用意する場合、配列として持つ方が都合が良いため、<code>PDOStatement::fetchAll</code>メソッドの使用することを推奨する。<br>
<syntaxhighlight lang="php">
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']);
}
</syntaxhighlight>
<br>
==== PDOStatement::fetchObjectメソッド ====
連想配列の代わりにクラスオブジェクトを取得する。<br>
これは、<code>PDO::FETCH_OBJ</code>を指定して<code>PDOStatement::fetch</code>メソッドを使用する場合と同じであるが、こちらの方が簡潔に記述できる。<br>
<syntaxhighlight lang="php">
while($row = $stmt->fetchObject())
{
    printf("%s lives in %s<br />\n", $row->name, $row->city);
}
</syntaxhighlight>
<br>
==== PDOStatement::fetchColumnメソッド ====
特定の1カラムのみを文字列として取得する。<br>
これは、<code>PDO::FETCH_COLUMN</code>を指定して<code>PDOStatement::fetch</code>メソッドを使用する場合と同じでるが、こちらの方が簡潔に記述できる。<br>
<br>
先頭から数えてそのカラムが何番目(0オリジン)にあるかを第1引数として渡す。(省略する場合、0を指定したとみなされる)<br>
カラムの値に0が含まれる可能性がある場合は、<code>false !==</code>の判定をしなければならない。<br>
<syntaxhighlight lang="php">
while(false !== $value = $stmt->fetchColumn())
{
    echo "{$value}<br />\n";
}
</syntaxhighlight>
<br>
==== PDOStatement::fetchAllメソッド ====
全てのレコードを取得して2次元配列とする。<br>
* 引数を省略する場合、デフォルトフェッチモードが使用される。
* 特定のカラムのみ全てのレコードを取得して1次元配列とする場合、<br>第1引数に<code>PDO::FETCH_COLUMN</code>を指定して、第2引数に先頭から数えてそのカラムが何番目(0オリジン)にあるかを渡す。(省略する場合、0を指定したとみなされる)
<syntaxhighlight lang="php">
$rows = $stmt->fetchAll();
var_dump($rows);
$values = $stmt->fetchAll(PDO::FETCH_COLUMN);
var_dump($values);
</syntaxhighlight>
<br>
==== PDOStatement::setFetchModeメソッド ====
上記のセクションでは、<code>PDO</code>クラス自身にデフォルトフェッチモードを指定する方法を記載したが、<br>
<code>PDOStatement::setFetchMode</code>メソッドを使用する場合、個別に発行された<code>PDOStatement</code>クラスに対して、後からフェッチモードを指定できる。<br>
また、モードにより引数の渡し方が異なるため、詳細はPHPのマニュアルを参照すること。<br>
<br>
以下の例では、0番目のカラムをforeach文で取得している。<br>
<syntaxhighlight lang="php">
$stmt->setFetchMode(PDO::FETCH_COLUMN, 0);
foreach($stmt as $i => $name)
{
    // ...処理
}
</syntaxhighlight>
<br>
また、<code>PDO::query</code>メソッドも同様の形式でフェッチモードを指定することができる。<br>
以下の例では、上記の例と同様、0番目のカラムをforeach文で取得している。<br>
<syntaxhighlight lang="php">
foreach($pdo->query($sql, PDO::FETCH_COLUMN, 0) as $i => $name)
{
    // ...処理
}
</syntaxhighlight>
<br>
<br>
==== Insert (1)====
==== SQLインジェクション攻撃の対策 ====
以下の例では、nameおよびvalueを名前付きプレースホルダで置き換えて、INSERT句を実行する。<br>
以下の例では、フォームで入力したキーの値に応じたデータを取得している。<br>
ユーザの入力内容は自動的に引用符で括られるため、SQLインジェクション攻撃の恐れは無い。<br>
<syntaxhighlight lang="php">
<?php
    $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?");
    if($stmt->execute(array($_GET['name'])))
    {
      while($row = $stmt->fetch())
      {
          print_r($row);
      }
    }
?>
</syntaxhighlight>
<br><br>
 
== レコードの追加(プリペアドステートメント) ==
以下の例では、nameおよびvalueを<u>名前付きプレースホルダ</u>で置き換えて、INSERT文を実行している。<br>
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
  <?php
  <?php
411行目: 902行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
==== Insert (2) ====
以下の例では、nameおよびvalueを<u>プレースホルダ<code>?</code></u>で置き換えて、INSERT文を実行している。<br>
以下の例では、nameおよびvalueをプレースホルダ<code>?</code>で置き換えて、INSERT句を実行する。<br>
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
  <?php
  <?php
427行目: 917行目:
     $name = 'two';
     $name = 'two';
     $value = 2;
     $value = 2;
    $stmt->execute();
?>
</syntaxhighlight>
<br><br>
== レコードの更新(プリペアドステートメント) ==
以下の例では、name、value、idを<u>名前付きプレースホルダ</u>で置き換えて、UPDATE文を実行している。<br>
<syntaxhighlight lang="php">
<?php
    $stmt = $dbh->prepare("UPDATE REGISTRY SET name = :name value = :value WHERE id = :id");
    $stmt->bindParam(':name', $name);
    $stmt->bindParam(':value', $value);
    $stmt->bindParam(':id', $id);
    // レコード目の更新
    $name = 'one';
    $value = 1;
     $stmt->execute();
     $stmt->execute();
  ?>
  ?>
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
==== SELECT ====
以下の例では、name、value、idを<u>プレースホルダ<code>?</code></u>で置き換えて、UPDATE文を実行している。<br>
以下の例では、フォームで入力したキーの値に応じたデータを取得している。<br>
ユーザの入力内容は自動的に引用符で括られるため、SQLインジェクション攻撃の恐れは無い。<br>
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
  <?php
  <?php
     $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?");
     $stmt = $dbh->prepare("UPDATE REGISTRY SET name = ? value = ? WHERE id = ?");
     if($stmt->execute(array($_GET['name'])))
     $stmt->bindParam(1, $name);
     {
     $stmt->bindParam(2, $value);
      while($row = $stmt->fetch())
    $stmt->bindParam(3, $id);
      {
          print_r($row);
    // レコードの更新
      }
    $name = 'one';
     }
    $value = 1;
     $stmt->execute();
  ?>
  ?>
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br><br>
 
== ストアドプロシージャ ==
==== ストアドプロシージャの呼び出し : 出力パラメータの指定 ====
==== ストアドプロシージャの呼び出し : 出力パラメータの指定 ====
データベースドライバがサポートしている時、入力パラメータだけでなく、出力パラメータもバインドすることが可能である。<br>
データベースドライバがサポートしている時、入力パラメータだけでなく、出力パラメータもバインドすることが可能である。<br>
483行目: 991行目:
  ?>
  ?>
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br><br>
==== プレースホルダの間違った使用例 ====
 
== プレースホルダの間違った使用例 ==
  <syntaxhighlight lang="php">
  <syntaxhighlight lang="php">
  <?php
  <?php
496行目: 1,005行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:Web]]
[[カテゴリ:Web]][[カテゴリ:MySQL]]