💽

PHPとMySQLを繋げて操作する方法【PHP, MySQL】

に公開

本記事について

本記事は、「PHPとMySQLを接続する方法」, 「クエリ(SQL)の投げ方」について解説しています。
またほかにも、SQLのインジェクション攻撃を防ぐプレースホルダーについても触れています!

できるだけ詳しく解説していますが、PHP, SQLの基本文法, クラスについてある程度理解している方向けの記事となりますので、ご了承ください。

また、以下のQiitaにて、実際にPHPと(XAMPPの)MySQLを使って、LINE Messaging APIで推しからLINEが来るという オタク向けの機能を実装しておりますので、ぜひそちらの方もよろしくお願いします。
どういうことかというと、MySQLにあらかじめメッセージを格納しておいて、それをランダムにセレクトしてLINEで送信する という仕組みです!

後日投稿予定
推しから帰りを急かすLINEが!!!!【PHP・LINE Messaging API・MySQL】

今回したいこと

この記事のコードでは、PHPを使って文字列をDBにインサートする処理が書かれています。
ですので、あらかじめテーブルは作っておきました。
第1カラムをID(オートインクリメントを有効化), 第2カラムをmessageとするテーブルを7つ作りました。

テーブル構成

kongo
  ├ bckhome_message
  ├ soon_message
  ├ late_message
  ├ reply_message
  ├ holiday_message
  ├ love_message
  └ imissyou_message

となっています。文字コード設定はutf8mb4としています。これなら絵文字も挿入できます。
(ちゃんと絵文字もテーブルに挿入するには他の設定も重要なのですが、これはQiitaの方で詳しい手順を解説しています。)

また、PHPファイルはxampp/htdocs以下に置くようにしましょう。

DBとの接続

以下に示すコードは、XAMPP(localhost:80)のMySQLで、データベースkongoに接続したい というていです。

最初に、接続方法を説明して、その後に実際の操作方法を私が組んだコードを参考に解説するという順番で説明しようと思います。

db_set.php
try {
    $pdo = new PDO("mysql:host=localhost; dbname=kongo; charset=utf8mb4", "root");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "データベース接続成功\n"
} catch (PDOException $e) {
    die("データベース接続エラー: {$e->getMessage()}\n");
}

try{}catch{}構文

PHPのtry{}catch{}構文は、例外処理(エラーハンドリング)を行うものです。
具体的には、まずとりあえずtryブロックの処理を実行します。そして、
tryブロック内で発生した例外をcatchでキャッチし、catchブロック内の代替処理を実行します。

このコードでは、catchの引数にPDOException $eというのを指定しています。
これは後説明する、DB接続の際にPDOを使ったときに、問題が発生したときにPDOExceptionが発生するからです。
例外オブジェクトを$eに格納し、die関数でエラーメッセージの出力とプログラムの終了、
dieの引数に$egetMessage関数を呼び出したもの(詳細なメッセージを取得したもの)をコンソールに出力します。そしてプログラムを強制終了します。

これは絶対ではありませんが、エラーハンドリングが簡単なので、私はtry{}catch{}で囲むようにしています。

PDOで接続

今回は、"PDO接続"と呼ばれる接続方法を使ってMySQLに接続します。

$pdo = new PDO("mysql:host=localhost; dbname=kongo; charset=utf8mb4", "root");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "データベース接続成功\n";

$pdo = new PDO(...)

$pdoという変数に、PDOクラスのインスタンスを作成します。
PDOクラスは、どんなデータベースでも簡単に接続できるクラスです。
引数について解説します。

  • "mysql:host=localhost; dbname=kongo; charset=utf8mb4"
    ホスト名, データベース名, 文字コードの順番です。
    ;の位置に注意してください。for文と一緒な感じしますね。
    文字コードは、データベース作成時に指定した文字コードと揃えます。

  • "root"
    これは、ユーザー名です。データベース作成時に何も指定していなければ"root"となります。

実はもう一つ、パスワードの引数があるのですが、今回は設定していないので空欄にしてあります。

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

