前回、ターミナル上で動作するタスク管理ツールを作成しました。
今回はこれまで(#12〜#24)の復習を兼ねて、WEB版のタスク管理ツールの作成を行いました。
実装した内容はタスクの登録とタスク一覧の表示のみ。
削除及び修正は実装していません。
完成後の画面
タスク登録画面
タスク一覧画面
ディレクトリ構成
tasks ├── create.php ├── index.php ├── initialize_tasks_table.php ├── lib │ ├── escape.php │ └── mysqli.php ├── new.php └── views ├── index.php ├── layout.php └── new.php
各コード
views/index.php
<a href="new.php" class="btn btn-primary mb-4">タスクを登録する</a> <main> <?php if (count($tasks) > 0) : ?> <table class="table table-hover"> <thead class="thead-dark text-center"> <tr> <th scope="col">タスク名</th> <th scope="col">期限</th> <th scope="col">進捗</th> <th scope="col">備考</th> </tr> </thead> <?php foreach ($tasks as $task) : ?> <tbody> <tr> <td scope="row"><?php echo escape($task['name']); ?></td> <td class="text-center"><?php echo escape(date('Y/m/d', strtotime(($task['limit_date'])))); ?></td> <td class="text-center"><?php echo escape($task['status']); ?></td> <td><?php echo nl2br(escape($task['remarks'])); ?></td> </tr> </tbody> <?php endforeach; ?> </table> <?php else : ?> <p>タスクが登録されていません。</p> <?php endif; ?> </main>
views/layout.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="../stylesheets/css/app.css"> <!-- $titleはlayout.phpの呼び出し元で定義 --> <title><?php echo $title; ?></title> </head> <body> <header class="navbar shadow-sm p-3 mb-4 bg-white"> <h1 class="h2"> <a class="text-decoration-none text-reset" href="index.php">タスク管理ツール</a> </h1> </header> <div class="container"> <!-- $contentはlayout.phpの呼び出し元で定義 --> <?php include $content; ?> </div> </body> </html>
views/new.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="../stylesheets/css/app.css"> <title>タスクリスト</title> </head> <body> <header class="navbar shadow-sm p-3 mb-4 bg-white"> <h1 class="h2"> <a class="text-decoration-none text-reset" href="index.php">タスクリスト</a> </h1> </header> <div class="container text-dark mb-4"> <h2 class="h3">登録画面</h2> <form action="create.php" method="POST"> <!-- バリデーションエラーを表示させる --> <?php if (count($errors)) : ?> <ul class="text-danger"> <?php foreach ($errors as $error) : ?> <li><?php echo $error; ?> </li> <?php endforeach; ?> </ul> <?php endif; ?> <div class="form-group"> <label for="name">タスク名</label> <input type="text" class="form-control" id="name" name="name" value="<?php echo $task['name'] ?>"> </div> <div class="form-group"> <label for="limit_date">期限</label> <input type="date" class="form-control" id="limit_date" name="limit_date" value="<?php echo $task['limit_date'] ?>"> </div> <div class="form-group"> <label>進捗</label> <div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="status" id="not_yet" value="未" <?php echo ($task['status'] === '未') ? 'checked' : ''; ?>> <label class="form-check-label" for="not_yet">未</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="status" id="doing" value="着手" <?php echo ($task['status'] === '着手') ? 'checked' : ''; ?>> <label class="form-check-label" for="doing">着手</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="status" id="finish" value="完了" <?php echo ($task['status'] === '完了') ? 'checked' : ''; ?>> <label class="form-check-label" for="finish">完了</label> </div> </div> </div> <div class="form-group"> <label for="remarks">備考</label> <textarea class="form-control" name="remarks" id="remarks" cols="30" rows="2"></textarea> </div> <button type="submit" class="btn btn-primary">登録する</button> </form> </div> </body> </html>
lib/escape.php
<?php function escape($string) { return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); }
lib/mysqli.php
<?php // Composerのautoloadを読み込む。 // これにより各ライブラリをPHPファイル内で呼び出せるようになる // 後で理解すればOK.composerを使用するときは書いておく。 require __DIR__ . '/../../vendor/autoload.php'; // データベースとの接続の関数を定義 function dbConnect() { // 以下の記述はオブジェクト指向を学んでから理解すればOK // 環境変数をPHPファイルに読み込む $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../..'); $dotenv->load(); $dbHost = $_ENV['DB_HOST']; $dbUsername = $_ENV['DB_USERNAME']; $dbPassword = $_ENV['DB_PASSWORD']; $dbDatabase = $_ENV['DB_DATABASE']; $link = mysqli_connect($dbHost, $dbUsername, $dbPassword, $dbDatabase); if (!$link) { echo 'Error: データベースに接続できません' . PHP_EOL; echo 'Debugging error: ' . mysqli_connect_error() . PHP_EOL; exit; } return $link; }
create.php
<?php // mysqli.phpファイルを読み込む require_once __DIR__ . '/lib/mysqli.php'; // データベースにデータを登録する関数を定義する function createTask($link, $task) { $sql = <<<EOT INSERT INTO tasks ( name, limit_date, status, remarks ) VALUES ( "{$task['name']}", "{$task['limit_date']}", "{$task['status']}", "{$task['remarks']}" ); EOT; $result = mysqli_query($link, $sql); // $resultがfalseなら、エラーログを吐き出す if (!$result) { error_log('Error: fail to create task'); error_log('Debugging Error: ' . mysqli_error($link)); } } // バリデーション関数を定義 function validate($task) { // エラーは複数発生しうるので配列で格納する $errors = []; // タスク名についてのバリデーション // 空欄及び文字列の最大値を超えていないかチェック if (!strlen($task['name'])) { $errors['name'] = 'タスク名を入力してください'; } elseif (mb_strlen($task['name']) > 255) { $errors['name'] = 'タスク名は255文字以内で入力してください'; } // 期限(日付)についてのバリデーション // 条件1 : 空欄の場合にエラー // 条件2 : 日付の形式(YYYY-MM-DD)でない場合にエラー // 条件3 : ありえない日付の場合にエラー // 条件3でcheckdate()を使用するため、'-'で日付を区切って配列として格納 // 例)2020-10-8 -> 2020 10 8 に分割する $dates = explode('-', $task['limit_date']); if (!strlen($task['limit_date'])) { $errors['limit_date'] = '期限を入力してください'; } elseif (count($dates) !== 3) { $errors['limit_date'] = '期限を正しい形式で入力してください'; // checkdateの引数は(月, 日, 年)とする必要があるため、以下のようにする } elseif (!checkdate($dates[1], $dates[2], $dates[0])) { $errors['limit_date'] = '期限を正しい日付で入力してください'; } // 進捗についてのバリデーション if (!strlen($task['status'])) { $errors['status'] = '進捗を入力してください'; } elseif (!in_array($task['status'], ['未', '着手', '完了'])) { $errors['status'] = '進捗は「未」、「着手」、「完了」のいずれかを入力してください'; } // 備考についてのバリデーション if (mb_strlen($task['remarks']) > 255) { $errors['remarks'] = '備考は255文字以内で入力してください'; } return $errors; } // HTTPメソッドがPOSTだったら if ($_SERVER['REQUEST_METHOD'] === 'POST') { // POSTされたタスク情報を変数に格納する // なお、status(進捗)はラジオボタンのため、チェックされていないとデータが送信されず、エラーとなる // そのため、以下を追記している $status = ''; if (array_key_exists('status', $_POST)) { $status = $_POST['status']; } $task = [ 'name' => $_POST['name'], 'limit_date' => $_POST['limit_date'], 'status' => $status, 'remarks' => $_POST['remarks'], ]; // バリデーションする $errors = validate($task); // バリデーションエラーがなければ if (!count($errors)) { // データベースに接続する $link = dbConnect(); // データベースにデータを登録する createTask($link, $task); // データベースとの接続を切断する mysqli_close($link); // index.phpへリダイレクト header("Location: index.php"); } // もしエラーがあれば、以下に処理を続行 include 'views/new.php'; }
index.php
<?php require_once __DIR__ . '/lib/escape.php'; require_once __DIR__ . '/lib/mysqli.php'; function listTasks($link) { $tasks = []; $sql = 'SELECT name, limit_date, status, remarks FROM tasks'; $results = mysqli_query($link, $sql); while ($task = mysqli_fetch_assoc($results)) { $tasks[] = $task; } mysqli_free_result($results); return $tasks; } $link = dbConnect(); $tasks = listTasks($link); // layout.php内の変数に渡す $title = 'タスク一覧'; $content = __DIR__ . '/views/index.php'; // layout.phpを取り込む include __DIR__ . '/views/layout.php';
new.php
<?php // $errorsが未定義だとエラーとなるため、初期化 $errors = []; // $taskが未定義だとエラーとなるため、初期化 $task = [ 'name' => '', 'limit_date' => '', 'status' => '', 'remarks' => '', ]; // views/new.phpを取り込む include 'views/new.php';
initialize_tasks_table.php
<?php // mysqli.phpファイルを読み込む require_once __DIR__ . '/lib/mysqli.php'; // tasksテーブルが既に存在していたら、削除する関数を定義 function dropTable($link) { $dropTableSql = 'DROP TABLE IF EXISTS tasks'; $result = mysqli_query($link, $dropTableSql); if ($result) { echo 'テーブルを削除しました' . PHP_EOL; } else { echo 'Error: テーブルの削除に失敗しました' . PHP_EOL; echo 'Debugging Error: ' . mysqli_error($link) . PHP_EOL . PHP_EOL; } } // tasksテーブルを作成する関数を定義 function createTable($link) { $createTableSql = <<<EOT CREATE TABLE tasks ( id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), limit_date DATETIME, status VARCHAR(30), remarks VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) DEFAULT CHARACTER SET = utf8mb4; EOT; $result = mysqli_query($link, $createTableSql); if ($result) { echo 'テーブルを作成しました' . PHP_EOL; } else { echo 'Error: テーブルの作成に失敗しました' . PHP_EOL; echo 'Debugging Error: ' . mysqli_error($link) . PHP_EOL . PHP_EOL; } } // データベースとの接続 -> テーブルの初期化(削除・作成)-> データベースの切断 を実行 $link = dbConnect(); dropTable($link); createTable($link); mysqli_close($link);