[2020/12/11] PHP 標準関数その1・文字列の分割・結合を行う関数(explode, implode)について (No.245)
[2020/12/09] PHP 問合せフォーム等の入力文字列の半角・全角変換について (No.244)
[2020/12/08] PHP QR-CODE をブラウザに表示する簡単な方法 (No.243)
[2020/12/07] PHP PDO クラスを使用したデータテーブル操作クラスを作成してみました (No.242)
[2020/12/05] PHP PDOStatement クラスの中で使うと思われるメソッド(closeCursor,errorInfo,fetchAll,rowCount)について (No.241)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
今回から PHP の標準関数の中で私が良く使うものを選んで、説明していきたいと思います。
その最初として文字列の分解・結合を行う関数である explode 及び implode を取り上げます。
■explode関数
先ずは explode 関数ですが以下の様な定義になっています。(PHP の正式な説明サイトより)
explode ( string $separator , string $string [, int $limit = PHP_INT_MAX ] ) : array [説明] 文字列の配列を返します。この配列の各要素は、 string を文字列 separator で区切った部分文字列となります。 [パラメータ ] $separator :区切り文字列。 $string :入力文字列。 $limit :limit に正の値が指定された場合、返される配列には 最大 limit の要素が含まれ、 その最後の要素には string の残りの部分が全て含まれます。 limit パラメータが負の場合、 最後の -limit 個の要素を除く全ての構成要素が返されます。 limit パラメータがゼロの場合は、1 を指定したものとみなされます。
それでは、簡単な文字列の分割について、以下にスクリプトを示します。 (尚、3行目の設定はWindowsコマンドプロンプトでの実行時に、コンソールへの出力を Shift-JIS にする為のものです)<?php /* f1-explode.php */ // Windowsコマンドプロンプトで[XAMPP]のPHPで表示を Shift-JIS にする為の設定 ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // 分割する文字列 $str = "東京,名古屋,京都,大阪"; // 分割処理 $arr = explode(",", $str); // 配列の内容表示 print_r($arr); </html>
これをWindowsコマンドプロンプトで実行すると以下の表示になります。
C:\xampp\htdocs\_test>php f1-explode.php Array ( [0] => 東京 [1] => 名古屋 [2] => 京都 [3] => 大阪 )
変数「$arr」に指標「0」から順にカンマで分割された文字列が設定されています。
それでは分割対象の文字列にカンマ(,)が無い場合と、関数の第3引数の「$limit」に指定を行う場合について例を示します。<?php /* f1-explode-2.php */ // Windowsコマンドプロンプトで[XAMPP]のPHPで表示を Shift-JIS にする為の設定 ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // 分割する文字列(カンマが無い) $str = "東京名古屋京都大阪"; // 分割処理 $arr = explode(",", $str); // 配列の内容表示 print_r($arr); // 分割する文字列 $str = "東京,名古屋,京都,大阪"; // 分割処理 $arr = explode(",", $str, 2); // limit:2 を設定 // 配列の内容表示 print_r($arr); </html>
これをWindowsコマンドプロンプトで実行すると以下の表示になります。
C:\xampp\htdocs\_test>php f1-explode-2.php Array ( [0] => 東京名古屋京都大阪 ) Array ( [0] => 東京 [1] => 名古屋,京都,大阪 )
分割文字列の中にカンマが無い場合は配列の指標「0」に分割指定文字列そのものが設定されます。
(区切り文字列に分割文字列に存在しないものを指定した場合や、NULL文字列でも同様となります。)
また、「$limit」が分割文字列のカンマの数より小さい場合は配列の指標「$limit - 1」の要素に残りの文字列が設定されます。
■implode関数
implode 関数ですが以下の様な定義になっています。
implode ( string $glue , array $pieces ) : string [説明] 配列の要素を glue 文字列で連結した文字列を返します。 [パラメータ ] $glue :連結する文字列。 $pieces :連結したい文字列の配列。
以下にimplode 関数を使用した例のスクリプトを示します。
<?php /* f1-implode.php */ // Windowsコマンドプロンプトで[XAMPP]のPHPで表示を Shift-JIS にする為の設定 ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // 連結する配列 $arr = array("東京", "名古屋", "京都", "大阪"); // 連結処理(カンマで連結) $str = implode(",", $arr); // 文字列の内容表示 echo "カンマで連結 [" . print_r($str, true) . "]\n"; // 連結処理(長い文字列で連結) $str = implode("--", $arr); // 文字列の内容表示 echo "長い文字列で連結[" . print_r($str, true) . "]\n"; // 連結処理(空白文字列で連結) $str = implode("", $arr); // 文字列の内容表示 echo "空白文字列で連結[" . print_r($str, true) . "]\n"; // 空の配列 $arr = array(); // 連結処理(カンマで連結) $str = implode(",", $arr); // 文字列の内容表示 echo "空の配列を連結 [" . print_r($str, true) . "]\n"; ?>
これをWindowsコマンドプロンプトで実行すると以下の表示になります。
C:\xampp\htdocs\_test>php f1-implode.php カンマで連結 [東京,名古屋,京都,大阪] 長い文字列で連結[東京--名古屋--京都--大阪] 空白文字列で連結[東京名古屋京都大阪] 空の配列を連結 []
ここまでは、単に関数のテストを行った感じですので、これらを利用した関数のスクリプトで示します。
■explode, implode関数を使った関数の例
大した関数ではないのですが、日付チェック用の関数を作ってみました。
日付が正しければ、日付文字列を返し、エラーの場合は false を返す様になっています。<?php /* f1-sample.php */ // Windowsコマンドプロンプトで[XAMPP]のPHPで表示を Shift-JIS にする為の設定 ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // ===== // 日付チェック // ===== // $date string :日付を表す文字列 // [返り値] :日付OKの場合は日付文字列で返し、NGの場合は false を返す // ===== function ChkDate($_date) { // ハイフン("-")をスラッシュ("/")に変換 $date = str_replace("-", "/", $_date); // 年月日を分割 $arr = explode("/", $date); // Y/m/d 形式?? if (isset($arr[0]) == true && isset($arr[1]) == true && isset($arr[2]) == true) { // intに変換して "Y-n-j" 形式にする $intYY = intval($arr[0]); $intMM = intval($arr[1]); $intDD = intval($arr[2]); // 日付チェック if (checkdate($intMM, $intDD, $intYY) == true) { // 日付OKの場合[Y/n/j]書式で返す return implode("/", $arr); //以下の処理の方がいいかもしれません //$strDate = "{$intYY}-{$intMM}-{$intDD}"; //return date("Y/n/j", strtotime($strDate)); } } // エラーの場合は[false]を返す return false; } // [ChkDate]を使う関数 function ExcChkDate($strDate) { $ret = ChkDate($strDate); if ($ret === false) { echo $strDate."...error\n"; } else { echo $strDate." => ".$ret."...OK\n"; } } // OKの日付 ExcChkDate("2020/12/01"); // NGの日付 ExcChkDate("2019/02/29"); // 書式NGの日付 ExcChkDate("201902/29"); // 書式NGの日付 ExcChkDate("/02/29"); ?>
これをWindowsコマンドプロンプトで実行すると以下の表示になります。
C:\xampp\htdocs\_test>php f1-sample.php 2020/12/01 => 2020/12/01...OK 2019/02/29...error 201902/29...error /02/29...error
PR -
Webサイトの 問合せフォーム などでいろんな項目を入力して問合せを行うわけですが、 電話番号や郵便番号などで半角文字しか受け付けないページをたまに見かけます。
番号入力のテキストボックスで、入力時にIMEがONの状態で全角文字の数字をいれて、フォームの登録ボタンを押下すると 「半角文字の数字で入力して下さい。」などとエラーメッセージが表示されます。
必要な情報は半角文字なので PHP には全角から半角への変換ができる mb_convert_kana 関数があるので、 これを使ってできる限り欲しいデータに変換するべきだと思います。
(ただし、住所などの場合、全角と半角の混在が必要なときもあるので、強制的に半角⇒全角の変換はそれぞれのシステムによるとは思います)
それでは、簡単な 問合せフォーム を作成しましたので、以下にスクリプトを示します。
このスクリプトは以下の様な構成になっています。- $_POST から受け取る各データ変数の宣言を行う。
- $_POST から各データを取得する。
- 各データのエラーチェックを行う。
- HTML内でエラーメッセージがあれば表示を行う。
- FORMタグ内で各入力データの表示を行う。
<?php // 初期化 $strName = ""; $strZipno = ""; $strAddr = ""; $strTelno = ""; // $POSTから取得 if((isset($_POST['pname'])== true) && ($_POST['pname'] != "")){ $strName = trim($_POST['pname']); } if((isset($_POST['zipno'])== true) && ($_POST['zipno'] != "")){ $strZipno = trim($_POST['zipno']); } if((isset($_POST['addr'])== true) && ($_POST['addr'] != "")){ $strAddr = trim($_POST['addr']); } if((isset($_POST['telno'])== true) && ($_POST['telno'] != "")){ $strTelno = trim($_POST['telno']); } // エラーチェック $strErr = ""; // 名前 if ($strName == "") { $strErr .= "名前を入力して下さい。<br />"; } // 郵便番号 if ($strZipno == "") { $strErr .= "郵便番号を入力して下さい。<br />"; } else { // 郵便番号チェック(数字3桁または、数字3桁-4桁) if(!preg_match('/^([0-9]{3})(-[0-9]{4})?$/i', $strZipno)){ $strErr .= $strZipno.'は不正な郵便番号です。<br />'; } } // 住所 if ($strAddr == "") { $strErr .= "住所を入力して下さい。<br />"; } // 電話番号 if ($strTelno == "") { $strErr .= "電話番号を入力して下さい。<br />"; } else { // 電話番号チェック(数字のみのチェック) if(!preg_match('/^[0-9]+$/i', $strTelno)){ $strErr .= $strTelno.'は不正な電話番号です。<br />'; } } ?> <html> <head> <meta charset="utf-8"> <title>問合せ・個人情報</title> </head> <body> <h1>問合せ・個人情報</h1> <?php // エラーメッセージ処理 if ($strErr != "") { ?> <div id="ErrorMessage" style="color:red"> <h2>エラーメッセージ</h2> <p> 以下のエラーがあります!<br /> <?php echo $strErr; ?> </p> </div> <?php } ?> <form name="enqform" action="enq1.php" method="post"> 名前 :<input type="text" name="pname" maxlength="20" value="<?php echo $strName; ?>" /><br /> 郵便番号:<input type="text" name="zipno" maxlength="8" value="<?php echo $strZipno; ?>" /><br /> 住所 :<input type="text" name="addr" maxlength="30" value="<?php echo $strAddr; ?>" /><br /> 電話番号:<input type="text" name="telno" maxlength="13" value="<?php echo $strTelno; ?>" /><br /> <input type="submit" value="送信" /> </form> </body> </html>
エラーが表示される様なデータを入力し「送信」ボタンを押下すると以下の表示になります。
「郵便番号」「電話番号」には全角の数字を入力しましたので、エラーメッセージが表示されます。
尚、このスクリプトは最初に表示した時に全ての項目の未入力エラーが表示されます。 これの解消は、自分自身で処理モードを持って最初の表示なのか、「送信」ボタンを押下した時なのかを判定する必要があります。
■全角文字を半角文字に変換する処理を追加したスクリプト
最初にもありましたが mb_convert_kana 関数を使って必要な個所で全角文字を半角文字に変換を行います。
mb_convert_kana 関数ですが以下の様な定義になっています。mb_convert_kana ( string $str [, string $option = "KV" [, string $encoding = mb_internal_encoding() ]] ) : string $str :変換される文字列。 $option :変換オプションで以下の組み合わせで行う。 ・"r" 「全角」英字を「半角」に変換します。 ・"R" 「半角」英字を「全角」に変換します。 ・"n" 「全角」数字を「半角」に変換します。 ・"N" 「半角」数字を「全角」に変換します。 ・"a" 「全角」英数字を「半角」に変換します。 ・"A" 「半角」英数字を「全角」に変換します。 ("a", "A" オプションに含まれる文字は、U+0022, U+0027, U+005C, U+007Eを除く U+0021 - U+007E の範囲です) ・"s" 「全角」スペースを「半角」に変換します(U+3000 -> U+0020)。 ・"S" 「半角」スペースを「全角」に変換します(U+0020 -> U+3000)。 ・"k" 「全角カタカナ」を「半角カタカナ」に変換します。 ・"K" 「半角カタカナ」を「全角カタカナ」に変換します。 ・"h" 「全角ひらがな」を「半角カタカナ」に変換します。 ・"H" 「半角カタカナ」を「全角ひらがな」に変換します。 ・"c" 「全角カタカナ」を「全角ひらがな」に変換します。 ・"C" 「全角ひらがな」を「全角カタカナ」に変換します。 ・"V" 濁点付きの文字を一文字に変換します。"K", "H" と共に使用します。 $encoding:文字エンコーディングを指定します。省略した場合は、 内部文字エンコーディングを使用します。
今回の電話番号、郵便番号は mb_convert_kana 関数のオプション指定は「全角」英数字を「半角」に変換する "a" となります。
あと一つ問合せフォームなどの入力値の扱いで重要なことがあります。
それは、POSTされてきた値をそのまま input タグの value に設定しないことです。
クロスサイトスクリプトの攻撃を防ぐためには必要です。
これを防ぐためには入力値を htmlspecialchars 関数で特殊文字を HTML エンティティに変換します。
関数には引数が必要なので、ラッパ関数として function h() を宣言しそれを使用します。
以下に改良したスクリプトを示します。<?php // 特殊文字を HTML エンティティに変換する関数 function h($s) { return htmlspecialchars($s, ENT_QUOTES, "UTF-8"); } // 処理モード define('MODE_NEW', 1); // 新規 define('MODE_REG', 2); // 登録 // 初期化 $intMode = 0; $strName = ""; $strZipno = ""; $strAddr = ""; $strTelno = ""; // $POSTから取得 if((isset($_POST['mode'])== true) && ($_POST['mode'] != "")){ $intMode = intval($_POST['mode']); } if ($intMode == 0) { $intMode = MODE_NEW; } if((isset($_POST['pname'])== true) && ($_POST['pname'] != "")){ $strName = trim($_POST['pname']); } if((isset($_POST['zipno'])== true) && ($_POST['zipno'] != "")){ $strZipno = trim($_POST['zipno']); $strZipno = mb_convert_kana($strZipno, "a"); // 「全角」⇒「半角」変換 } if((isset($_POST['addr'])== true) && ($_POST['addr'] != "")){ $strAddr = trim($_POST['addr']); } if((isset($_POST['telno'])== true) && ($_POST['telno'] != "")){ $strTelno = trim($_POST['telno']); $strTelno = mb_convert_kana($strTelno, "a"); // 「全角」⇒「半角」変換 } // エラーチェック $strErr = ""; if ($intMode == MODE_NEW) { // 新規モードの場合は、登録モードにする $intMode = MODE_REG; } else if ($intMode == MODE_REG) { // 名前 if ($strName == "") { $strErr .= "名前を入力して下さい。<br />"; } // 郵便番号 if ($strZipno == "") { $strErr .= "郵便番号を入力して下さい。<br />"; } else { // 郵便番号チェック(数字3桁または、数字3桁-4桁) if(!preg_match('/^([0-9]{3})(-[0-9]{4})?$/i', $strZipno)){ $strErr .= $strZipno.'は不正な郵便番号です。<br />'; } } // 住所 if ($strAddr == "") { $strErr .= "住所を入力して下さい。<br />"; } // 電話番号 if ($strTelno == "") { $strErr .= "電話番号を入力して下さい。<br />"; } else { // 電話番号チェック(数字のみのチェック) if(!preg_match('/^[0-9]+$/i', $strTelno)){ $strErr .= $strTelno.'は不正な電話番号です。<br />'; } } } ?> <html> <head> <meta charset="utf-8"> <title>問合せ・個人情報</title> </head> <body> <h1>問合せ・個人情報</h1> <?php // エラーメッセージ処理 if ($strErr != "") { ?> <div id="ErrorMessage" style="color:red"> <h2>エラーメッセージ</h2> <p> 以下のエラーがあります!<br /> <?php echo $strErr; ?> </p> </div> <?php } ?> <form name="enqform" action="enq2.php" method="post"> 名前 :<input type="text" name="pname" maxlength="20" value="<?php echo h($strName); ?>" /><br /> 郵便番号:<input type="text" name="zipno" maxlength="8" value="<?php echo h($strZipno); ?>" /><br /> 住所 :<input type="text" name="addr" maxlength="30" value="<?php echo h($strAddr); ?>" /><br /> 電話番号:<input type="text" name="telno" maxlength="13" value="<?php echo h($strTelno); ?>" /><br /> <input type="hidden" name="mode" value="<?php echo $intMode; ?>" /><br /> <input type="submit" value="送信" /> </form> </body> </html>
尚、 htmlspecialchars 関数は以下の様に定義されています。
htmlspecialchars ( string $string [, int $flags = ENT_COMPAT [, string|null $encoding = NULL [, bool $double_encode = TRUE ]]] ) : string $string 変換される文字列。 $flags 以下のフラグを組み合わせたビットマスクです。 クォートや無効な符号単位シーケンス、そして文書型の扱いを指定します。 デフォルトは ENT_COMPAT | ENT_HTML401 です。 ・ENT_COMPAT ダブルクオートは変換しますがシングルクオートは変換しません。 ・ENT_QUOTES シングルクオートとダブルクオートを共に変換します。 ・ENT_NOQUOTES シングルクオートとダブルクオートは共に変換されません。 ・ENT_SUBSTITUTE 無効な符号単位シーケンスを含む文字列を渡したときに、 空の文字列を返すのではなく Unicode の置換文字に置き換えます。 (UTF-8 の場合は U+FFFD、それ以外の場合は � となります) ・ENT_DISALLOWED 指定した文書型において無効な符号位置を、 Unicode の代替文字である U+FFFD (UTF-8) あるいは � で置き換えます。 これを設定しなければ、無効な符号位置をそのまま残します。 これは、外部コンテンツを埋め込んだ XML 文書を整形式に保つために有用です。 ・ENT_HTML401 コードを HTML 4.01 として処理します。 ・ENT_XML1 コードを XML 1 として処理します。 ・ENT_XHTML コードを XHTML として処理します。 ・ENT_HTML5 コードを HTML 5 として処理します。 $encoding オプションの引数。文字を変換するときに使うエンコーディングを定義します。
上記のスクリプトはまだまだ突っ込みどころ満載で以下の様な処理が必要かと思われますので、 皆さんで追加して下さい。- 問合せの内容を登録する処理。(データベースに依存)
- 登録後の処理をどうするのか。(「登録しました」等のメッセージ表示?)
- 電話番号のエラーチェックが甘い。
- 住所の情報は1個で良いのか。(データベース・テーブルに依存)
- 現状メールアドレスはありませんが、メールアドレスへの確認送信。
- ブラウザのページ戻りの処理をどうするのか?
-
以前 Javascript の以下の記事で QR-CODE の表示を説明しました。
⇒ JavaScript jQueryでQRコードを表示する方法
そこで今回は PHP で作成されたライブラリを使う方法で行ってみます。
このライブラリですが、以下のページからダウンロードできます。
⇒QRcode Perl/CGI & PHP scripts ver. 0.50j
「QRcode Perl CGI & PHP scripts ver. 0.50j ( 2013/5/18)」の「qr_img0.50j.tar.gz (1007KB) 」をダウンロードします。
ダウンロード後は以下の様に展開します。- [qr_img0.50j.tar.gz]ファイルを解凍 ⇒ [qr_img0.50j.tar]ができる。
- [qr_img0.50j.tar]ファイルを解凍 ⇒ [qr_img0.50j]フォルダへのソース展開される。
- [qr_img0.50j]フォルダの中の[qr_img0.50j]フォルダを自分の使用する適当なフォルダにコピーする。
このライブラリは「Y.Swetake」さんが作成されたもので、 「これらのプログラムの著作権は作者であるY.Swetakeにあります。 これらのプログラムはフリーウエアです。もとの著作権表示を変更しなければ 自由に再配布・改造してもかまいません。」とのことです。
それでは、このライブラリの使い方を説明します。使用方法は HTML の IMG タグに以下の様に記述します。<img src="xxx/qr_img0.50j/php/qr_img.php?d=data[&e=(L,M,Q,H)][&s=int size][&v=(1-40)][&t=J]"> ・d:コード変換する文字列(特殊文字や8bit文字はURLencodeされている必要があります) ・e:エラー訂正レベル(指定できるのはL,M,Q,Hの4種類で省略した場合 M が選択されます) ・s:モジュールサイズ(指定できるのは1以上の整数,省略した場合 4(png) または 8(jpeg) ) ・v:バージョン(1~40,省略した場合はプログラムが自動選択) ・t:画像タイプ(J:Jpeg、省略した場合は PNG) xxx:「qr_img0.50j」フォルダが存在する上位のフォルダ
尚、指定するパラメータは「d」「s」だけで問題無いと思います。
■QR-CODEを表示するPHPスクリプト
以下にスクリプトを示します。
<?php $strQR1en = urlencode("Test QR-Code"); $strQR2 = "Test QR-Code 0123456789 abcdefghijklmnopqrstuvwxyz"; $strQR2en = urlencode($strQR2); ?> <html> <head> <meta charset="utf-8"> <title>Test QR-Code</title> </head> <body> <h1>Test QR-Code</h1> <br />size=4<br /> <img src="./qr_img0.50j/php/qr_img.php?s=4&d=<?php echo $strQR1en; ?>"> <br />size=5<br /> <img src="./qr_img0.50j/php/qr_img.php?s=5&d=<?php echo $strQR1en; ?>"> <br />size=6<br /> <img src="./qr_img0.50j/php/qr_img.php?s=6&d=<?php echo $strQR1en; ?>"> <br />size=6 [<?php echo $strQR2; ?>]<br /> <img src="./qr_img0.50j/php/qr_img.php?s=6&d=<?php echo $strQR2en; ?>"> </body> </html>
以下に実行結果を示します。
-
PDO 及び PDOStatement クラスを使ったデータベースへの接続からテーブルレコード取得、及びデータレコードに対しての登録・更新・削除、さらにトランザクションについて、以下の記事で説明してきました。
⇒PHP PDO(PHP Data Objects)クラスを使ったデータベースへのアクセスについて
⇒PHP PDO(PHP Data Objects)クラスを使ったデータベースへのアクセス(登録、更新、削除)について
⇒PHP PDO(PHP Data Objects)クラスを使ったトランザクションについて
⇒PHP PDO(PHP Data Objects)クラスを使った「AUTO_INCREMENT」カラムの取得について
⇒PHP PDOStatement クラスの中で使うと思われるメソッド(closeCursor,errorInfo,fetchAll,rowCount)について
そこでこれらの処理を簡単に行える様にするためのクラスを作成してみました。
クラスの中で PDO クラスのインスタンスを生成し、外部から直接 PDO オブジェクトを参照することが無いようにしました。 このクラスのメソッドは以下の様になります。メソッド名 説明 __construct コンストラクタ(データベースへの接続) getLastError 最終発生エラー情報の取得 close データベース接続を閉じる begin トランザクション開始 commit トランザクション・コミット rollback トランザクション・ロールバック prepare [prepare]メソッドのラッパ関数 lastInsertId 最後のAUTOINCREMENTの取得 execute SQLステートメント実行(prepare, bindValue, excute を内部で順次実行) getColumnData SQLステートメント実行し1個のカラム値を取得(自身の execute メソッドを使用) getSelectCount SQLステートメント実行行数の取得(自身の getColumnData メソッドを使用) ■クラスのスクリプト [cPDOMySQL.php]
以下にスクリプトを示します。
データベースの接続用のホスト名などのデフォルト値をこのソースの先頭に記述しています。
本来であれば、別のPHPソースまたは、ini ファイルなどで読込んで指定すべきかと思います。<?php // ===== // MySQLデータベース操作クラス [cPDOMySQL.php] // ===== // デフォルトのDB指定値 define('MYSQL_HOST', 'localhost'); define('MYSQL_DBNAME', 'pdo'); define('MYSQL_CHARSET', 'UTF8'); define('MYSQL_USER', 'root'); define('MYSQL_PASSWORD','password'); class cPDOMySQL { private $pdo; // PDOクラスインスタンス private $host; // 接続先ホスト private $dbname; // データベース名 private $user; // 接続ユーザ private $password; // 接続パスワード private $charset; // キャラクタセット名 // 最終実行SQL private $sql; // 最終発生エラー情報(配列データ) // 配列[0]:SQLSTATE エラーコード (これは、ANSI SQL 標準で定義された英数 5 文字の ID) // 配列[1]:ドライバ固有のエラーコード // 配列[2]:ドライバ固有のエラーメッセージ private $error; // ===== // 最終発生エラー情報の取得 // ===== // [返り値] // array PDO::errorInfo() または PDOStatement::errorInfo() public function getLastError() { return $this->error; } // ===== // コンストラクタ(データベースへの接続) // ===== // [引数] // $host string 接続先ホスト (デフォルト:MYSQL_HOST) // $dbname string データベース名 (デフォルト:MYSQL_DBNAME) // $charset string キャラクタセット名(デフォルト:MYSQL_CHARSET) // $user string 接続ユーザ (デフォルト:MYSQL_USER) // $password string 接続パスワード (デフォルト:MYSQL_PASSWORD) public function __construct($host = MYSQL_HOST, $dbname = MYSQL_DBNAME, $charset = MYSQL_CHARSET, $user = MYSQL_USER, $password = MYSQL_PASSWORD) { // データベース指定値退避 $this->host = trim($host); $this->dbname = trim($dbname); $this->charset = trim($charset); $this->user = trim($user); $this->password = trim($password); // 初期化 $this->sql = ""; $this->clearError(); // データベース接続 try { $dsn = "mysql:host={$this->host};dbname={$this->dbname};charset={$this->charset}"; $this->pdo = new PDO($dsn, $this->user, $this->password); } catch (PDOException $e) { echo 'データベース接続エラー:'.$e->getMessage(); } if ($this->pdo == false) { die("データベースへの接続に失敗しました。"); } } // ===== // エラー情報クリア // ===== protected function clearError() { $this->error = array(0 => null, 1 => null, 2 => null); } // ===== // データベース接続を閉じる // ===== public function close() { $this->pdo = null; } // ===== // トランザクション開始 // ===== public function begin() { return $this->pdo->beginTransaction(); } // ===== // トランザクション・コミット // ===== public function commit() { return $this->pdo->commit(); } // ===== // トランザクション・ロールバック // ===== public function rollback() { return $this->pdo->rollBack(); } // ===== // [prepare]メソッドのラッパ関数 // ===== // [引数] // $sql string SQLステートメント // [返り値] // mixed 正常終了:[PDOStatement]オブジェクト,エラー:false public function prepare($sql) { // エラークリア $this->clearError(); // SQL文の準備 $pdostmt = $this->pdo->prepare($sql); if ($pdostmt === false) { // エラーの場合 $this->error = $pdostmt->errorInfo(); return false; } // [PDOStatement]オブジェクトを返す return $pdostmt; } // ===== // 最後のAUTOINCREMENTの取得 // ===== // [返り値] // mixed 正常終了:最後に挿入された行の[ID]値の文字列 // エラー :false (getLastError()でエラー情報が取得できる) public function lastInsertId() { // エラークリア $this->clearError(); try { $ret = $this->pdo->lastInsertId(); } catch (PDOException $e) { $this->error = $e->errorInfo; $ret = false; } return $ret; } // ===== // SQLステートメント実行 // ===== // [引数] // $sql string SQLステートメント // $arrBind array SQL内のプレースホルダの値の配列 array(':name' => value, ...) // [返り値] // mixed 正常終了:SELECT文の場合[PDOStatement]オブジェクト, // SELECT文以外の場合 true, // エラー :false public function execute($sql, $arrBind = array()) { // SQL準備 $pdostmt = $this->prepare($sql); if ($pdostmt === false) { return false; } // バインド foreach ($arrBind as $key => $val) { $ret = $pdostmt->bindValue($key, $val); if ($ret === false) { $this->error = $pdostmt->errorInfo(); return false; } } // 実行 $ret = $pdostmt->execute(); if ($ret === false) { $this->error = $pdostmt->errorInfo(); return false; } // SELECTステートメントの場合?? if (preg_match('/^select/i', trim($sql))) { // [PDOStatement]オブジェクト return $pdostmt; } else { // selectステートメント以外の場合 return true; } } // ===== // SQLステートメント実行し1個のカラム値を取得 // ===== // [引数] // $sql string SQLステートメント // $arrBind array SQL内のプレースホルダの値の配列 array(':name' => value, ...) // $columnName string カラム名、またはカラムIndex値 // [返り値] // mixed 正常終了:カラムの値 // エラー :false public function getColumnData($sql, $arrBind = array(), $columnName = 0) { // SQLステートメント実行 $pdostmt = $this->execute($sql, $arrBind); if ($pdostmt === false) { return false; } // 1行取得 $row = $pdostmt->fetch(); if ($row === false) { // エラー $this->error = $pdostmt->errorInfo(); $ret = false; } else { // カラムデータ $ret = $row[$columnName]; } // 接続を閉じる $pdostmt = null; // カラムデータを返す return $ret; } // ===== // SQLステートメント実行行数の取得 // ===== // [引数] // $sql string SQLステートメント // $arrBind array SQL内のプレースホルダの値の配列 array(':name' => value, ...) // [返り値] // mixed 正常終了:件数 // エラー :false public function getSelectCount($sql, $arrBind = array()) { // SELECT文を SELECT count(*) でラップ $sql = "select count(*) from ({$sql}) as tx"; // 件数を取得して返します。 return $this->getColumnData($sql, $arrBind); } } ?>
■クラス cPDOMySQL を利用したスクリプト例
cPDOMySQL クラスは bindValue を複数記述するのが面倒だったために、内部で繰り返し処理しています。
ただし、PDOStatement クラスのオブジェクトを取得した後の処理は、cPDOMySQL クラスの呼び出し側で 処理する必要があります。
データ取得後のデータ処理もクラス化するのであれば、このクラスを継承して、その子クラスで処理を行えばよいかと思います。 (突っ込みどころ満載かと思いますので、改変して下さい。)
取敢えず、cPDOMySQL クラスを利用したスクリプトの例を以下に記します。<?php /* [pdoc1.php] */ include "./cPDOMySQL.php"; ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // MySQLデータベース操作クラス生成(データベース接続) $pdo = new cPDOMySQL(); // データ取得SQL(名前付きパラメータ) $sqlList = "select * from `tm_shohin` where id >= :id1 and id <= :id2 order by id"; // SELECTクエリのパラメータに実際の値設定と実行 $pdostmt = $pdo->execute($sqlList, array(":id1" => "1", ":id2" => "3")); // PDOStatementクラスの fetch メソッドで1行のデータ取得 while ($result = $pdostmt->fetch()) { // データが取得できた場合(データ表示) echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n"; } echo "end...\n"; $ret = $pdo->getSelectCount($sqlList, array(":id1" => "1", ":id2" => "3")); echo "getSelectCount...[$ret]\n"; $ret = $pdo->getColumnData("select now()"); echo "system time...[$ret]\n"; // トランザクション開始 $ret = $pdo->begin(); if ($ret == true) { // トランザクション開始OK try { // レコード登録(INSERT)SQL $sql = "insert into `tm_shohin` (id, name, price) VALUE (:id, :name, :price)"; // SQL文の実行([id:3]のデータ) if ($ret == true) { $arrPara = array(":id" => 3, ":name" => 'パソコン003', ":price" => 303000); $ret = $pdo->execute($sql, $arrPara); } // SQL文の実行([id:4]のデータ) if ($ret == true) { $arrPara = array(":id" => 4, ":name" => 'プリンタ001', ":price" => 50000); $ret = $pdo->execute($sql, $arrPara); } } catch (Exception $e) { // 何かスクリプト上のエラーが在った!! $ret = false; } // トランザクション終了処理 if ($ret == true) { // コミット $pdo->commit(); } else { // ロールバック $pdo->rollback(); } } else { // トランザクション開始NG } // 再度、SELECTクエリのパラメータに実際の値設定と実行 $pdostmt = $pdo->execute($sqlList, array(":id1" => "1", ":id2" => "4")); // PDOStatementクラスの fetch メソッドで1行のデータ取得 while ($result = $pdostmt->fetch()) { // データが取得できた場合(データ表示) echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n"; } echo "end...\n"; // 接続を閉じる $pdo->close(); ?>
これを実行すると以下の様に表示されます。
C:\xampp\htdocs\_test>php pdoc1.php id:1 name :パソコン001 price:110000 id:2 name :パソコン002 price:222200 end... getSelectCount...[2] system time...[2020-12-07 18:07:58] id:1 name :パソコン001 price:110000 id:2 name :パソコン002 price:222200 id:3 name :パソコン003 price:303000 id:4 name :プリンタ001 price:50000 end...
-
PDO クラスを使ったデータベースへの接続からテーブルレコード取得、及びデータレコードに対しての登録・更新・削除、さらにトランザクションについて、以下の記事で説明してきました。
⇒PHP PDO(PHP Data Objects)クラスを使ったデータベースへのアクセスについて
⇒PHP PDO(PHP Data Objects)クラスを使ったデータベースへのアクセス(登録、更新、削除)について
⇒PHP PDO(PHP Data Objects)クラスを使ったトランザクションについて
⇒PHP PDO(PHP Data Objects)クラスを使った「AUTO_INCREMENT」カラムの取得について
そこで、今回は PDOStatement クラスの中で使うと思われるメソッドについて説明します。
- PDOStatement::closeCursor — カーソルを閉じてステートメントを再実行できるようにする
- PDOStatement::errorInfo — 文ハンドラにおける直近の操作に関連する拡張エラー情報を取得する
- PDOStatement::fetchAll — 全ての結果行を含む配列を返す
- PDOStatement::rowCount — 直近の SQL ステートメントによって作用した行数を返す
■PDOStatement::closeCursor — カーソルを閉じてステートメントを再実行できるようにする
カーソル とはデータ取得SQL(SELECT)を実行し、データをフェッチする時の位置を指し示すポインタの様な概念です。
closeCursor はその カーソル を廃棄し再度のSQL実行を有効とします。 以下にスクリプト例を示します。<?php /* [pdo19.php] */ ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // MySQLデータベースに接続 $dsn = "mysql:host=localhost;dbname=pdo;"; $user = 'root'; $password = 'password'; try { // PDOクラス生成(データベース接続) $pdo = new PDO($dsn, $user, $password); } catch (PDOException $e) { // エラー発生 die('データベース接続ERROR:'.$e->getMessage()."\n"); } // 全データ取得SQL $sql = "select * from `tm_shohin` order by id"; // SELECTクエリの準備 $pdostmt = $pdo->prepare($sql); // SELECTクエリの実行 $pdostmt->execute(); // 先頭のデータ取得 $result = $pdostmt->fetch(); // 2番目のデータ取得 $result = $pdostmt->fetch(); echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n"; // カーソルクローズ $pdostmt->closeCursor(); // 再度SELECTクエリの実行 $pdostmt->execute(); // 先頭のデータ取得 $result = $pdostmt->fetch(); echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n"; // 接続を閉じる $pdo = null; ?>
これを実行すると以下の様に表示されます。 カーソルクローズ後、再度クエリ実行した時に、先頭からフェッチされるのがわかります。
C:\xampp\htdocs\_test>php pdo19.php id:2 name :パソコン002 price:202000 id:1 name :パソコン001 price:100000
■PDOStatement::errorInfo — 文ハンドラにおける直近の操作に関連する拡張エラー情報を取得する
errorInfo メソッドは直前のSQL実行に対してのエラー情報を取得します。
エラー情報は以下の様に配列データ(指標:[0 ~ 2])として返されます。- 情報配列[0]: SQLSTATE エラーコード (これは、ANSI SQL 標準で定義された英数 5 文字の ID)
- 情報配列[1]: ドライバ固有のエラーコード
- 情報配列[2]: ドライバ固有のエラーメッセージ
<?php /* [pdo20.php] */ ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // MySQLデータベースに接続 $dsn = "mysql:host=localhost;dbname=pdo;"; $user = 'root'; $password = 'password'; try { // PDOクラス生成(データベース接続) $pdo = new PDO($dsn, $user, $password); } catch (PDOException $e) { // エラー発生 die('データベース接続ERROR:'.$e->getMessage()."\n"); } // 全データ取得SQL:エラーがあるSQL $sql = "select * from `tm_shohin` order by "; // SELECTクエリの準備 $pdostmt = $pdo->prepare($sql); // SELECTクエリの実行 $ret = $pdostmt->execute(); if ($ret === false) { // 実行でエラーが発生したので、エラー情報取得 $arr = $pdostmt->errorInfo(); print_r($arr); } // 接続を閉じる $pdo = null; ?>
これを実行すると以下の様に表示されます。
C:\xampp\htdocs\_test>php pdo20.php Array ( [0] => 42000 [1] => 1064 [2] => You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to us e near '' at line 1 )
■PDOStatement::fetchAll — 全ての結果行を含む配列を返す
fetch メソッドは1行毎のデータを取得しますが、fetch メソッドは全ての結果行を配列で返します。
// fetchメソッド定義 public PDOStatement::fetchAll([ int $fetch_style [, mixed $fetch_argument [, array $ctor_args = array() ]]]):array $fetch_style レコードを呼び出し元に返す方法を制御します。 PDO::FETCH_* 定数のどれかで、デフォルトは PDO::FETCH_BOTH です。 $fetch_argument この引数は、fetch_style の値によって意味が異なります。 PDO::FETCH_COLUMN: ここで指定した、 0 から始まる番号のカラムを返します。 PDO::FETCH_CLASS: ここで指定したクラスのインスタンスを返します。 各行のカラムがクラスのプロパティ名にマッピングされます。 PDO::FETCH_FUNC: ここで指定した関数をコールした結果を返します。 各行のカラムを関数コール時のパラメータとします。 $ctor_args fetch_style が PDO::FETCH_CLASS のときに使う、独自のクラスコンストラクタへの引数。 返り値: 結果セットに残っている全ての行を含む配列を返します。 取得結果がゼロ件だった場合は空の配列を返し、失敗した場合は FALSE を返します。
以下にスクリプトの例を示します。
<?php /* [pdo21.php] */ ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // MySQLデータベースに接続 $dsn = "mysql:host=localhost;dbname=pdo;"; $user = 'root'; $password = 'password'; try { // PDOクラス生成(データベース接続) $pdo = new PDO($dsn, $user, $password); } catch (PDOException $e) { // エラー発生 die('データベース接続ERROR:'.$e->getMessage()."\n"); } // 全データ取得SQL:エラーがあるSQL $sql = "select * from `tm_shohin` order by id"; // SELECTクエリの準備 $pdostmt = $pdo->prepare($sql); // SELECTクエリの実行 $pdostmt->execute(); // 全ての行を取得 $arr = $pdostmt->fetchAll(); print_r($arr); // 接続を閉じる $pdo = null; ?>
これを実行すると以下の様に表示されます。
C:\xampp\htdocs\_test>php pdo21.php Array ( [0] => Array ( [id] => 1 [0] => 1 [name] => パソコン001 [1] => パソコン001 [price] => 100000 [2] => 100000 ) [1] => Array ( [id] => 2 [0] => 2 [name] => パソコン002 [1] => パソコン002 [price] => 202000 [2] => 202000 ) [2] => Array ( [id] => 3 [0] => 3 [name] => パソコン003 [1] => パソコン003 [price] => 303000 [2] => 303000 ) [3] => Array ( [id] => 4 [0] => 4 [name] => プリンタ001 [1] => プリンタ001 [price] => 50000 [2] => 50000 ) )
■PDOStatement::rowCount — 直近の SQL ステートメントによって作用した行数を返す
rowCount メソッドは相当する PDOStatement オブジェクトによって実行された 直前の DELETE, INSERT, UPDATE 文によって作用した行数を返します。
尚、SELECT 文によって作用した行数を返さないので、別の方法が必要になります。 (取得するSELECT文をラップして「SELECT COUNT(*)...」するとかでしょうか)<?php /* [pdo22.php] */ ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); }); // MySQLデータベースに接続 $dsn = "mysql:host=localhost;dbname=pdo;"; $user = 'root'; $password = 'password'; try { // PDOクラス生成(データベース接続) $pdo = new PDO($dsn, $user, $password); } catch (PDOException $e) { // エラー発生 die('データベース接続ERROR:'.$e->getMessage()."\n"); } // レコード更新(UPDATE)SQL $sql = "update `tm_shohin` set price = price * 1.1 where id >= :id1 and id <= :id2"; // SQL文の準備 $pdostmt = $pdo->prepare($sql); // データ変数設定 $intId1 = 1; $intId2 = 3; // データバインド $pdostmt->bindValue(":id1", $intId1, PDO::PARAM_INT); $pdostmt->bindValue(":id2", $intId2, PDO::PARAM_INT); // SQL文の実行 $ret = $pdostmt->execute(); // 実行行数の取得 $count = $pdostmt->rowCount(); echo "rowCount(): $count \n"; // 接続を閉じる $pdo = null; ?>
これを実行すると以下の様に表示されます。
C:\xampp\htdocs\_test>php pdo22.php rowCount(): 3