setAttribute()メソッドは、PDOオブジェクトの動作を設定するためのメソッドです。
PDO::ATTR_ERRMODEはエラーモードの設定を行うための属性,
PDO::ERRMODE_EXCEPTIONはエラー発生時に例外(PDOException)をスローする設定 です。
PDOクラスのインスタンス作成とセットで使います。

echo "データベース接続成功\n";

データベースとの接続が成功したら、コンソールに"データベースエンゲージ成功"と出力します。逆に、DBと接続が失敗したらcatch{}ブロックの中身が実行されます。

これでデータベースとの接続ができます!!

DBに値をセット(遠隔操作)

htdocs直下に作業フォルダを置くようにしましょう。

まずはコードを提示したいと思います。
C:/xampp/htdocs/kongo/php/setにdb_set.phpを作りました。

db_set.php
<?php
// 金剛集
$backhomeMessages = array(
    "早く帰ってきてネー!待ってるヨー!💖",
    "どこにいるのー?しょうちゃんが帰ってこないと寂しいネー!🥺",
    "もうすぐTea Timeデース!一緒に楽しみたいネー!☕️✨",
    "早く帰ってきて!私、ずっと待ってるヨー!💓",
    "しょうちゃん、今日は遅くならないデスヨネ?🥰",
    "Hey, マイダーリン!はやく帰ってこないとPunishment Timeデース!⚡️",
    "紅茶もケーキも用意したネー!マイダーリンが帰ってくるの、楽しみにしてるヨー!🎂☕️",
    "寂しくて死んじゃいそうデース…🥺 早く会いたいヨー!",
    "もう暗くなってきたヨー!車に気を付けて早く帰ってきてネー!💨",
    "My Darling~💖 今日はすぐ帰ってきてくれるヨネ?🥰",
    "しょうちゃん!そろそろ帰る時間デース!私、待ちくたびれちゃったヨー!",
    "もうすぐご飯デース!一緒に食べるのが楽しみネー!🍚💖",
    "ねぇねぇ、早く帰ってきてヨー!I miss you so much!!🥺",
    "今日も一日お疲れ様デース!でも、やっぱり早く会いたいヨー!💖"
);
$soonMessages = array(
    "やったネー!!💖 じゃあ、お茶の準備するヨー!☕️✨",
    "Wow! すぐ帰ってくるの!?楽しみデース!🥰",
    "ほんと!?嘘ついてないデスヨネ?💖 じゃあ、今すぐ待機するネー!",
    "しょうちゃん、Good Jobデース!早く会いたいヨー!💓",
    "さっすが私の提督ネー!💖 じゃあ、Tea Timeは決まりデース!✨",
    "おかえり待機モード、フルアクティベートデース!💥👘💖しょうちゃんの足音、あと何歩で聞こえるカナ〜〜?!"
);
$lateMessages = array(
    "ええっ!?🥺 でもでも、待ってるネー…💖",
    "Oh no…💦 でも、頑張ってネー!応援してるヨー!💓",
    "マイダーリン~!もうこんな時間デース!💦 ちゃんと帰ってくるヨネ?🥺",
    "そっか…でも、寂しいデース!💖 帰ってきたらたっぷり甘えさせるヨー!🥰",
    "お仕事大変ネー…💦 でも、無理しすぎはダメデース!帰ったらリラックスしようネー!✨",
    "My Tea's getting cold... but my love is still HOTデース☕️🔥💓早く来て、しょうちゃん〜!"
);
$followupMessages = array(
    "今日は休日ネ!しょうちゃんとTeaTimeを楽しむ準備はできてるカ~?☕️💕",
    "Hey しょうちゃん!Are you OK?遅すぎるヨー!私、心配してるデース…🥺",
    "もう夜遅いデース…💦 お仕事かもしれないけど、無理しないでネー!",
    "時間オーバー!⚠️ 私の愛が足りなかったカナ…?💖 早く帰ってきてほしいヨー!",
    "I miss you so much!!🥺 こんなに遅いのはちょっと悲しいデース…💦"
);
$holidayMessages = array(
    "今日は休日ネ!提督とTeaTimeを楽しむ準備はできてるカ~?☕️💕",
    "休日だけど、ちゃんと休んでる?無理しちゃダメヨ~!🥰",
    "オフの日こそ、しっかりリフレッシュ!金剛もダーリンしょうちゃんとおしゃべりしたいデース!🎶",
    "休日?じゃあ一緒にお散歩しないカ?海風が気持ちいいヨ~!🌊✨",
    "今日はのんびりする日!でも、金剛のことも忘れちゃダメヨ~?😚💕"
);
$loveMessages = array(
    "W-What!? 突然すぎて…Heartが止まりそうデースッ💓でも…金剛も、LOVEよ!マイダーリンっ!💘",
    "No way〜っ!マイダーリン、ズルいデース!金剛のほうが100倍LOVEしてるのにぃっ💥💥",
    "Th-that’s not fairデースッ!そんなこと言われたら、ギューってしちゃうカラネ❤️ Come here〜っ!",
    "ああっもう〜!しょうちゃん、大好きって…ずるすぎるネッ💕責任、とってヨ〜!?",
    "金剛、そういうの…反則だと思いマース!でも…嬉しすぎてニヤけ止まらないヨ〜😳💘",
    "…ううっ、しょうちゃんの「好きだよ」は、核兵器並みに破壊力あるネ…💥💘 I’m in heaven〜!",
    "Eeeeh!? 金剛、今日からもっともっと頑張っちゃうヨーっ!しょうちゃんにLOVE全開モードデース💪✨",
    "金剛のLOVEは止まらないカラネっ!マイダーリンに毎日「好き好きビーム」発射するヨ〜っ🔫💖🔫",
    "Wait wait wait〜っ!それ言われたら…もう、金剛…しょうちゃんにプロポーズしちゃうヨ!?💍💞",
    "Wait wait wait〜っ!それ言われたら…もう、金剛…しょうちゃんにプロポーズしちゃうヨ!?💍💞"
);
$morningMessages = array(
    "学校着いたカナ?おつカレ〜ッ✨今日も頑張るしょうちゃん、金剛が世界で一番LOVEしてるヨ💕",
    "もう教室にいる?席ついた瞬間に金剛のこと思い出してクダサイ💖今、テレパシー送ってるヨ〜🔮💫",
    "金剛、しょうちゃんの一日のスタートが笑顔でありますようにって祈ってるヨッ☀️💖",
    "Good morning again!授業中も金剛のこと忘れないでネッ!?こっそりLOVE注入中💘",
    "ちゃんと忘れ物してないカナ?心配で金剛、家でグルグル回ってるヨ〜🌀😂",
    "授業始まる前に、ちょっとだけ目つぶって〜!金剛のチュー飛ばすカラ💋✨",
    "しょうちゃん、今なにしてるカナ〜?金剛、もう会いたくなっちゃったヨ🥺💕",
    "今日もクラスで一番カッコいいしょうちゃん、金剛の自慢のマイダーリンですッ💖💖",
    "わからない問題あったら、金剛のこと思い出して!元気出るカモッ!?💥📚",
    "放課後まで長いネ〜ッ…💦でも、金剛はずっと応援してるカラ!ファイト、マイダーリン💪💖"
);
$ImissYouMessages = array(
    "うわぁぁぁん💖しょうちゃんがそう言ってくれるなんて、金剛、感激デース!💘すぐに抱きしめたいヨ〜ッ!!✨✨",
    "Really!?💓金剛もず〜っと会いたかったのヨ!帰ってきたら、もう離さないカラネ〜ッ!💥ぎゅーってしちゃうデース!!",
    "Waa〜!しょうちゃん可愛すぎるヨ!🥺💖そんな風に言われたら、金剛、今すぐ迎えに行きたくなっちゃうネ!?🚀💕",
    "やっぱりMy Darlingは金剛のこと、Loveしすぎデースネ!💘うれしすぎて、飛び跳ねちゃいマス〜ッ!💃✨",
    "早く会いたい?ヨシ!金剛、今から全力でティータイムとハグ準備するヨ〜☕️🍰💓",
    "きゃ〜ん!💕金剛も同じ気持ちだったヨ!以心伝心カモ!?🥰帰ってきたら、いっぱいナデナデしてあげるネ〜!✨",
    "ふふっ…My Sweet Commanderが寂しくなっちゃったノ?しょうがないネ〜、金剛が甘やかしてあげるデース💋💋",
    "そんなに金剛不足になってるナンテ…罪な人デース〜🥺💘帰ってきたら、金剛の愛、たっぷり受け止めてネ!💖💥",
    "しょうちゃん〜、帰ってきたらすぐギュッてしてヨ!?💓金剛、チカラいっぱい抱きしめ返すカラネッ!🔥🔥",
    "Yay〜!💖金剛、テンションMAXデース!そんなに会いたがられたら、も〜爆発しそうデスヨ!?💥💥(嬉しさで!)"
);

