コマンドラインでアイスクリーム記録アプリを作って、関数とDB操作の基本を学んでみた（PHP × MySQL）

本記事は、PHP を使ってコマンドラインからアイスクリームの記録を登録・表示できる簡単なアプリを作成した備忘録です。

「関数」「バリデーション」「DB操作」「メニュー処理」などを学びながら手を動かした記録です。

# やりたいこと
- ターミナルからアイスクリーム名 / メーカー / 購入日 / 評価点 / 感想 を入力してDBに保存

- 保存されたログを一覧表示

- 1ファイルで完結するCLIアプリケーション

# 使用した開発環境構成

現在、学習中の独学エンジニアの環境を使って開発しました。

- Docker + Docker Compose を使用して、開発環境をコンテナで構築

- Webアプリ（app）コンテナ：PHP 7.4 + Apache

- データベース（db）：MySQL 5.5.62

- CLI（コマンドライン）アプリケーションも同じ環境内で実行

# 実行方法

作成したPHPアプリケーションは、以下のコマンドでターミナルから実行できます。

```bash
docker-compose exec app php <ファイル名>
```

# 完成したコード

```php
<?php

// データベースに接続する
function dbConnect(){

    // ホスト名、ユーザー名、パスワード、データベース名は仮の名前
    $link = mysqli_connect('YOUR_DB_HOST', 'YOUR_DB_USER', 'YOUR_DB_PASSWORD', 'YOUR_DB_NAME');

    if(!$link){
        echo 'データベースに接続できませんでした' . PHP_EOL;
        echo 'Debugging Error: ' . mysqli_connect_error() . PHP_EOL;
        exit(1);
    } else {
        echo 'データベースに接続できました' . PHP_EOL;
    }
    return $link;
}

// アイスクリームログの入力を受け取る
function createLogs(){
    echo 'アイスクリーム名を入力してください：';
    $ice_cream_name = trim(fgets(STDIN));

    echo 'アイスクリームのメーカーを入力してください：';
    $maker = trim(fgets(STDIN));

    echo '購入日を入力してください（YYYY-MM-DD形式）：';
    $purchase_date = trim(fgets(STDIN));

    echo '点数を入力してください（1～5の整数）：';
    // 入力時に整数にキャスト
    $score = (int)trim(fgets(STDIN));

    echo '食べた感想を入力してください：';
    $flavor_comment = trim(fgets(STDIN));

    $ice_cream_log = [
        'ice_cream_name' => $ice_cream_name,
        'maker' => $maker,
        'purchase_date' => $purchase_date,
        'score' => $score,
        'flavor_comment' => $flavor_comment
    ];

    return $ice_cream_log;
}

// 入力データのバリデーション
function validation($logs){
    $errors = [];

    // --- アイスクリーム名 ---
    if(empty($logs['ice_cream_name'])){
        $errors[] = 'アイスクリーム名が入力されていません。';
    } elseif (mb_strlen($logs['ice_cream_name']) > 100) {
        $errors[] = 'アイスクリーム名は100文字以内で入力してください。';
    }

    // --- メーカー ---
    if(empty($logs['maker'])){
        $errors[] = 'メーカー名が入力されていません。';
    } elseif (mb_strlen($logs['maker']) > 100) {
        $errors[] = 'メーカー名は100文字以内で入力してください。';
    }

    // --- 購入日 ---
    $trimmed_date = trim($logs['purchase_date']);
    if (empty($trimmed_date)) {
        $errors[] = '購入日が入力されていません。';
    } elseif (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $trimmed_date)) {
        $errors[] = '購入日はYYYY-MM-DDの形式で入力してください。';
    } else {
        $date_parts = explode('-', $trimmed_date);
        $year  = (int)$date_parts[0];
        $month = (int)$date_parts[1];
        $day   = (int)$date_parts[2];

        if (!checkdate($month, $day, $year)) {
            $errors[] = '購入日は有効な日付ではありません。日付が存在しないか、形式が不正です。';
        }
    }

    // --- 点数 ---
    $score = $logs['score'];
    if ($score < 1 || $score > 5) {
        $errors[] = '点数は1～5の整数で入力してください。';
    }

    // --- 感想 ---
    if(empty($logs['flavor_comment'])){
        $errors[] = '感想が入力されていません。';
    } elseif (mb_strlen($logs['flavor_comment']) > 1000) {
        $errors[] = '感想は1000文字以内で入力してください。';
    }

    return $errors; // バリデーションエラー配列を返す
}

// データベースにログを登録する
function logRecord($logs, $link) { // 引数の順序を呼び出し元と一致

    $sql = <<<EOT
    INSERT INTO ice_cream_logs (
        ice_cream_name,
        maker,
        purchase_date,
        score,
        flavor_comment
    ) VALUES (
        "{$logs['ice_cream_name']}",
        "{$logs['maker']}",
        "{$logs['purchase_date']}",
        "{$logs['score']}",
        "{$logs['flavor_comment']}"
    )
EOT;

    $result = mysqli_query($link, $sql);

    if(!$result){
        echo 'データを登録できませんでした' . PHP_EOL;
        echo 'Debugging Error: ' . mysqli_error($link) . PHP_EOL; // $link を指定
        return false; // 失敗時はfalseを返す
    }

    return true; // 成功時はtrueを返す
}

// データベースからログを表示する
function showLogs($link) {
    // ice_cream_logsテーブルからデータを取得
    $sql = "SELECT ice_cream_name, maker, purchase_date, score, flavor_comment FROM ice_cream_logs";

    $result = mysqli_query($link, $sql);

    if (!$result) {
        echo 'ログの取得に失敗しました: ' . mysqli_error($link) . PHP_EOL;
        return;
    }

    if (mysqli_num_rows($result) === 0) { // データが0件の場合のメッセージ
        echo '登録されているアイスクリームログはありません。' . PHP_EOL;
        mysqli_free_result($result); // 結果セットを解放
        return;
    }

    echo '--- アイスクリームログ一覧 ---' . PHP_EOL;
    while($listLog = mysqli_fetch_assoc($result)){
        echo 'アイスクリーム名：' . $listLog['ice_cream_name'] . PHP_EOL;
        echo 'メーカー名：' . $listLog['maker'] . PHP_EOL;
        echo '購入日：' . $listLog['purchase_date'] . PHP_EOL;
        echo '点数：' . $listLog['score'] . '点' . PHP_EOL;
        echo '感想：' . $listLog['flavor_comment'] . PHP_EOL;
        echo '----------------------------' . PHP_EOL;
    }
    mysqli_free_result($result); // 結果セットを解放
}

// --- メイン処理 ---
$link = dbConnect(); // データベース接続を試みる

// メインループ
while(true){
    echo PHP_EOL . '------------------------------------' . PHP_EOL;
    echo 'メニューから選択してください' . PHP_EOL;
    echo '1: アイスクリームログを登録する' . PHP_EOL;
    echo '2: アイスクリームログを表示する' . PHP_EOL;
    echo '3: 終了する' . PHP_EOL;
    echo '------------------------------------' . PHP_EOL;
    echo '選択: ';

    $num = trim(fgets(STDIN));

    if ($num === '1'){
        echo '--- アイスクリームログ登録 ---' . PHP_EOL;
        $logs = createLogs(); // 引数なしで呼び出し
        $errors = validation($logs); // バリデーション実行

        if (count($errors) > 0){
            echo '入力エラーがあります:' . PHP_EOL;
            foreach ($errors as $error){
                echo '- ' . $error . PHP_EOL;
            }
        } else {
            // logRecordが成功した場合のみ成功メッセージを表示
            if (logRecord($logs, $link)) {
                echo 'ログを登録しました！' . PHP_EOL;
            } else {
                echo 'ログの登録に失敗しました。詳細については上記のエラーメッセージをご確認ください。' . PHP_EOL;
            }
        }
    } elseif ($num === '2'){
        echo '--- アイスクリームログ表示 ---' . PHP_EOL;
        showLogs($link);
    } elseif ($num === '3'){
        echo 'アプリケーションを終了します。' . PHP_EOL;
        break; // ループを抜ける
    } else {
        echo '無効な入力です。1, 2, 3のいずれかを入力してください。' . PHP_EOL;
    }
}

// プログラム終了時にデータベース接続を閉じる
mysqli_close($link);
echo 'データベース接続を閉じました。' . PHP_EOL;

?>

```


