読書ログアプリケーションの作成(独学エンジニア レッスン6より)

# 目標
<strong>今回やること</strong>
* Webアプリケーションを作成する

<strong>タスクばらし<strong/>
* 必須知識
* 設計・テーブル作成
* [登録ページ] HTMLで表示する
* [登録ページ] 読書ログを登録する
* [登録ページ] CSSでスタイルを整える
* [トップページ] HTMLで表示する
* [トップページ] 読書ログの一覧を表示する
* [トップページ] CSSでスタイルを整える

# 必須知識
<strong>目的</strong>
* PHPで開発するにあたって必須な知識を学ぶ

<strong>開発の基本順序</strong>
* 設計→テーブル作成→HTML→ロジック処理→CSS

<strong>Composer</strong>
* 現状の課題
機密データがソースコードにベタ書き
ソースコードを誰かに見られたら機密データが漏洩し、データが流出する

* 解決策
ライブラリを使用して、機密データを別途管理できる仕組みを入れよう

* ライブラリ
使いたい部品が入っている道具箱
特定の機能を提供するコードをひとまとめにしたもの

ex)機密データ管理ライブラリ、ログ管理ライブラリ

* PHPでライブラリを導入したい時はComposerを使うと便利。
ComposerはPHPの依存管理ツール

何も使わないと、
1, 一つのライブラリを使うのに他のライブラリをいちいちインストールしてこないといけない
2, チーム開発時に人によって使用しているライブラリのバージョンがバラバラ。それが起因でエラーになる。

Composerを使うと、
1, プロジェクトが使用しているライブラリとそのバージョンを統一できる
2, ライブラリが依存しているライブラリもセットでインストールできる
3, プロジェクトごとにライブラリを管理できる

composer.jsonとcomposer.lockファイルで依存するライブラリを定義する

* composer.json
プロジェクトで使用するライブラリを一覧にしたもの

* composer.lock
実際にどのライブラリのどのバージョンをダウンロードしたかをひとまとめにしたもの

1, coposer installを実行すると、インストールしたライブラリとバージョン情報が書き出される
2, composer.lockがある状態でcomposer installすると、composer.lockに記されたバージョンのライブラリがインストールされる

よって、チーム内で同じバージョンのライブラリを使える

コマンド流れ

* init
対話形式でcomposer.jsonを作成する（プロジェクトの初期に一度実行）
```linux:linux
composer init
```

* require
新しいライブラリを追加する
composer.jsonにライブラリが、  composer.lockに実際にインストールしたものが記載される
```linux:linux
composer require <ライブラリ名>
```
* install
composer.jsonもしくはcomposer.lockに従ってライブラリをインストールする
チーム開発時に使用。共通のバージョンのライブラリを使うことができる
```linux:linux
composer install
```

* remove
ライブラリを取り除く
```linux:linux
composer remove <ライブラリ名>
```

<strong>環境変数</strong>
機密データをソースコードから分離するのに環境変数を使う

* 通常の変数
PHPのコードの中で定義し、実行したプロセス内でのみ使用できる

* 環境変数
PHPが動いてるOSに依存し、PHPアプリケーションに渡す仕組み（OSに格納される変数）
PHPコードに直接記述しないので、コードを見ても変数の中身がバレず、また環境によって切り替えられる

環境変数を使うのに、.envファイルを使うと簡単に設定できて便利(phpdotenv)

現状
* ソースコードにベタ書き
```linux:linux
$link = mysqli_connect('db', 'book_log', 'pass', 'book_log');
```

env使うと
* ソースコードから分離できる。環境によっても使い分けられる
envファイル：重要な設定データを定義する→環境変数として定義される
DB_HOST=db
* プログラムのファイル：.envファイルに定義された値を取得できる

```linux:linux
$dbHost = getenv('DB_HOST'); //$_ENV['DB_'HOST]でも取得できる
$link = mysqli_connect($dbHost, ..., ..., ,,,);
```

<strong>Webページが表示される仕組み</strong>

