SHINYA TECH

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

#9 バリデーション処理

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

ユーザが予期せぬ値を入力した際に、セキュリティ上問題が生じたり、変なデータがデータベースに保存され、プログラムが不具合を起こす可能性をなくすため、バリデーション処理を行いました。

バリデーションとは

バリデーションとは、入力された値が期待される値のものかをチェックすること。 ユーザが入力する箇所ではバリデーションは必須。

よくあるバリデーションの条件

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

バリデーションの基本形

<?php

function validate($name)  // バリデーションメソッドを用意
{
    $errors = [];
    if (!strlen($name)){    // $nameが未入力だったら
        $errors['name'] = '社名を入力してください';
    } elseif (mb_strlen($name) > 100) {    // $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; //処理を中断する
}

$sql = "INSERT INTO companies ('name') VALUES ({$name})";   // 問題がなければ処理を続ける
  • strlen = string length = 文字列の長さをカウント
    • strlen 関数は全角文字(日本語)をカウントすると、1文字を3文字としてカウントする。
    • これは strlen が文字数ではなくバイト数を返しており、日本語は一文字で複数バイトを使うため。
    • mb_strlen 関数だと複数バイトを考慮し、日本語の1文字を1文字としてカウントする。
  • PHPの場合、0 = false となる

論理演算子

条件分岐で条件を否定したいときや2つ以上の条件を組合せたいときは論理演算子を使う。

否定の演算子 (!)

!$a    // $aがtrue出ない時true

論理積の演算子 (&&)

$a && $b    // $aも$bもtrueならtrue

論理和の演算子 (||)

$a || $b    // $aと$bのどちらかでもtrueならtrue

型キャスト

型キャストを使うことで型を別の型へ変換できる。

ユーザの入力値を適切な型に変換してプログラムで扱う時などに使用する。

  • 変換しようとする型を括弧で括り、変換する変数の前に置くと、型を変換できる。

    • (int) - 整数への変換

    • (bool) - 論理値への変換

    • (string) - 文字列への変換

対話型シェルで確認

対話型シェル(Interactive shell)を使用することでPHPの動作を確認できる。

sk@MacBook-Air part2 % docker-compose exec app /bin/bash
root@e59265f2a27b:/var/www/html# php -a
Interactive shell

php > echo 1;
1
php > echo 1 + 2;
3
php > $a = '5';
php > var_dump($a);
string(1) "5"
php > var_dump((int) $a);
int(5)
php > var_dump((int) 5.8);
int(5)
php > var_dump((int) 'hoge');
int(0)

数値の比較

比較演算子を使うことで2つの値を比較することができる

  • $a == $b

    • $aと$boが等しい場合にTRUE。型が違う場合はキャストされる(型変換される)
  • $a === $b

    • $aと$bが等しい場合にTRUE。キャストが行われない(厳密な比較)
      • プログラムが思わぬ挙動をするのを防ぐことができるため、こちらを使用するのが望ましい
  • $a != $b

    • $aと$bが等しくない場合にTRUE。型が違う場合はキャストされる
  • $a !== $b

    • $aと$bが等しくない場合にTRUE。キャストが行われない(厳密な比較)
  • $a < $b

    • $aが$bより小さい場合にTRUE
  • $a > $b

    • $aが$bより大きい場合にTRUE
  • $a <= $b

    • $aが$b以下の場合にTRUE
  • $a >= $b

    • $aが$b以上の場合にTRUE

対話型シェルで確認

sk@MacBook-Air part2 % docker-compose exec app /bin/bash
root@e59265f2a27b:/var/www/html# php -a
Interactive shell

php > var_dump(1 > 2);
bool(false)
php > var_dump(1 < 2);
bool(true)
php > var_dump(1 === 1);
bool(true)
php > var_dump(1 === '1');
bool(false)
php > var_dump(1 == '1');
bool(true)

読書ログにバリデーション処理を実装

<?php

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

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

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

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

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

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

    return $errors;
}


// MySQLへの接続処理を関数として定義
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;
    }

    echo 'データベースに接続できました' . PHP_EOL;

    // データベースと切断する際に、接続情報を使用するため、returnで返す
    return $link;
}


// 読書ログ登録の関数を定義
function createReview($link)    // 引数で $link を受け取る
{
    $review = [];

    echo '読書ログを登録してください' . PHP_EOL;
    echo '書籍名:';
    $review['title'] = trim(fgets(STDIN));

    echo '著者名:';
    $review['author'] = trim(fgets(STDIN));

    echo '読書状況(未読、読んでいる、読了):';
    $review['status'] = trim(fgets(STDIN));

    echo '評価(5点満点の整数):';
    $review['score'] = (int) trim(fgets(STDIN));  //整数として変数に格納

    echo '感想:';
    $review['summary'] = trim(fgets(STDIN));

    $validated = validate($review);
    if (count($validated) > 0) {
        foreach ($validated as $error) {
            echo $error . PHP_EOL;
        }
        return;
    }

    $sql = <<<EOT
INSERT INTO reviews (
    title,
    author,
    status,
    score,
    summary
) VALUES (
    "{$review['title']}",
    "{$review['author']}",
    "{$review['status']}",
    "{$review['score']}",
    "{$review['summary']}"
);
EOT;    // EOT; の前にスペースやインデントをつけてはいけない。

    $result = mysqli_query($link, $sql);
    if ($result) {
        echo '登録が完了しました' . PHP_EOL . PHP_EOL;
    } else {
        echo 'Error: データの追加に失敗しました' . PHP_EOL;
        echo 'Debugging error:' . mysqli_error($link) . PHP_EOL . PHP_EOL;
    }
}


/* ログの表示については、あとで修正
// 読書ログ表示の関数を定義
function getReviews($reviews)
{
    echo '読書ログを表示します' . PHP_EOL;

    foreach ($reviews as $review) {
        echo '書籍名:' . $review['title'] . PHP_EOL;
        echo '著者名:' . $review['author'] . PHP_EOL;
        echo '読書状況:' . $review['status'] . PHP_EOL;
        echo '評価:' . $review['score'] . PHP_EOL;
        echo '感想:' . $review['summary'] . PHP_EOL;
        echo '-----------------' . PHP_EOL;
    }
}
*/

// MySQLとの接続処理
// 切断処理をする際に接続情報が必要なため、別途$link変数に格納している
$link = dbConnect();


while (true) {
    echo '1. 読書ログを登録' . PHP_EOL;
    echo '2. 読書ログを表示' . PHP_EOL;
    echo '9. アプリケーションを終了' . PHP_EOL;
    echo '実行したい番号(1,2,9)を入力してください:';
    $i = trim(fgets(STDIN));

    if ($i === '1') {
        // 読書ログを登録する
        createReview($link);   // 引数で $link を渡す
        /*
    } elseif ($i === '2') {
        // 読書ログを表示する
        getReviews($reviews);
        */
    } elseif ($i === '9') {
        // MySQLとの切断処理
        mysqli_close($link);
        break;
    }
}

参考URL

www.php.net