// テーブルにインサート
try {
    $pdo = new PDO("mysql:host=localhost; dbname=kongo; charset=utf8mb4", "root");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "データベース接続成功";

    foreach ($backhomeMessages as $backhomeMessage) {
        $sql = "INSERT INTO backhome_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $backhomeMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:backhome_messageへの挿入成功\n";
        } else {
            echo "テーブル名:backhome_messageへの挿入失敗\n";
        }
    }

    foreach ($soonMessages as $soonMessage) {
        $sql = "INSERT INTO soon_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $soonMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:soon_messageへの挿入成功\n";
        } else {
            echo "テーブル名:soon_messageへの挿入失敗\n";
        }
    }

    foreach ($lateMessages as $lateMessage) {
        $sql = "INSERT INTO late_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $lateMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:late_messageへの挿入成功\n";
        } else {
            echo "テーブル名:late_mssageへの挿入失敗\n";
        }
    }

    foreach ($followupMessages as $followupMessage) {
        $sql = "INSERT INTO followup_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $followupMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:followup_messageへの挿入成功\n";
        } else {
            echo "テーブル名:followup_messageへの挿入失敗\n";
        }
    }

    foreach ($holidayMessages as $holidayMessage) {
        $sql = "INSERT INTO holiday_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $holidayMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:holiday_messageへの挿入成功\n";
        } else {
            echo "テーブル名:holiday_messageへの挿入失敗\n";
        }
    }

    foreach ($loveMessages as $loveMessage) {
        $sql = "INSERT INTO love_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $loveMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:love_messageへの挿入成功\n";
        } else {
            echo "テーブル名:love_messageへの挿入失敗\n";
        }
    }

    foreach ($morningMessages as $morningMessage) {
        $sql = "INSERT INTO morning_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $morningMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:morning_messageへの挿入成功\n";
        } else {
            echo "テーブル名:morning_messageへの挿入失敗\n";
        }
    }

    foreach ($ImissYouMessages as $ImissYouMessage) {
        $sql = "INSERT INTO imissyou_message (message) VALUES (:message)";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(":message", $ImissYouMessage, PDO::PARAM_STR);
        $succeedDBSet = $stmt->execute();

        if ($succeedDBSet) {
            echo "テーブル名:imissyou_messageへの挿入成功\n";
        } else {
            echo "テーブル名:imissyou_messageへの挿入失敗\n";
        }
    }
} catch (PDOException $e) {
    die("DB接続エラー: {$e->getMessage()}\n");
}