Webページが表示される流れを銀行に例えると、
* お客さん
ブラウザ
* 窓口
apache
* 手続きなど作業する人
PHP
* 作業する人があつかうデータ
データベース

# 設計・テーブル作成
<strong>目的</strong>
* テーブルの初期化をPHPから実施する(毎回SQL文を入力するのは面倒なため)

<strong>必要な知識</strong>
* 特になし

<strong>実践</strong>
* テーブルを初期化する処理をinitialize_reviews_table.phpに記載

```php:initialize_reviews_table.php
<?php

require_once __DIR__ . '/lib/mysqli.php';

function dropTable($link)
{
    $dropTable = 'DROP TABLE IF EXISTS reviews;';
    $result =  mysqli_query($link, $dropTable);
    if ($result) {
        echo 'テーブルの削除に成功しました' . PHP_EOL;
    } else {
        echo 'Error: テーブルの削除に失敗しました' . PHP_EOL;
        echo 'Debugging error: ' . mysqli_error($link)  . PHP_EOL;
    }
}

function createTable($link)
{
    $createTable = <<<EOT
CREATE TABLE reviews (
    id INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,
    title VARCHAR(255),
    author VARCHAR(100),
    status VARCHAR(10),
    score INTEGER,
    summary VARCHAR(1000),
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) DEFAULT CHARACTER SET=UTF8MB4;
EOT;
    $result =  mysqli_query($link, $createTable);
    if ($result) {
        echo 'テーブルの作成に成功しました' . PHP_EOL;
    } else {
        echo 'Error: テーブルの作成に失敗しました' . PHP_EOL;
        echo 'Debugging error: ' . mysqli_error($link)  . PHP_EOL;
    }
}

$link = dbConnect();
dropTable($link);
createTable($link);
mysqli_close($link);

```

* 該当のディレクトリで以下コマンドを実施するとテーブルの初期化が実施できる

```linux:linux
docker-compose exec app php reviews/initialize_reviews_table.php
```

# [登録ページ] HTMLで表示する
<strong>目的</strong>
PHPでHTMLを表示する

<strong>必要な知識</strong>

* HTML、CSS、PHPの作成手順
HTML→PHP→CSS

* HTMLを書くときの順序
1, WFを書く
2, HTMLで基本構成とheadを作成する
3, 大枠のレイアウトをメモする
4, HTMLで大枠のレイアウトを作成する
5, 個別パーツのレイアウトをメモする
6, HTMLで個別パーツを作成する

※HTMLを書くときのコツは入れ子構造を意識すること

<strong>実践</strong>

* 表示したい内容のHTMLをファイルに記載

* ブラウザのURL欄に以下を記載する

localhost:アプリケーションのポート番号/表示したいファイルのルートからのフルパス

* src/reviews/new.phpに記載

```php:new.php
<body>
    <h1>読書ログ</h1>
    <form action="" method="post">
        <div>
            <label for="title">書籍名</label>
            <input type="text" name="title" id="title">
        </div>
        <div>
            <label for="author">著者名</label>
            <input type="text" name="author" id="author">
        </div>
        <div>
            <label>読書状況</label>
            <div>
                <div>
                    <input type="radio" name="status" id="status1" value="未読">
                    <label for="status1">未読</label>
                </div>
                <div>
                    <input type="radio" name="status" id="status2" value="読んでる">
                    <label for="status2">読んでる</label>
                </div>
                <div>
                    <input class="form-check-input" type="radio" name="status" id="status3" value="読了">
                    <label for="status3">読了</label>
                </div>
            </div>
        </div>
        <div>
            <label for="score">評価（5点満点の整数）</label>
            <input type="number" name="score" id="score">
        </div>
        <div>
            <label for="summary">感想</label>
            <textarea type="text" name="summary" id="summary" rows="10"></textarea>
        </div>
        <button type="submit">登録する</button>
    </form>
</body>
```

# [登録ページ] 読書ログを登録する
<strong>目的</strong>
PHPでデータを登録する

<strong>必要な知識</strong>

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

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

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

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

