PHPで作る!シンプルなTodoリスト – データベース連携編

前回の記事では、セッションでの一時的なデータ保存を実装しました。今回は、実践的なデータベース管理へとステップアップしましょう。この記事では、PHPとMySQLを使ってTodoリストを改良し、永続的にデータを保存できるようにします。

データベースの作成

まずXAMPP Control Panelを開き、ApacheとMySQLをStartします。

次にMySQLの行にあるAdminをクリックします。

するとブラウザでphpmyadminが開きます。

phpmyadminとは

phpMyAdminはMySQLをブラウザ上で簡単に扱えるツールです。データベースやテーブルの編集をGUI(マウスでの操作)で行うことができます。

サイドバーの「新規作成」からデータベースを作成します。今回はtodo_dbという名前にします。

テーブルの作成

データベースを作成したら次にテーブルを作成するのですが、その前にTodoを構成するデータを洗い出していきます。

Todoを構成する要素、およびデータの形式
  • id – 文字列
  • text – 文字列
  • 作成日時 – 日時

テーブル名は「todos」、カラム数は3(id・Todo・作成日時)です。

テーブルを作成すると、カラムの詳細を設定する画面に移ります。下記画像のように変更していきます。色のついている箇所です)

Todoを構成する要素、およびMySQLでのデータ型
  • id – VARCHAR(13)
    • 文字列を格納できます
    • カッコ内の数字は最大文字数を表します
    • IDなどの文字列型の識別子によく使用されます
  • text – TEXT
    • 長い文字列データを格納できます
    • メモや説明文など、長さが決まっていない文字列データに向いています
  • created_at – DATETIME
    • 日付と時刻を格納できます
    • ‘YYYY-MM-DD HH:MM:SS’ の形式で保存されます
    • current_timestampをデフォルト値とすることで、レコード作成時の時刻が自動的に設定されます
    • タイムスタンプとして使用され、データの作成・更新時刻の管理に適しています

上記入力後、「保存する」を押すとテーブルが完成します。これでデータを保存できるようになりました!

php側の処理

phpのコーディングに移ります。config.phpを新たに作成して、データベースに関する処理を記述していきます。

<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'todo_db');
define('DB_USER', 'root');
define('DB_PASS', '');

try {
    $pdo = new PDO(
        "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME,
        DB_USER,
        DB_PASS
    );
} catch (PDOException $e) {
    exit('データベース接続に失敗しました。' . $e->getMessage());
}

定数の定義

define('DB_HOST', 'localhost');
define('DB_NAME', 'todo_db');
define('DB_USER', 'root');
define('DB_PASS', '');

まずdefine構文で定数を宣言しています。

Manual – define関数
define(定数名, 値);

指定した名前で定数を作成します。定数は変更できず、内容が変化しないことが保証される名前が必要なときに使用されます。

上から順に、ホスト名、データベース名、ユーザー名、パスワードと定義しています。

データベースとの接続

次のtry節ではデータベースと接続しています。PDOというオブジェクトを使うことで、DBとの接続をシンプルに行えます。

try {
    $pdo = new PDO(
        "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
        DB_USER,
        DB_PASS
    );
} catch (PDOException $e) {
    exit('データベース接続に失敗しました。' . $e->getMessage());
}
Manual – PDO

pdoはmysqlのほか、postgreSQLやSQLiteなどのデータベースと接続できます。データベースが何であるかにかかわらず、同じ関数を使用してクエリの発行やデータの取得が行えます。

new PDO(dsn, ユーザー名, パスワード);

dsn(Data Source Name)にはデータベースに接続するために必要な情報(データベースの種類、ホスト名、データベース名、文字セットなど)が記載されています。

今回使っているdsnは以下のようになります。
“mysql:host=localhost; dbname=todo_db; charset=utf8mb4”

  • mysql: [データベース管理システム]
  • host=localhost [ホスト名]
  • dbname=todo_db [データベース名]
  • charset=utf8mb4 [文字セット]

フロントエンドとデータベースの連携

先ほど見たconfig.phpファイルを読み込んで、データベースに接続します。

<?php
require_once 'config.php';

// Todo追加
function addTodo($text)
{
    global $pdo;
    $id = uniqid();
    $stmt = $pdo->prepare("INSERT INTO todos (id, text) VALUES (?, ?)");
    return $stmt->execute([$id, $text]);
}

