SHINYA TECH

公務員(行政職)から34歳でエンジニア転職した人

#15 Web版読書ログサービスの作成 ~ データの登録 ~

f:id:kshinya-tech:20220224052059p:plain

前回、Webページが表示される仕組みを学び、HTMLにて登録ページの骨格を作成しました。

今回は読書ログのデータを登録できるようにします。

(バリデーション処理は次回行います。)

shinya-tech.com

PHPの定義済み変数

PHPで定義されている変数のこと。外部から情報などが取得できる。

$_ENV
  • 環境変数
    • 連想配列として受け取る


$_POST
  • POSTされた情報
    • 連想配列として受け取る


$_GET
  • URLパラメータの情報
    • 連想配列として受け取る


$_SERVER
  • サーバや実行時の環境情報
    • 連想配列として受け取る
    • REQUEST_METHOD : ページにアクセスする際に使用されたリクエストのメソッド名。'GET', 'POST'など。

マジック定数

使われる場所によって値が変化する定数のこと。

__DIR__
  • そのファイルの存在するディレクトリ名

外部ファイルの読み込み方

共通の処理は別ファイルに一箇所にまとめて、そのファイルを読み込んで使用する

require

指定したファイルを読み込む

  • 読み込みが失敗すると処理がそこで中断される
require '<ファイル名>';

require_once

requireとほぼ同じだが、一度しかファイルを読み込まない

  • 悩んだらrequire_onceを使う
  • 何度も外部ファイルを読み込むことは稀なため

データ登録後、リダイレクトするようにする

リダイレクトとは、ユーザを自動的に別のページへ転送する仕組みのこと。

header関数を使ってリダイレクトする。

  • 生のHTTPヘッダを送信する

    • $header : ヘッダ文字列

    header($header)

  • リダイレクトするには Location:ヘッダを付ける

header("Location: <リダイレクト先>");

エラーログを出力するようにする

ログは、障害などが起きたときに何が起きたかを把握できるように、プログラムの実行状況・データ送受信状況などを記憶しておくもの。

ユーザの行動履歴が分かるため、サイトの不具合や不審なアクセスを見つけられる。

PHP・Apacheのログ

ログには主に、アクセスログとエラーログがある。

PHPはエラーログ、Apacheはアクセスログとエラーログを主に出力する。

アクセスログ

  • ブラウザがサーバ(Apache)にリクエストし、それにApacheが応えるごとに記録される。
  • アクセスの履歴を確認するために使う。

エラーログ

  • リクエストの結果がエラーになったものだけが記録される。
  • サーバに問題が起きたら検知するために使う。

エラーログの出力には error_log() を使う

  • エラーメッセージをWebサーバのエラーログに送信する

    • $message : エラーログに記録されるメッセージ

    error_log($message)

  • 例えば、「データベースに接続できません」と記録するなら

error_log("データベースに接続できません");

ログの確認方法

Dockerコンテナ内のログを確認するにはdocker logsコマンドを使う。

docker-composeコマンドでも出力可能であるが、アクセスログとエラーログを分けて出力することができないため、今回はdockerコマンドを用いる。

  • 事前準備 : コンテナ名を確認
docker ps
  • ログを確認したい時(-f : follow。ログの出力を表示し続ける)
docker logs -f <コンテナ名>
  • アクセスログを確認したい時(エラーログを捨てる)
docker logs <コンテナ名> -f 2>/dev/null
  • エラーログを確認したい時
docker logs <コンテナ名> -f 1>/dev/null

書いたコード

ちなみに、ディレクトリの構造は以下のとおり

gyazo.com

データベースへの接続処理は色々な場所で出てくるため、mysqli.phpというファイルを作って、その内容を他のファイルに継承するようにする。

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 'データベースに接続しました' . PHP_EOL;
    } else {
        echo 'Error: データベースに接続できません' . PHP_EOL;
        echo 'Debugging error: ' . mysqli_connect_error() . PHP_EOL;
        exit;
    }
    return $link;
}


上記で共通化した項目をrequire_once __DIR__ . '/lib/mysqli.php';で読み込み、該当箇所は削除。