* $_SERVER
連想配列として受け取る
REQUEST_METHOD：ページにアクセスする際に使用されたリクエストのメソッド名。''GET', 'POST'など

マジック定数

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

* __FILE__
ファイルのフルパスとファイル名

* __LINE__
そのファイル上の現在の行番号

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

* require
指定したファイルを読み込む
読み込みが失敗すると処理がそこで中断される

* require_once
requireとほぼ同じだが、一度しかファイルを読み込まない
悩んだらrequire_onceを使うことを推奨

リダイレクト
ユーザを自動的に別のページヘ転送する仕組み
1つのページに1つの処理

* header
生のHTTPヘッダを送信する

```php:php
$header：ヘッダ文字列
header($header)
```

* リダイレクトするには"Location:"ヘッダを付ける
リダイレクト処理するとこれ以降のコードは実行されないので注意
```php:php
header("Location: http://www.example.com/");
```

エラーハンドリング

* エラーメッセージをログに書き出す
ログは、障害などが起きた時に何が起きたかを把握できるように、プログラムの実行状況・データ送受信状況などを記録しておくもの

* PHP・Apacheのログ
ログには主にアクセスログとエラーログがある（自分で設定できる）。
PHPはエラーログ、Apacheはアクセスログとエラーログを主に出力

* アクセスログ
1, ブラウザがサーバー（Apache）にリクエストし、それにApacheが応えるごとに記録される（アクセスのたびに記録）
2, アクセスの履歴を確認するために使う

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

* error_log
エラーメッセージをWebサーバーのエラーログに送信する
error_log($message)

「データベースに接続できません」と記録するなら
```php:php
error_log("データベースに接続できません");
```

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

事前準備：コンテナ名を確認
```linux:linux
docker ps
```

ログを確認したい時（-f：follow。ログの出力を表示し続ける）
```linux:linux
docker logs -f <コンテナ名>
```

アクセスログを確認したい時（エラーログを捨てる）
```linux:linux
docker logs <コンテナ名> -f 2>/dev/null
```

エラーログを確認したい時
```linux:linux
docker logs <コンテナ名> -f 1>/dev/null
```

PHPとHTMLファイルを分割する
* include
指定したファイルを読み込む
読み込みが失敗しても処理が中断されない

※require_onceと似ているが、処理が中断されない点が異なる。HTMLやテキストなどを読み込む時に使う
※ロジックとHTMLはファイル分割する
※DRY
Don' t repeat yourself

バリデーションエラーしても入力値を残す

* value属性
input要素の値を指定する属性
テキスト入力欄などにおいて初期入力値を表す

```php:php
<input type="text" id="name" name="name" value="<?php echo $company['name'] ?>">
```

* バリデーションエラー時に入力した値が消える理由は、新しくHTMLを読み込んでいるから。これを改善するにフォームの初期値を設定しておく方法をパーツごとに抑えとくとよい。

* checked属性を付けると初期値で選択された状態になる
```php:php
<div>
	// 「男性」が最初から選択された状態になる
	<input type="radio" name="sex" id="sex1" value="男性" checked>
	<label for="sex1">男性</label>
</div>
<div>
	<input type="radio" name="sex" id="sex2" value="女性">
	<label for="sex2">女性</label>
</div>

<div>
	<input type="radio" name="sex" id="sex1" value="男性"
		<?php echo ($user['sex'] === '男性') ? 'checked' : ''; ?>>
	<label for="sex1">男性</label>
</div>
```


<strong>実践</strong>

src/reviews/lib/mysqli.php に共通処理をまとめる。

```php:mysqli.php
<?php
require __DIR__ . '/../vendor/autoload.php';

function dbConnect()
{
    $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;
}
```

src/reviews/new.php のformのactionを指定する
```php:new.php
<form action="create.php" method="post">
```

src/reviews/create.php で登録処理を行う。
```php:create.php
<?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);
    if (!$result) {
        error_log('Error: fail to create review');
        error_log('Debugging Error: ' . mysqli_error($link));
    }
}

if ($_SERVER['REQUEST_METHOD'] === '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);
}

header("Location: index.php");
```

