データベースとCUIアプリケーションの接続（独学エンジニア　レッスン５より）

# 目標
<strong>今回やること</strong>
* CUIアプリケーションをデータベースに対応させる

<strong>タスクばらし<strong/>
* 読書ログを保存するテーブルを作成する
* PHPからデータベースに接続する
* 読書ログをデータベースに登録する
* 読書ログをデータベースから表示する

# 補足

<strong>MySQLの操作の流れ</strong>
接続→命令→切断

* MySQLに接続する
* MySQLに命令する
  データベースを作成する
  テーブルを作成する
  テーブルにデータを登録する
  テーブルからデータを取得する
* MySQLとの接続を切断する

接続はMySQLに電話をかけて、相手が「今いいよ」といってから命令を出すイメージ
MySQLとの間に確立された接続のことを「コネクション」という

# 読書ログを保存するテーブルを作成する 
<strong>目的</strong>

DockerでMySQLに接続し、読書ログを保存するテーブルを作成する

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

* MySQLと接続するにはmysqlコマンドを使う

クライアントからmysqlサーバーに接続する
```mysql:qiita.sql
// -pで値を指定しないとターミナルが入力を求め、入力時に表示されないため、セキュア
mysql -h ホスト名 -u ユーザー名 -D データベース名 -p
```

appコンテナからdbコンテナのmysqlに接続する
```linux:qiita.linux
docker-compose exec app mysql -h db -u book_log -D book_log -p
```

* MySQLに命令する
[dev.mysql.com](https://dev.mysql.com/doc/refman/5.6/ja/create-table.html)を参考にして、SQL文を作成する

* ファイルからSQL文を実行することで、毎回手入力せずに済む

テキストファイルにSQL文を保存し、下記コマンドを実行する
```linux:qiita.linux
mysql < text_file
```
Docker上のMySQLに対して実行するなら以下のコマンド
```linux:qiita.linux
docker-compose exec app /bin/bash
mysql -h db -u book_log -D book_log -p < text_file
```
<strong>実践</strong>

* Dockerの機能でコンテナを立ち上げた際に、データベースは作成される
そのため、以下のコマンドを入力すれば、データベースは自動で作成される

```linux:qiita.linux
docker-compose exec app mysql -h db -u book_log -D book_log -p
```

* MySQLでテーブルを作成する

```mysql:qiita.sql
CREATE TABLES 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;
```

次の作業でSQLをファイルから実行するため、一旦作成したテーブルは削除する

```mysql:qiita.sql
DROP TABLE reviews;
```

* ファイルにテーブル作成のSQL文を保存し、そのファイルからSQL文を実行する

保存するテーブルの中身
```mysql:itinitialize_reviews_table.sql
// reviewsテーブルを初期化
DROP TABLE IF EXISTS reviews;

CREATE TABLES 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;
```
ファイルを実行
```linux:qiita.linux
docker-compose exec app /bin/bash
mysql -h db -u book_log -D book_log -p < itinitialize_reviews_table.sql
```

# PHPからデータベースに接続する 
<strong>目的</strong>

* PHPからMySQLに接続する

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

* PHPからMySQLを利用する時はMySQLiを使おう
MySQLに接続するにはmysqli_connect関数使おう
```linux:qiita.linux
mysqli_connect('my_host', 'my_user', 'my_password', 'my_db')
```

読書ログでMySQLに接続するなら
```linux:qiita.linux
mysqli_connect('db', 'book_log', 'pass', 'book_log')
```

* MySQLの使用が完了したらMySQLとの接続を切る

接続を切るにはmysqli_close関数を使う
```linux:qiita.linux
// $link:mysqli_connect()が返すリンクID
mysqli_close($link)
```

<strong>実践</strong>

・読書ログでMySQLに接続、切断する

以下をbook_log.phpに追記
```php:book_log.php
function dbConnect()
{
    $link = mysqli_connect('db', 'book_log', 'pass', 'book_log');
    
    // データベースに接続できなかったときのエラー処理
    if (!$link) {
        echo 'Error: データベースに接続できません' . PHP_EOL;
        echo 'Debugging error: ' . mysqli_connect_error() . PHP_EOL;
        exit;
    }
    // $linkに格納された値は関数の外でも使用するため、戻り値に指定
    return $link;
}
```

# 読書ログをデータベースに登録する 
<strong>目的</strong>

* PHPからMySQLにデータを追加する

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

* SQLのINSERT文でテーブルにデータを登録する

INSERT文を使うと、テーブルに一行のデータを挿入する
```php:php
$link : mysql_connect()が返すリンクID
$query : SQL文の文字列
```
companiesテーブルにデータを登録するなら
```php:php
$link = mysqli_connect('db', 'book_log', 'pass', 'book_log');
$sql = "INSERT INTO compaies (name) VALUES ('SmartHR inc')";
mysqli_query($link, $sql);
```

* mysqli_query()は実行時に失敗した場合にFALSEを返すことを利用してエラーメッセージを表示する
```php:php
$link = mysqli_connect('db', 'book_log', 'pass', 'book_log');
$sql = "INSERT INTO compaies (name) VALUES ('SmartHR inc')";
$result = mysqli_query($link, $sql);
if ($result) {
	echo 'データを追加しました' . PHP_EOL;
} else {
	echo 'Error: データの追加に失敗しました' . PHP_EOL;
	echo 'Debugging Error: ' . mysqli_error($link) . PHP_EOL;
}
```

* ヒアドキュメントを使うと、複数行の文字列をそのままコード中に記載できるので、SQL文を書くときなどに見やすくなり便利
```php:php
// ヒアドキュメント書く際は必ず左詰め
$sql = <<<EOT
INSERT INTO companies (
	name
) VALUES (
	'SmartHR inc'
)
EOT;
```

* バリデーション処理をする

バリデーション処理をしないと、、
予期せぬ値が入力されるかも
セキュリティ上問題があったり、変なデータがデータベースに保存され、プログラムが不具合を起こしたりしかねない

* よくあるバリデーション処理

値が入力されているか？
文字列の長さは適当な範囲か？
数値が入力されているか？
数値は適切な範囲内の値か？
選択肢から選ばれた値か？
書式が正しいか？（メールアドレス、電話番号など）

* バリデーションの基本形

strlen関数は全角文字（日本語）をカウントすると、1文字を3文字としてカウントする。
これはstrlenが文字数ではなくバイト数を返しており、日本語は一文字で複数バイトを使うため。
mb_strlen関数だと複数バイトを考慮し、日本語の1文字を1文字としてカウントする。
```php:php
// バリデーションメソッドを用意
function validate($name)
{
	$erros = [];
	if (!strlen($name)) {
		$errors['name'] = '社名を入力してください';
	} elseif (strlen($name) > 100) {
		$errors['name'] = '社名は100文字以内で入力してください';
	}
	// エラー変数を返す
	return $errors;
}

$name = trim(fgets(STDIN));
$validated = validate($name);
if (count($validated) > 0) {
	foreach ($validated as $error) {
		echo $error . PHP_EOL;
	} 
	// 処理を中断する
	return;
}
```

* 整数のバリデーション
文字列を整数に変換することができる
ユーザーの入力値を適切な型に変換してプログラムで扱う時などに使用する

型キャスト
(int) 
(bool)
(string)

文字列を整数へ変換するなら
```php:php
$a = '2';
var_dump( (int) $a )      // int(2)
```

ex)型キャストの挙動
```php:php
php > var_dump((int)5.8);
int(5)
php > var_dump((int) 'hoge');
int(0)
php >
```
※php -a
インタラクティブにphpを実行できる
※変数の型を意識する