// Todo一覧取得
function getTodos()
{
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM todos ORDER BY created_at DESC");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// Todo削除
function deleteTodo($id)
{
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM todos WHERE id = ?");
    return $stmt->execute([$id]);
}

// フォーム処理
if (isset($_POST['add']) && !empty($_POST['todo'])) {
    addTodo($_POST['todo']);
}

if (isset($_POST['delete']) && !empty($_POST['id'])) {
    deleteTodo($_POST['id']);
}

// Todo一覧取得
$todos = getTodos();
?>

<!DOCTYPE html>
<html>

<head>
    <title>My Todo List</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="container">
        <h1>My Todo List</h1>

        <form method="POST" class="add-form">
            <input type="text" name="todo" placeholder="新しいタスクを入力" required>
            <button type="submit" name="add">追加</button>
        </form>

        <div class="todo-list">
            <?php foreach ($todos as $todo): ?>
                <div class="todo-item">
                    <div class="todo-content">
                        <div class="todo-text"><?php echo htmlspecialchars($todo['text']); ?></div>
                    </div>
                    <form method="POST" class="form-inline">
                        <input type="hidden" name="id" value="<?php echo $todo['id']; ?>">
                        <button type="submit" name="delete" class="delete-btn">delete</button>
                    </form>
                </div>
            <?php endforeach; ?>
        </div>
    </div>
</body>

</html>

Todo追加機能

function addTodo($text)
{
    global $pdo;
    $id = uniqid();
    $stmt = $pdo->prepare("INSERT INTO todos (id, text) VALUES (?, ?)");
    return $stmt->execute([$id, $text]);
}

この関数で新しいTodoをデータベースに追加します。
まず関数の中で外部の$pdo変数を使うために、global宣言をしています。

(※今回はシンプルな解説にするためにglobalを使用しています。基本的には、管理が難しくなるためglobal宣言は避けた方が良いです。)

次に、PDOのprepare関数によってSQL文をセットしています。IDとTodoをINSERT文に安全に挿入するために、INSERT文の中には疑問符 (?) パラメータマークを使っています。最後のexecute関数に、疑問符パラメータの数と同じ要素数の配列を引数として渡します。配列の中身の順番と、疑問符パラメータの順番が一致するようにしましょう。

Manual – prepare関数

SQL文に変数を挿入するなら、そのまま結合なり展開なりしてしまえば楽なのでは?と思いますが、そうすると攻撃者が悪意のあるSQL文を挿入、実行できてしまいます。この攻撃をSQLインジェクションと言います。

SQL文をセットする際に変数を挿入したいときは、PDOのprepare関数を使います。

$pdo->prepare("SQL文")

prepare関数でSQL文をセットした後、execute関数により後から変数を代入しています。この一連の流れはプリペアドステートメントと呼ばれ、SQL文の中に安全に変数を挿入できます。このひと手間を怠るとセキュリティホールが生まれることがありますので、ユーザの入力値をSQL文に使用する場合は必ず行ってください。

Todo一覧取得機能

function getTodos() {
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM todos ORDER BY created_at DESC");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

この関数でデータベースから全てのTodoを取得しています。
Todoの取得には変数を使用しないため、PDOのprepare関数ではなくquery関数でSQL文をセットします。created_atの降順(新しい順)で並び替えしています。

最後に、$stmtのfetchAll関数によりTodoを全て取得して、returnしています。引数には、データの取得形式を指定します。
PDO::FETCH_ASSOCは、[カラム名1 => 値1, カラム名2 => 値2, …]のような連想配列でデータを取得します。

$todos = getTodos();

今回は$todos変数にTodoのリストを代入しています。

フォーム処理部分

if (isset($_POST['add']) && !empty($_POST['todo'])) {
    addTodo($_POST['todo']);
}

if (isset($_POST['delete']) && !empty($_POST['id'])) {
    deleteTodo($_POST['id']);
}

2つ条件分岐を記述しています。

1つ目のif文はボタンの押下判定とテキストの存在判定を行い、Todoの追加処理をしています。2つ目のif文はボタンの押下判定とテキストの存在判定を行い、削除処理をしています。

フロントエンド側の処理

フロントエンド側は前回とほぼ変わっていません。一点、前回はセッションからTodoリストを取得、foreach文で回していたので、そこを$todos変数からの取得に変更しています。

<?php foreach ($todos as $todo): ?>
    // 略
<?php endforeach; ?>

これでデータベースとの連携が完了し、Todoリストを永続的に保存できるようになりました。お疲れさまでした!

コメント