src/reviews/index.php を一覧ページとして用意する
```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>
```

src/reviews/views/new.php を作成し、HTMLをまとめる
```php:new.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>
    <form action="create.php" method="post">
        <?php if (count($errors)) : ?>
            <ul>
                <?php foreach ($errors as $error) : ?>
                    <li><?php echo $error; ?></li>
                <?php endforeach; ?>
            </ul>
        <?php endif; ?>

        <div>
            <label for="title">書籍名</label>
            <input type="text" name="title" id="title">
        </div>
        <div>
            <label for="author">著者名</label>
            <input type="text" name="author" id="author">
        </div>
        <div>
            <label>読書状況</label>
            <div>
                <div>
                    <input type="radio" name="status" id="status1" value="未読">
                    <label for="status1">未読</label>
                </div>
                <div>
                    <input type="radio" name="status" id="status2" value="読んでる">
                    <label for="status2">読んでる</label>
                </div>
                <div>
                    <input class="form-check-input" type="radio" name="status" id="status3" value="読了">
                    <label for="status3">読了</label>
                </div>
            </div>
        </div>
        <div>
            <label for="score">評価（5点満点の整数）</label>
            <input type="number" name="score" id="score">
        </div>
        <div>
            <label for="summary">感想</label>
            <textarea type="text" name="summary" id="summary" rows="10"></textarea>
        </div>
        <button type="submit">登録する</button>
    </form>
</body>

</html>
```

src/reviews/new.php にて、src/reviews/views/new.php を読み込む。また $errors を初期化し、エラーが起こらないようにする
```php:new.php
<?php

$errors = [];

include 'views/new.php';
```

src/reviews/create.php の末尾にて、src/reviews/views/new.php を読み込む
```php:new.php
<?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);
    if (!$result) {
        error_log('Error: fail to create review');
        error_log('Debugging Error: ' . mysqli_error($link));
    }
}

function validate($review)
{
    $errors = [];

    // 書籍名が正しく入力されているかチェック
    if (!strlen($review['title'])) {
        $errors['title'] = '書籍名を入力してください';
    } elseif (strlen($review['title']) > 255) {
        $errors['title'] = '書籍名は255文字以内で入力してください';
    }

    // 著者名が正しく入力されているかチェック
    if (!strlen($review['author'])) {
        $errors['author'] = '著者名を入力してください';
    } elseif (strlen($review['author']) > 255) {
        $errors['author'] = '著者名は255文字以内で入力してください';
    }

    // 読書状況が正しく入力されているかチェック
    if (!in_array($review['status'], ['未読', '読んでる', '読了'])) {
        $errors['status'] = '読書状況は「未読」「読んでる」「読了」のいずれかを入力してください';
    }

    // 評価が正しく入力されているかチェック
    if ($review['score'] < 1 || $review['score'] > 5) {
        $errors['score'] = '評価は1〜5の整数を入力してください';
    }

    // 感想が正しく入力されているかチェック
    if (!strlen($review['summary'])) {
        $errors['summary'] = '感想を入力してください';
    } elseif (strlen($review['summary']) > 10000) {
        $errors['summary'] = '感想は10,000文字以内で入力してください';
    }

    return $errors;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $status = '';
    if (array_key_exists('status', $_POST)) {
        $status = $_POST['status'];
    }

    $review = [
        'title' => $_POST['title'],
        'author' => $_POST['author'],
        'status' => $status,
        'score' => $_POST['score'],
        'summary' => $_POST['summary']
    ];

    $errors = validate($review);

    if (!count($errors)) {
        $link = dbConnect();
        createReview($link, $review);
        mysqli_close($link);
        header("Location: index.php");
    }
}

// ここを追記。以下のHTMLは削除
include 'views/new.php';
```

src/new.php
```php:new.php
// ここを追記
$review = [
    'title' => '',
    'author' => '',
    'status' => '未読',
    'score' => '',
    'summary' => ''
];

$errors = [];
```

