引き続きCRUD処理の開発を進めていきましょう。次はカテゴリー更新処理を実装します。

ここでは以下の手順にしたがってプログラムを作成します。

  1. カテゴリー詳細画面の修正 - show.php
  2. カテゴリー更新画面の作成 - edit.php
  3. カテゴリー更新処理の実装 - update.php

1. カテゴリー詳細画面の修正 - show.php

show.php ファイルのHTMLコードにカテゴリー更新画面へのリンクを追加します。

...省略
<body>
  <h3>Categories - Show</h3>
  <hr>
  ID: <?= htmlspecialchars($category["id"]) ?><br>
  TITLE: <?= htmlspecialchars($category["title"]) ?><br>
  <a href="edit.php?id=<?= htmlspecialchars($category['id']) ?>">EDIT</a> 
  <hr>
  <a href="index.php">BACK</a>
</body>
</html>

ここでは a タグによって EDIT リンクを追加しています。リンク先URLにはクエリパラメータ( id )を指定しています。クエリパラメータ ?id= の値部分にカテゴリーIDを指定しています。

2. カテゴリー更新画面の作成 - edit.php

続いてカテゴリー更新画面を表示する edit.php ファイルを作成します。

<?php
$id = (string)filter_input(INPUT_GET, "id");
if ($id === "") {
    error_log("Validate: id is required.");
    header("Location: error.php");
    exit();
}
if (filter_var($id, FILTER_VALIDATE_INT) === false) {
    error_log("Validate: id is not int.");
    header("Location: error.php");
    exit();
}