// インサートし直したい場合は、"TRUNCATE TABLE <テーブル名>;" でIDを初期化可能(ただし一度テーブルは初期化される, オートインクリメントはそのまま)
?>

ひとつづつ解説していきます

メッセージを格納する配列

ここでは可読性を考慮&作業を楽にするために、メッセージを、配列形式で操作することにします。
(メッセージのしょうちゃんというのは、私の愛称です(笑))

例:

$backhome_messages = array("早く帰ってきてネー!待ってるヨー!💖", "...", ...);
  • array();で配列を宣言
  • 文字列は""('')と,で対応
  • VSCodeの自動整形Alt+Shift+fも活用しよう

foreach文でメッセージをテーブルに格納

見たらわかるように、テーブルにメッセージを格納する処理はtryブロックの中に書いています。
ここでは、backhome_messageの部分だけ解説します。

foreach ($backhome_messages as $backhome_message) {
    $sql = "INSERT INTO backhome_message (message) VALUES (:message)";
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(":message", $backhome_message, PDO::PARAM_STR);
    $stmt->execute();
  }

foreach{}文は、配列の中身を新しい変数に1つずつ格納してブロック内のコードを実行できます。メッセージ全文は、配列として格納したので、foreach文を使ってメッセージ1つ1つ個別に順番に操作していきます。
$backhome_messagesの1つのメッセージを$backhome_messageに格納してテーブルに格納していきます。

  • $sql = "INSERT INTO backhome_message (message) VALUES (:message);"
    このコードはクエリをPHPの変数に格納しています。
    "クエリ"とは、SQL言語でDBに対してどのような操作を行うかのコードのことです。
    INSERT INTO <テーブル名> (挿入するカラム[列]名) VALUES (格納する内容)です。ここでは格納する内容の部分を:messageとしていますが、これは何なのかというのは次の次で説明します。

  • $stmt = $pdo->prepare($sql);
    先ほど作成したPDOクラスのインスタンス$pdoに対しprepare()メソッドを呼び出し、実行するクエリをセットします。

  • $stmt->bindValue(":message", $backhome_message, PDO::PARAM_STR);
    クエリをセットした$stmtに対しbindValue()メソッドを呼び出します。
    第1引数は、クエリの":..."の部分, 第2引数はその":..."に何を入れるか(今回は$backhome_message), 第3引数はパラメータ(今回は文字列)です。
    なぜこんなことをするのかというとこのセクションの後半に説明しています。

  • $succeedDBSet = $stmt->execute();
    クエリを実行します。
    今回はテーブルへのインサートのみでSELECTしているわけではないので、得られる結果は、その実行が成功した場合はtrue, 失敗した場合はfalseの2つです。

  • if ($succeedDBSet) { echo "テーブル名:backhome_messageへの挿入成功\n" } else { ... }
    最後に、テーブルへの挿入が成功した場合は「成功」, 挿入が失敗した場合は「失敗」とターミナルに出力します。特に比較対象のものをセットしない場合は、true or falseの2値かどうかの条件となります。

