質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

2回答

14127閲覧

PHP/MySQL PDOを使った複数のSQLを文を実行する処理

junkboy

総合スコア45

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

1グッド

4クリップ

投稿2015/11/24 06:03

編集2015/11/25 05:53

1つ関数内で複数のSQL文を実行する場合の処理なのですが、下記ソースのように2回(実行回数)「execute();」を使用して処理してしまっても、可読性など問題はないでしょうか。

こうすれば可読性が上がったり、短縮してかけるよ、という事があればご教授いただけると、大変助かります。

lang

1function cuteInsert() { 2 $dbh = connectDb(); 3 $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 4 5 // 1回目 6 $post_id = h($_POST['post_id']); 7 $sql = "insert into post_ip(post_id, remote_addr, user_agent, created ) values (:post_id, :remote_addr, :user_agent, now())"; 8 $stmt = $dbh->prepare($sql); 9 $stmt->setFetchMode(PDO::FETCH_ASSOC); 10 $stmt->bindValue(':remote_addr', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR); 11 $stmt->bindValue(':user_agent', $_SERVER['HTTP_USER_AGENT'], PDO::PARAM_STR); 12 13 try { 14 $stmt->execute(); 15 } catch (\PDOException $e) { 16 throw new \Exception('今日は投稿できません。'); 17 } 18 19 // 2回目 20 $eva = h($_POST['evaluation']); 21 if ($eva == "0") { 22 $evaluation = 1; 23 } else { 24 $evaluation = $eva + 1; 25 } 26 27 $cute_sql = "update post set evaluation = :evaluation where post_id = :post_id"; 28 29 $stmt = $dbh->prepare($cute_sql); 30 $stmt->setFetchMode(PDO::FETCH_ASSOC); 31 $stmt->bindParam(':post_id', $post_id, PDO::PARAM_INT); 32 $stmt->bindParam(':evaluation', $evaluation, PDO::PARAM_INT); 33 $stmt->execute(); 34 35 return $evaluation; 36 }

↓Kosuke_Shibuyaさん、KiyoshiMotokiさんにご指摘いただいた内容を加味し、ソースを修正しました。

lang

1 function cuteInsert() { 2 $dbh = connectDb(); 3 // $post_id = h($_POST['post_id']); から変更 4 $post_id = filter_input(INPUT_POST, 'post_id'); 5 6 $sql = "insert ignore into post_ip(post_id, remote_addr, user_agent, created ) values (:post_id, :remote_addr, :user_agent, now())"; 7 $dbh->beginTransaction(); 8 $stmt = $dbh->prepare($sql); 9 $stmt->bindValue(':post_id', $post_id, PDO::PARAM_INT); 10 $stmt->bindValue(':remote_addr', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR); 11 $stmt->bindValue(':user_agent', $_SERVER['HTTP_USER_AGENT'], PDO::PARAM_STR); 12 $stmt->execute(); 13 $count = $stmt->rowCount(); 14 15 // 登録できた場合 16 if ($count == "1") { 17 $eva = filter_input(INPUT_POST, 'evaluation'); 18 if ($eva == "0") { 19 $evaluation = 1; 20 } else { 21 $evaluation = $eva + 1; 22 } 23 24 $cute_sql = "update post set evaluation = :evaluation where post_id = :post_id"; 25 26 $stmt = $dbh->prepare($cute_sql); 27 $stmt->bindValue(':post_id', $post_id, PDO::PARAM_INT); 28 $stmt->bindValue(':evaluation', $evaluation, PDO::PARAM_INT); 29 $stmt->execute(); 30 $dbh->commit(); 31 // 登録できなかった場合 32 } else { 33 $dbh->rollBack(); 34 } 35 }
yodel👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

提示のコードだと、「update」の処理は絶対に行われませんね…
(コードに間違いがあるので…)

下記ソースのように2回(実行回数)「execute();」を使用して処理してしまっても、可読性など問題はないでしょうか

「可読性」を考慮するなら、自分だったら処理は分けますし、グローバルな関数は定義せずに、クラスで実装します。

他に気づいたところでは、

この部分は、別の関数に切り分けする。

php

1$dbh = connectDb(); 2$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

提示のコードで以下の部分は無意味

php

1$stmt->setFetchMode(PDO::FETCH_ASSOC);

例外ブロックを変更すべき

php

1try ~ catch (\PDOException $e) { 2}

以下の部分

php

1$post_id = h($_POST['post_id']);

ここで使われている h()htmlspecialchars 相当の機能であるなら、使用方法を間違っています。

以下の2つについても、\ の意味を理解せずに使っているのかなと。

php

1$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

php

1catch (\PDOException $e) {

投稿2015/11/24 08:15

編集2015/11/24 09:54
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

junkboy

2015/11/24 08:25

大変丁寧にご回答いただき、ありがとうございます。 頂いた回答を1つ1つ読み込みつつ、ソースを修正していきたいと思います。 取り急ぎ、お礼まで。
junkboy

2015/11/25 03:24

ご指摘いただいた部分について ・この部分は、別の関数に切り分けする。 '$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);' DBにアクセスする関数'connectDb();'に組み込みました。 'connectDb();'を呼び出す度に指定しており、モヤモヤしていた箇所だったので、ご指摘いただけた事で確信を持って調べる事ができました。ありがとうございます。 処理を分けてクラスで実装される、という部分について、どういうクラスを作成すればよいのか考え中です。 例えばクラス(.phpファイル)が複数あり、それぞれにDBにアクセスする必要がある場合ですと、'connectDb();'のような関数はfunctions.phpに記述しても問題ないでしょうか。 セキュリティ的にクラス毎に実装したほうが良いなどありましたら、ご指摘いただけると幸いです。 ・$stmt->setFetchMode(PDO::FETCH_ASSOC); 他の処理で取得した値を配列に格納した時に使用していたものを、そのままコピペしていましたので、削除しました。 ご指摘ありがとうございます。 ・htmlspecialchars 仰るとおり、使用方法を間違っていました。 いろいろと調べたところ、HTMLに出力する際にサニタイズするのが正しいということだったので、修正しました。 ・\ の意味 こちらもおっしゃるとおり、意識せずに使用していました。 '$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);' を指定しているので、エスケープ処理している上に、’\PDOExceptio’でさらにエスケープ処理をしていた、という認識で良かったでしょうか。 通常'\'は記号をエスケープするときに使用するのだと思いますが、'\PDOExceptio'にはどのような意味があるのでしょうか。 質問を重ねる形で大変恐縮ですが、お時間あるときにでもご教授いただけると幸いです。
退会済みユーザー

退会済みユーザー

2015/11/27 19:38

クラスについて 同種の関数のまとまりを持たせる意味もあるので、自分は、 ・DB接続 ・SELECTの実行 ・UPDATEの実行 ・INSERTの実行 ・DELETEの実行 というような感じでクラスにまとめています。
junkboy

2015/11/30 03:10

・'\'について 調べても辿りつけなかったので、大変助かります。 ・クラスについて なるほど、DBで行う処理内容によって、クラスを作っておられるのですね。 大変参考になります。 投稿した内容とは違う質問にも関わらず、ご丁寧に回答いただき、ありがとうございました。
guest

0

ベストアンサー

細かい話ですが、
1回目のクエリ実行ではbindValueを、2回目ではbindParamを使用しているのが気になります。
"可読性"という点では、特に理由がなければどちらかに統一した方が良いと思います。

以下、ご質問の内容(行数・可読性)とは若干ズレますが、提示いただいたコードで気になった点を上げさせていただきます。
(Kosuke_Shibuya様の回答と重複する内容もあります)

レコードの存在確認について

post_ipテーブルに既にレコードが登録されているか否かを、
PDOExceptionが送出されたか否かで判断するのはよろしくありません。

この方法では、DBに障害が発生した場合など、本当に例外的な状況との区別がつかないからです。

MySQLをお使いであれば、insert into ...insert ignore into ...に変更し、
PDOStatement::rowCount()メソッドで登録された行数を確認する方が良いです。
http://php.net/manual/ja/pdostatement.rowcount.php

トランザクションを使用していない

トランザクションを使用していないため、以下のような状況が発生する可能性があります。
0. 1回目のクエリ実行 -> 成功
0. DB障害発生
0. 2回目のクエリ実行 -> 失敗
0. 結果、post_ipテーブルにレコードは登録されたが、postテーブルは何も変更されない

1回目と2回目のクエリはトランザクションの中で実行し、クエリの実行に失敗したらロールバックしてやる必要があります。
http://php.net/manual/ja/pdo.begintransaction.php

$stmt->setFetchMode(PDO::FETCH_ASSOC); は不要

SQL文の実行後に$stmt->fetch()を実行しない場合、$stmt->setFetchMode()を実行する必要はありません。
http://php.net/manual/ja/pdostatement.setfetchmode.php

投稿2015/11/24 09:29

KiyoshiMotoki

総合スコア4791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

junkboy

2015/11/25 02:29

細かい点までリンク付きでご指摘いただき、ありがとうございます。 Kosuke_Shibuyaさんにご指摘いただいた箇所と合わせて、1つ1つ落とし込んでいきたいと思います。 大変参考になります。 取り急ぎ、お礼まで。
junkboy

2015/11/25 05:53

・bindValue、bindParam 参照している変数の中身が変わると、別の値がバインドされる可能性があるということだったので、bindValueに統一しました。 ・FETCH_ASSOC 別の処理で取得結果を配列に格納するために使用しており、それをコピペしていたので削除しました。 ・レコードの存在確認/トランザクション なるほど、'rowCount()'メソッドを使用して登録された行数を確認する方法があるのですね。 トランザクションについても、ご丁寧にありがとうございます。 ご指摘頂いた内容でソースを更新いたしましたので、お時間のあるときに意向にそった内容になっているか、ご確認いただけますと幸いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問