TODOリストを作ってみています。
ピンポイントの「TODOリストの作り方」記事は見ずに、できるかぎり調べて書いています。
前回はDBのテーブルの表示までできました。
次は「登録」ボタンを押してPOST送信したデータをテーブルに追加します。
4. SQL文でレコードの追加
いきなりどうすればいいのか分からないので調べました。
formタグから入力したデータをデータベースに登録 | CBC | Webデザインやプログラミングの基礎学習
【プログラミング構築】入力したデータをPOST送信でデータベースに登録しよう | 株式会社LIG
1つ目の記事を見ると、接続部分と追加部分、それぞれでtry/catch で囲っています。
2つ目の記事では追加部分はそのまま書かれています(クラスのメソッドになっている...複雑)。
ひとまずtry/catch で囲わずに書いてみます。
一旦一覧表示は非表示にします。
SQL INSERT文
INSERT文もすっかり忘れました。
INSERT INTO テーブル名 (列名1, 列名2,...) VALUES (値1, 値2,...);
こちらに倣って書こうと思いますが、VALUES
の中は「パラメータをつかってバインドしている」ようです。
VALUES ($POST_['day-r'], $POST_['priority'],...)
と、直接VALUES
の中に POST送信のデータを入れるのは良くないということなのでしょう。
ここはまず固定値を入れてINSERTしてみます。
サーバーエラーになる
POST送信のタイミングでNSERTしてみましたが、500のエラーが返ってきました。
エラーになったコードです。
//TODO追加 if (!empty($_POST['content'])) { $sql = 'INSERT INTO todo(id, registration-date, priority, content, note, period-date, category) VALUES(NULL, 2021-06-01, "中", "病院に行く", "10時に予約", 2021-06-05, "外出")'; $stmt = $pdo->query($sql); $stmt->execute(); }
そこでtry/catchを入れてみます。
すると以下のエラーが返ってきました。
Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-date, priority, content, note, period-date, category) VALUES(NULL, 2021-06-01, ' at line 1
貴重な手がかり!
どうやらSQLの構文ミスのようです。
「'id, registration-date...'付近」ではなく「'-date, ..' 付近」の構文を確認してください、というのが引っかかります。
そもそもハイフン -
をカラム名(フィールド名)に使えるのかどうか調べました。
データベース名にハイフン(-)入れたらSQL文でエラーになった 【特殊文字】 - Qiita
DB名やカラム名では極力ハイフンを使わない方がよいですね。
ハイフンを使う場合はバッククオート「`」で囲むエスケープ処理が必要だそうです。
また「table」などの予約語を使う場合もエスケープ処理が必要です。
なので、上記のSQL文はregistration-date
と period-date
をバッククオートで囲みました。
すると、無事にDBに追加されていました!
(ページ読み込みを何回かしたためか、たくさん入っている)
プレースホルダでバインドする
VALUEに固定値を入れていたので、パラメータを使ってPOSTデータをINSERTします。
バインドとは
バインド (bind)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
https://wa3.i-3-i.info/word12448.html
バインドとは関連付ける、紐付けるという意味です。
先程のLIGさんの記事では
バインドとは、上記のようにプレースホルダに変数を割り当てる処理のことをいいます。
また、今回のようにPOSTデータを受け取り、データベースに登録を行う場合に、insert文に直接取得した変数を与える形だと「SQLインジェクション」というデータベースの不正操作を引き起こすことになってしまいますが、PDOを用いてプレースホルダを利用して取得変数を組み込んだり、バインドする際に型を指定することでSQLインジェクション対策を含むことが容易にできます。
とあります。
具体的には
$stmt->bindParam(':name', $_POST['name'], PDO::PARAM_STR);
ですね。
プレースホルダが「:name」で変数が「$_POST['name']」ですね。
プレースホルダとは
プレースホルダは「:で始まる代替文字列」の部分です。
引き続きLIGさんの記事では
プレースホルダとは実際の内容を後から挿入するために仮で確保している場所
とあります。
プレースホルダとは何か?SQLインジェクション攻撃を回避せよ!
こちらでもSQLインジェクションについて具体的に説明されています。
とあるので、以下の書き方を必ずするということですね。
バインドパラメータ、バインド変数は文字通りバインドした変数ですね。多分。
記事をいろいろ見ていると表現もいろいろで、考えると混乱しますが、きっとそういう解釈でいいはずです。
データを入れる前の事前準備のprepareメソッドを使って以下のようにしました。
☆idはオートインクリメントなので、飛ばしました。
if (!empty($_POST['content'])) { $sql = 'INSERT INTO todo(`registration-date`, priority, content, note, `period-date`, category) VALUES(:registration_date, :priority, :content, :note, :period_date, :category)'; $stmt = $pdo->prepare($sql); $stmt->bindParam(':registration_date', $_POST['day-r'], PDO::PARAM_STR); $stmt->bindParam(':priority', $_POST['priority'], PDO::PARAM_STR); $stmt->bindParam(':content', $_POST['content'], PDO::PARAM_STR); $stmt->bindParam(':note', $_POST['note'], PDO::PARAM_STR); $stmt->bindParam(':period_date', $_POST['day-p'], PDO::PARAM_STR); $stmt->bindParam(':category', $_POST['category'], PDO::PARAM_STR); $stmt->execute(); }
登録時に日付のフォーマット(yyyy-mm-dd ←正式な書き方ではないかも)に気をつけて入力、送信したら無事にINSERTできました◎
5. TODO登録後の一覧表示
一覧表示は以前にできていたので、今度は新しく登録したtodoを含めてtodoリストを表示します。
DOCTYPEタグの前にいろいろとPHPで処理を書いていますが、try/catch の中にまとめて入れてみました。
try { // DBに接続 $dsn = 'mysql:dbname=practice;host=localhost;charset=utf8mb4'; $username = 'root'; $password = 'root'; $driver_options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ]; $pdo = new PDO($dsn, $username, $password, $driver_options); //TODO追加 if (!empty($_POST['content'])) { $sql = 'INSERT INTO todo(`registration-date`, priority, content, note, `period-date`, category) VALUES(:registration_date, :priority, :content, :note, :period_date, :category)'; $stmt = $pdo->prepare($sql); $stmt->bindParam(':registration_date', $_POST['day-r'], PDO::PARAM_STR); $stmt->bindParam(':priority', $_POST['priority'], PDO::PARAM_STR); $stmt->bindParam(':content', $_POST['content'], PDO::PARAM_STR); $stmt->bindParam(':note', $_POST['note'], PDO::PARAM_STR); $stmt->bindParam(':period_date', $_POST['day-p'], PDO::PARAM_STR); $stmt->bindParam(':category', $_POST['category'], PDO::PARAM_STR); $stmt->execute(); } // テーブル情報取得 $stmt = $pdo->query('SELECT * FROM todo'); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch { header('Content-Type: text/plain; charset=UTF-8', true, 404); exit($e->getMessage()); }
これでうまく動きました!
同じ $stmt
を使い回していいものなのか、catch
の中身も果たして妥当なのか(エラーはログに書き出すはず)まだ怪しいですがひとまず。
次回へ続けたい...
追加ときたら削除まではやっておきたいです。
ただ削除ボタンは作らないといけないので、ボタンを付けたら次へ進もうと思います。
答え合わせまではやりたいです。