try {
    $dsn = "mysql:host=localhost;dbname=eldb;charset=utf8mb4";
    $username = "root";
    $password = "admin";
    $options = [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false
    ];
    $pdo = new PDO($dsn, $username, $password);

    $sql = "select id, title from categories where id = :id";
    $ps = $pdo->prepare($sql);
    $ps->bindValue(":id", $id, PDO::PARAM_INT);
    $ps->execute();
    $category = $ps->fetch();
    if ($category === false) {
        error_log("Invalid id. $id");
        header("Location: error.php");
        exit();
    }
} catch (PDOException $e) {
    error_log("PDOException: " . $e->getMessage());
    header("Location: error.php");
    exit();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>PHP DB</title>
</head>
<body>
  <h3>Categories - Edit</h3>
  <hr>
  <form action="update.php" method="post">
    <input type="hidden" name="id" value="<?= htmlspecialchars($category['id']) ?>">
    ID: <?= htmlspecialchars($category['id']) ?><br>
    TITLE: <input type="text" name="title" value="<?= htmlspecialchars($category['title']) ?>"><br>
    <button type="submit">UPDATE</button>
  </form>
  <hr>
  <a href="index.php">BACK</a>
</body>
</html>

少し長いコードになりますが、落ち着いて見ると以前に作成したカテゴリー詳細画面である show.php とよく似ていることに気づくでしょう。プログラムの先頭部分からデータベースに接続して select 文を実行するまでの一連の処理は同じです。

カテゴリー詳細画面と異なるのは、HTMLコードの出力部分です。カテゴリー更新画面である edit.php ファイルは form タグを使って入力フォームを定義しています。

  <form action="update.php" method="post">
    <input type="hidden" name="id" value="<?= htmlspecialchars($category['id']) ?>">
    ID: <?= htmlspecialchars($category['id']) ?><br>
    TITLE: <input type="text" name="title" value="<?= htmlspecialchars($category['title']) ?>"><br>
    <button type="submit">UPDATE</button>
  </form>

この入力フォームは、カテゴリー作成画面と同様に IDTITLE の2つの項目を表示します。ただしIDはテキストボックスではありません。またタイトルはテキストボックスで表示しますが value 属性を指定することでテキストボックスに初期値を表示しています。

入力フォームの中にはもうひとつ input タグがあります。

    <input type="hidden" name="id" value="<?= htmlspecialchars($category['id']) ?>">

上記の input タグは type 属性の値が hidden となっています。input タグは type 属性に hidden を指定すると隠し項目として機能します。つまり、画面上には表示されない項目になりますが、リクエストパラメータとしては値を送信する、という具合です。

このプログラムではID項目はテキストボックスではない、という点に注意してください。ID項目はユーザの入力を許可しないので、TITLE項目のようにリクエストパラメータとしてサーバに送信されません。それでは後のPHPプログラムで困るので、隠し項目(<input type="hidden>)を定義してリクエストパラメータに ID項目を含むように実装しています。

3. カテゴリー更新処理の実装 - update.php

さいごにカテゴリー更新画面から送信されるリクエストを処理する update.php ファイルを作成します。

<?php
$id = (string)filter_input(INPUT_POST, "id");
if ($id === "") {
    error_log("Validate: id is required.");
    header("Location: error.php");
    exit();
}
if (filter_var($id, FILTER_VALIDATE_INT) === false) {
    error_log("Validate: id is not int.");
    header("Location: error.php");
    exit();
}

$title = (string)filter_input(INPUT_POST, "title");
if ($title === "") {
    error_log("Validate: title is required.");
    header("Location: error.php");
    exit();
}
if (mb_strlen($title) > 255) {
    error_log("Validate: title length > 255");
    header("Location: error.php");
    exit();
}

try {
    $dsn = "mysql:host=localhost;dbname=eldb;charset=utf8mb4";
    $username = "root";
    $password = "admin";
    $options = [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false
    ];
    $pdo = new PDO($dsn, $username, $password);

    $sql = "update categories set title = :title where id = :id";
    $ps = $pdo->prepare($sql);
    $ps->bindValue(":title", $title, PDO::PARAM_STR);
    $ps->bindValue(":id", $id, PDO::PARAM_INT);
    $ps->execute();

    header("Location: index.php");
} catch (PDOException $e) {
    error_log("PDOException: " . $e->getMessage());
    header("Location: error.php");
}

プログラムが少し長くなりましたが、これまでに作成してきたプログラムとよく似ているのがわかります。

このプログラムでは先頭部分で以下の仕様に従って入力チェックを実装しています。

項目名 パラメータ名 入力チェック
カテゴリーID id 必須、整数型
タイトル title 必須、最大255文字まで
<?php
$id = (string)filter_input(INPUT_POST, "id");
if ($id === "") {
    error_log("Validate: id is required.");
    header("Location: error.php");
    exit();
}
if (filter_var($id, FILTER_VALIDATE_INT) === false) {
    error_log("Validate: id is not int.");
    header("Location: error.php");
    exit();
}

$title = (string)filter_input(INPUT_POST, "title");
if ($title === "") {
    error_log("Validate: title is required.");
    header("Location: error.php");
    exit();
}
if (mb_strlen($title) > 255) {
    error_log("Validate: title length > 255");
    header("Location: error.php");
    exit();
}

入力チェック部分はカテゴリー作成画面と同じです。隠し項目として送信されたカテゴリーIDとタイトルについて入力チェックを実装しています。

次にデータベースへの接続部分を見ていきましょう。

try {
    $dsn = "mysql:host=localhost;dbname=eldb;charset=utf8mb4";
    $username = "root";
    $password = "admin";
    $options = [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false
    ];
    $pdo = new PDO($dsn, $username, $password);

    $sql = "update categories set title = :title where id = :id";
    $ps = $pdo->prepare($sql);
    $ps->bindValue(":title", $title, PDO::PARAM_STR);
    $ps->bindValue(":id", $id, PDO::PARAM_INT);
    $ps->execute();

    header("Location: index.php");
} catch (PDOException $e) {
    error_log("PDOException: " . $e->getMessage());
    header("Location: error.php");
}

こちらもこれまでに作成してきたコードとよく似ています。先頭部分ではPDOインスタンスを生成してMySQLデータベースに接続しています。それからプリペアドステートメント を定義して、リクエストパラメータをプレースホルダにバインドし、update 文を実行しています。今回は既存レコードの更新処理を行うので update 文を実行します。

update 文の実行が終わったら、header 関数を使って index.php にリダイレクトするように実装しています。

データベースとのやりとりで例外が発生した場合は catch ブロックの処理が実行されます。ここでは error_log 関数を使ってエラーログを出力して、エラー画面にリダイレクトするように実装しています。

動作確認

それではビルトインWebサーバを起動して、ブラウザから実行結果を確認してみましょう。

コマンドラインからビルトインWebサーバを起動します。

$ php -S localhost:8080

ブラウザから以下のURLにアクセスします。

https://〜.vfs.cloud9.ap-northeast-1.amazonaws.com/categories/index.php

上記のようにカテゴリー一覧画面が表示されることを確認します。表示されたテーブルの中から任意のレコードの SHOW リンクをクリックします。

上記のようにカテゴリー詳細画面に選択したレコードが表示されていることを確認できるでしょう。また新たに追加した EDIT リンクも表示されているのがわかります。EDIT リンクをクリックするとカテゴリー更新画面が表示されます。

表示された画面でそれからTITLEを更新してみましょう。

それから UPDATE ボタンをクリックします。

上記のようにカテゴリー一覧画面にリダイレクトされると、更新したカテゴリーレコードを確認できるでしょう。

ターミナルからMySQLに接続して categories テーブルのレコードを確認すると、以前のレコードが更新されていることを確認できます。