<strong>実践</strong>
* 読書ログをPHPからデータベースに登録する

book_log.phpの関数createReviewsに追記
```php:book_log.php
function createReviews($link)
{
..
..
..

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) {
        echo 'データを追加しました' . PHP_EOL;
    } else {
        echo 'Error: データの追加に失敗しました' . PHP_EOL;
        echo 'Debugging error: ' . mysqli_error($link)  . PHP_EOL;
    }
}
```

* 上記の「読書ログをPHPからデータベースに登録する処理」にバリデーション処理を加える

book_log.phpの関数validateを作成し、中に処理を記載
```php:book_log.php
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']) > 100) {
        $errors['author'] = '著者名は100文字以内で入力してください';
    }

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

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

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

    return $errors;
}
```

# 読書ログをデータベースから表示する
<strong>目的</strong>
* PHPからMySQLのデータを取得し、表示する

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

* mysqli_*関数

mysqli_query：データベース上でクエリを実行する
成功したらmysqli_resultオブジェクトを返す
```php:php
mysqli_query($link, $query)
```

mysqli_fetch_assoc：結果の行を連想配列で取得する
$result：mysqli_resultオブジェクト
```php:php
mysqli_fetch_assoc($result)
```

mysqli_free_result：結果に関連付けられたメモリを開放する
$result：mysqli_resultオブジェクト
```php:php
mysqli_free_result($result)
```

* PHPからSQLでデータを取得する

```php:php
$link = mysqli_connect('db', 'book_log', 'pass', 'book_log');
$sql = "SELECT * FROM compaies";
$result = mysqli_query($link, $sql);

// 連想配列で結果を取得
while ($company = mysqli_fetch_assoc($results)) {
	echo '会社名: ' . $company['name'] . PHP_EOL;
	echo '代表者: ' . $company['founder'] . PHP_EOL;
}

// メモリを開放する
mysqli_free_result($result);
```
<strong>実践</strong>

book_log.phpの関数viewReviewsに処理を追記
```php:book_log.php
function viewReviews($link)
{
    var_dump($link) . PHP_EOL;
    $sql = 'SELECT id, title, author, status, score, summary FROM reviews';
    $results =  mysqli_query($link, $sql);

    echo '登録されている読書ログを表示します' . PHP_EOL;

    while ($review = mysqli_fetch_assoc($results)) {
        echo '書籍名：' . $review['title'] . PHP_EOL;
        echo '著者名：' . $review['author'] . PHP_EOL;
        echo '読書状況（未読,読んでる,読了）：' . $review['status'] . PHP_EOL;
        echo '評価（5点満点の整数）：' . $review['score'] . PHP_EOL;
        echo '感想：' . $review['summary'] . PHP_EOL;
        echo '---------------------' . PHP_EOL;
    }

    mysqli_free_result($results);
}
```

# 参考にしたサイト
[独学エンジニア](https://dokugaku-engineer.com/course/serverside "独学エンジニア")