initialize_reviews_table.php

<?php
// mysqli.phpファイルを読み込む
require_once __DIR__ . '/lib/mysqli.php';

// companiesテーブルが既に存在していたら、削除する関数を定義
function dropTable($link)
{
    $dropTableSql = 'DROP TABLE IF EXISTS reviews';
    $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;
    }
}

// companiesテーブルを作成する関数を定義
function createTable($link)
{
    $createTableSql = <<<EOT
CREATE TABLE reviews (
    id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    author VARCHAR(30),
    status VARCHAR(30),
    score INTEGER,
    summary VARCHAR(500),
    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);


create.php

<?php
// mysqli.phpファイルを読み込む
require_once __DIR__ . '/lib/mysqli.php';

// データベースにデータを登録する関数を定義する
function createReview($link, $review)
{
    $sql = <<<EOT
INSERT INTO reviews (
    title,
    author,
    status,
    score,
    summary
) VALUES (
    "{$review['title']}",
    "{$review['author']}",
    "{$review['status']}",
    "{$review['score']}",
    "{$review['summary']}"
);
EOT;
    $result = mysqli_query($link, $sql);
    // $resultがfalseなら、エラーログを吐き出す
    if (!$result) {
        error_log('Error: fail to create review');
        error_log('Debugging Error: ' . mysqli_error($link));
    }
}

// HTTPメソッドがPOSTだったら
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // POSTされた読書ログ情報を変数に格納する
    $review = [
        'title' => $_POST['title'],
        'author' => $_POST['author'],
        'status' => $_POST['status'],
        'score' => $_POST['score'],
        'summary' => $_POST['summary'],
    ];
    // バリデーションする
    // (バリデーション処理はあとで記述する)

    // データベースに接続する
    $link = dbConnect();

    // データベースにデータを登録する
    createReview($link, $review);

    // データベースとの接続を切断する
    mysqli_close($link);
}

// index.phpへリダイレクト
header("Location: index.php");


ひとまず、リダイレクト先を簡易に作成

index.php

<?php
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>読書ログ一覧ページ</title>
</head>

<body>
    <h1>読書ログ一覧ページ</h1>
</body>

</html>

前回作成した登録ページのファイル(再掲)。 formタグにて、送信先にcreate.phpを指定(action="create.php")

<?php
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>読書ログの登録</title>
</head>

<body>
    <h1>読書ログ</h1>

    <h2>読書ログの登録</h2>

    <form action="create.php" method="POST">
        <div>
            <label for="title">書籍名</label>
            <input type="text" id="title" name="title">
        </div>
        <div>
            <label for="author">著者名</label>
            <input type="text" id="author" name="author">
        </div>
        <div>
            <label>読書状況</label>
            <div>
                <div>
                    <input type="radio" name="status" id="not_yet" value="未読">
                    <label for="not_yet">未読</label>
                </div>
                <div>
                    <input type="radio" name="status" id="reading" value="読んでいる">
                    <label for="reading">読んでいる</label>
                </div>
                <div>
                    <input type="radio" name="status" id="finish" value="読了">
                    <label for="finish">読了</label>
                </div>
            </div>
        </div>
        <div>
            <label for="score">評価(5点満点の整数)</label>
            <input type="number" id="score" name="score" min="1" max="5">
        </div>
        <div>
            <label for="summary">感想</label>
            <textarea name="summary" id="summary" cols="20" rows="3"></textarea>
        </div>
        <button type="submit">登録する</button>
    </form>
</body>

</html>

備忘録(詰まった点)

パスを指定する箇所において、カレントディレクトリからのパス(相対パス)が間違っていてエラーが出た。

エラーにNo such file or directoryが記載されていたら、パスの指定誤りを疑う。

[23-Feb-2022 19:37:04 Asia/Tokyo] PHP Warning:  require_once(/var/www/html/lib/mysqli.php): failed to open stream: No such file or directory in /var/www/html/create.php on line 3

Warning: require_once(/var/www/html/lib/mysqli.php): failed to open stream: No such file or directory in /var/www/html/create.php on line 3