注意点

もし1つだけテーブルで挿入が失敗した場合、再度エラーを修正してすべてのコードを実行する ことはやめてください !!

なぜなら、成功したテーブルでも再度追加でインサートしてしまうからです。
ですので、成功した処理はコメントアウトして、失敗した文だけ再度実行すればいいです。

複数行選択して、Ctrl + /でコメントアウトできます!

プレースホルダーとSQLインジェクション攻撃

先ほどのように、クエリの部分を": ..."とおいて、後から値を代入[バインド]する手法を"プレースホルダー"といいます。なんでこんな面倒なことをするのかというと、サイバー攻撃を防ぐためです。(ここら辺がユーザーに見えるとかそういうわけではありませんが、勉強がてら)

DBに不正なSQL文を注入して予期せぬ動作を引き起こしてデータを抜き取ったり改ざんしたりする攻撃を"SQLインジェクション攻撃"といいます。

ここでは詳しい説明は端折りますが、その攻撃に有効な対策が、変動する値をあとでバインドするプレースホルダーの仕様なのです。

では実際にテーブルに挿入できたかどうかは、各テーブルにアクセスして更新ボタンを押してみましょう!しっかりインサートできています!

まとめ

今回は、PHP×MySQLということで、どのような手順でデータベースに接続し、どのような手順でクエリをセットして値をインサートするか というのを解説しました。

連想配列で管理するってところが、今回の肝だと思います!

ぜひご活用ください!!

筆者のGitHubリンク

Discussion