src/views/new.php
```php:new.php
// 下記をそれぞれ修正
<input type="text" name="title" id="title" value="<?php echo $review['title'] ?>">

<input type="text" name="author" id="author" value="<?php echo $review['author'] ?>">

<input type="radio" name="status" id="status1" value="未読" <?php echo ($review['status'] === '未読') ? 'checked' : ''; ?>>

<input type="radio" name="status" id="status2" value="読んでる" <?php echo ($review['status'] === '読んでる') ? 'checked' : ''; ?>>

<input class="form-check-input" type="radio" name="status" id="status3" value="読了" <?php echo ($review['status'] === '読了') ? 'checked' : ''; ?>>

<input type="number" name="score" id="score" value="<?php echo $review['score'] ?>">

<textarea type="text" name="summary" id="summary" rows="10"><?php echo $review['summary'] ?></textarea>
```

# [登録ページ] CSSでスタイルを整える
<strong>目的</strong>
Bootstrapを用いて画面のスタイルを整える

<strong>必要な知識</strong>

* CSSを書くときの順序
1, 大枠のレイアウトをスタイリングする
2, 大枠のレイアウトをレスポンシブ対応する
3, 個別パーツをスタイリングする
4, 個別パーツをレスポンシブ対応する

* 処理を書くコツは先にコメントで実装したい処理を書く
* エラー処理をしっかり書いておくと、エラーが起きた際の発見が早くなるため返って実装の速度が上がる。サボらずに書こう