## 苦労したこと・学び
### 日付のバリデーション処理
preg_match + checkdate() の組み合わせで日付の形式や実在をチェックする方法を学びました。

### mysqli 関数
MySQLに接続してデータを保存する処理をPHPで書く中で、mysqli 関数の種類が多くて混乱しました。


| よく出てきた関数名                     | 役割（ざっくり）                                |
|--------------------------|-----------------------------------------------|
| mysqli_connect()                | データベースに接続する                            |
| mysqli_query()                | SQL文を実行する                             |
| mysqli_fetch_assoc()              | 結果を連想配列で取り出す                           |
| mysqli_error()               | 接続またはクエリ実行のエラー情報を取得する                   |
| mysqli_num_rows()              | 結果セットの行数を取得する                            |
| mysqli_close()             | データベース接続を終了する                          |


最初は「全部が似た名前でどれが何をするのか区別がつかない…」と感じていましたが、関数の名前を「動作＋対象」と意識して読めば整理しやすくなりました。

たとえば…

- connect → 接続する
- query → クエリ（SQL文）を実行する
- fetch → データを取得する
- error → エラー情報を取得する（主に $link または $result を引数に取る）
- num_rows → 行数を取得する
- close → 接続を終了する

特に mysqli_error() は、引数に接続オブジェクト ($link) や クエリ結果オブジェクト ($result) を渡すことで、どの操作でエラーが発生したかを詳しく知ることができる点も学びが深かったです。

今後は「公式ドキュメント」を参照し、また「データベース処理の流れ（接続 → 実行 → データ取得 → 終了）」を意識することで、それぞれの関数の役割と正しい使い分けをよりスムーズにできるようになりたいと思っています。

## 実装できた機能
- アイスクリームログの登録

- バリデーションチェック（100文字、日付形式など）

- 登録されたデータの一覧表示

- DB接続＆MySQLテーブル作成済み（例: ice_cream_logs）

## 次にやりたいこと
- Webアプリ版（フォーム入力）


##  最後に（まとめ）

まだまだ修正が必要だと思いますが、1つのアプリを作れた達成感は大きいと思いました。

PHPはWebだけでなくCLIツールとしても役立つことを実感しました。

今後も小さく作って、少しずつ大きくすることを意識していきたいです。