* bootstrapの使い方は以下公式ドキュメント参照
[bootstrap](https://getbootstrap.jp/docs/4.5/getting-started/introduction/)


<strong>実践</strong>

stylesheets/css ディレクトリを作成した上で、下記コマンドを実行し、SassをCSSにコンパイルする。
```linux:linux
vendor/scssphp/scssphp/bin/pscss < stylesheets/scss/app.scss > stylesheets/css/app.css
```

src/reviews/views/new.php で app.css を読み込む
```php:new.php
<head>
    ...
    <link rel="stylesheet" href="stylesheets/css/app.css">
    <title>会社情報の登録</title>
</head>
```

src/reviews/views/new.php
```php:new.php
<body>
    <header class="navbar shadow-sm p-3 mb-5 bg-white">
        <h1 class="h2">
            <a class="text-body text-decoration-none" href="index.php">読書ログ</a>
        </h1>
    </header>
    <div class="container">
        <h2 class="h3 text-dark mb-4">読書ログの登録</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="title">書籍名</label>
                <input type="text" name="title" id="title" class="form-control" value="<?php echo $review['title'] ?>">
            </div>
            <div class="form-group">
                <label for="author">著者名</label>
                <input type="text" name="author" id="author" class="form-control" value="<?php echo $review['author'] ?>">
            </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="status1" value="未読" <?php echo ($review['status'] === '未読') ? 'checked' : ''; ?>>
                        <label class="form-check-label" for="status1">未読</label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="status" id="status2" value="読んでる" <?php echo ($review['status'] === '読んでる') ? 'checked' : ''; ?>>
                        <label class="form-check-label" for="status2">読んでる</label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="status" id="status3" value="読了" <?php echo ($review['status'] === '読了') ? 'checked' : ''; ?>>
                        <label class="form-check-label" for="status3">読了</label>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label for="score">評価（5点満点の整数）</label>
                <input type="number" name="score" id="score" class="form-control" value="<?php echo $review['score'] ?>">
            </div>
            <div class="form-group">
                <label for="summary">感想</label>
                <textarea type="text" name="summary" id="summary" class="form-control" rows="10"><?php echo $review['summary'] ?></textarea>
            </div>
            <button type="submit" class="btn btn-primary">登録する</button>
        </form>
    </div>
</body>
```

# [トップページ] HTMLで表示する
<strong>目的</strong>
読書ログの一覧画面のHTMLを作成する

<strong>必要な知識</strong>

<strong>実践</strong>

* 一覧画面のHTMLを作成する
* 登録画面と一覧画面のHTMLのレイアウト部分を共通化します
* バリデーションエラーになった画面（src/create.php）にもスタイルを当ててください。こちらは現状Bootstrapが当たってない状態になっているので、Bootstrapを当てればOKです（講義の会社情報ではこちらの対応やってなく、クイズ独自の対応です）

src/reviews/new.php
```php:new.php
<?php

$review = [
    'title' => '',
    'author' => '',
    'status' => '未読',
    'score' => '',
    'summary' => ''
];
$errors = [];

$title = '読書ログ登録';
$content = __DIR__ . "/views/new.php";
include __DIR__ . '/views/layout.php';
```
src/review/index.php

```php:index.php
<?php

$title = '読書ログ一覧';
$content = __DIR__ . "/views/index.php";
include __DIR__ . '/views/layout.php';
```

src/reviews/views/layout.php
```php:layout.php
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="stylesheets/css/app.css">
    <title><?php echo $title; ?></title>
</head>

<body>
    <header class="navbar shadow-sm p-3 mb-5 bg-white">
        <h1 class="h2">
            <a class="text-body text-decoration-none" href="index.php">読書ログ</a>
        </h1>
    </header>
    <div class="container">
        <?php include $content; ?>
    </div>
</body>
```

src/rviews/views/new.php
```php:new.php
<h2 class="h3 text-dark mb-4">読書ログの登録</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="title">書籍名</label>
        <input type="text" name="title" id="title" class="form-control" value="<?php echo $review['title'] ?>">
    </div>
    <div class="form-group">
        <label for="author">著者名</label>
        <input type="text" name="author" id="author" class="form-control" value="<?php echo $review['author'] ?>">
    </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="status1" value="未読" <?php echo ($review['status'] === '未読') ? 'checked' : ''; ?>>
                <label class="form-check-label" for="status1">未読</label>
            </div>
            <div class="form-check form-check-inline">
                <input class="form-check-input" type="radio" name="status" id="status2" value="読んでる" <?php echo ($review['status'] === '読んでる') ? 'checked' : ''; ?>>
                <label class="form-check-label" for="status2">読んでる</label>
            </div>
            <div class="form-check form-check-inline">
                <input class="form-check-input" type="radio" name="status" id="status3" value="読了" <?php echo ($review['status'] === '読了') ? 'checked' : ''; ?>>
                <label class="form-check-label" for="status3">読了</label>
            </div>
        </div>
    </div>
    <div class="form-group">
        <label for="score">評価（5点満点の整数）</label>
        <input type="number" name="score" id="score" class="form-control" value="<?php echo $review['score'] ?>">
    </div>
    <div class="form-group">
        <label for="summary">感想</label>
        <textarea type="text" name="summary" id="summary" class="form-control" rows="10"><?php echo $review['summary'] ?></textarea>
    </div>
    <button type="submit" class="btn btn-primary">登録する</button>
</form>/layout.php';
```

src/reivews/views/index.php
※sectionタグの中身の文字は何でもOK
※「&nbsp;」は特殊文字で、スペースを入れるために使用している。本来は「挿入した箇所の前後の改行を禁止する」役割だが、現場ではスペースを入れる目的でもよく使用される
```php:index.php
<?php

$review = [
    'title' => '',
    'author' => '',
    'status' => '未読',
    'score' => '',
    'summary' => ''
];
$errors = [];

$title = '読書ログ登録';
$content = __DIR__ . "/views/new.php";
include __DIR__ . '/views/layout.php';
```

```php:new.php
<?php

$review = [
    'title' => '',
    'author' => '',
    'status' => '未読',
    'score' => '',
    'summary' => ''
];
$errors = [];

$title = '読書ログ登録';
$content = __DIR__ . "/views/new.php";
include __DIR__ . '/views/layout.php';
```

src/reviews/create.php
※ここだけファイルの一部を掲載
```php:create.php
$title = '読書ログ登録';
// ここを修正
$content = __DIR__ . "/views/new.php";
include __DIR__ . '/views/layout.php';
```

# [トップページ] 読書ログの一覧を表示する
<strong>目的</strong>
データベースからデータを取得し、HTMLで画面に表示する

<strong>必要な知識</strong>

* データベースからデータを取得
配列で取得

* HTMLでデータを表示
ループで一つずつ表示

* データがないときの処理
データがないときなど、イレギュラーパターンを想定して処理を書けることがプロダクションコードへ第一歩

* TRUNCATE TABLE
指定したテーブルの全データを削除する
```mysql:mysql
tbl_name：テーブル名
TRUNCATE TABLE [TABLE] tbl_name
```

* 会社情報テーブルの全データを削除する
```mysql:mysql
TRUNCATE TABLE companies;
```

PHPでXSS対策するには
htmlspecialchars()を使ってHTML特殊記号をエスケープする

* HTMLの特殊記号を文字参照に置き換える
```
$string ：変換する文字列
$flags：処理の仕方を指定するフラグ
$encoding：文字を変換する時に使用するエンコーディング
htmlspecialchars($string, $flags, $encoding)
```

* ユーザーが入力してDBに保存された以下の値をエスケープするなら
```
「<script>...</script>」
htmlspecialchars('<script>...</script>', ENT_QUOTES, 'UTF-8')
```

実行結果
```
$lt;script&gt;...&lt;/script&gt;
```

<strong>実践</strong>

src/revies/lib/escape.php
```php:escape.php
<?php

function escape($string)
{
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
```

src/reviews/index.php
```php:index.php
<?php

require_once __DIR__ . '/lib/escape.php';
require_once __DIR__ . '/lib/mysqli.php';

function listReviews($link)
{
    $reviews = [];
    $sql = "SELECT id, title, author, status, score, summary FROM reviews";
    $results = mysqli_query($link, $sql);

    while ($review = mysqli_fetch_assoc($results)) {
        $reviews[] = $review;
    }

    mysqli_free_result($results);

    return $reviews;
}

$link = dbConnect();
$reviews = listReviews($link);

$title = '読書ログ一覧';
$content = __DIR__ . "/views/index.php";
include __DIR__ . '/views/layout.php';
```

src/reviews/views/index.php

```php:index.php
<a href="new.php">読書ログを登録する</a>
<main>
    <?php if (count($reviews) > 0) : ?>
        <?php foreach ($reviews as $review) : ?>
            <section>
                <h2><?php echo escape($review['title']) ?></h2>
                <div>
                    <?php echo escape($review['author']) ?>&nbsp;/&nbsp;
                    <?php echo escape($review['status']) ?>&nbsp;/&nbsp;
                    <?php echo escape($review['score']) ?>点
                </div>
                <p>
                    <?php echo nl2br(escape($review['summary']), false) ?>
                </p>
            </section>
        <?php endforeach; ?>
    <?php else : ?>
        <p>まだ読書ログが登録されていません。</p>
    <?php endif; ?>
</main>
```

# [トップページ] CSSでスタイルを整える
<strong>目的</strong>
一覧画面をデザインする
<strong>必要な知識</strong>
[登録ページ] CSSでスタイルを整えると同様
<strong>実践</strong>

src/revies/views/index.php
```php:index.php
<a href="new.php" class="btn btn-primary mb-4">読書ログを登録する</a>
<main>
    <?php if (count($reviews) > 0) : ?>
        <?php foreach ($reviews as $review) : ?>
            <section class="card shadow-sm mb-4">
                <div class="card-body">
                    <h2 class="card-title h4 text-dark mb-3"><?php echo escape($review['title']) ?></h2>
                    <div class="small mb-3">
                        <?php echo escape($review['author']) ?>&nbsp;/&nbsp;
                        <?php echo escape($review['status']) ?>&nbsp;/&nbsp;
                        <?php echo escape($review['score']) ?>点
                    </div>
                    <p>
                        <?php echo nl2br(escape($review['summary']), false) ?>
                    </p>
                </div>
            </section>
        <?php endforeach; ?>
    <?php else : ?>
        <p>まだ読書ログが登録されていません。</p>
    <?php endif; ?>
</main>
```

# 参考にしたサイト

[独学エンジニア](https://dokugaku-engineer.com/course/serverside/)