PHPで作る掲示板に画像をアップロード

php-db07:データベースを使った掲示板で作成した掲示板に、画像がアップロードできる機能を追加してみましょう。データベース掲示板の作成は下記のサイトを参照してください。

Webtraining - Webトレは、 初心...
PHPとデータベースを使った掲示板 | Webtraining - Webトレは、 初心者から実務までのWeb学習トレーニング 完成イメージ この章では、php-basic10 で作成した「テキストファイルに保存するミニ掲示板」を、MySQLのデータベースに保存する構成へとリファクタリングします。これによ...

フォルダ構成とファイル名

  • 作成フォルダ:php-uploadimage
  • 作成ファイル:bbs.php
  • 使用CSSファイル(任意):style.cssは任意で作成
  • /upload 画像を格納するためのフォルダの作成

データベーステーブルの作成

アップロード付のテーブルを新規で作成しておきましょう。データベースはtestdbです

CREATE TABLE bbs_upload (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    comment TEXT NOT NULL,
    image VARCHAR(255) DEFAULT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

完成イメージ

アップロード掲示板+DBのコード
<?php
session_start();

// DB接続
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'root';
$password = '';

function h($str)
{
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

try {
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $errors = [];
    $rows = [];
    $maxComment = 100; // 最大文字数を定数的に定義

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name = trim($_POST['name'] ?? '');
        $comment = trim($_POST['comment'] ?? '');
        $imageName = null;

        // 画像がアップロードされたら保存
        if (!empty($_FILES['image']['name'])) {
            // サイズチェック(2MB超ならエラー)
            if ($_FILES['image']['size'] > 2 * 1024 * 1024) {
                $errors[] = '画像は2MB以下にしてください。';
            } else {
                $ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION));

                if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
                    $imageName = uniqid() . '.' . $ext;
                    if (!move_uploaded_file($_FILES['image']['tmp_name'], __DIR__ . '/upload/' . $imageName)) {
                        $errors[] = '画像のアップロードに失敗しました。';
                    }
                } else {
                    $errors[] = '画像は jpg, jpeg, png, gif のいずれかを選択してください。';
                }
            }
        }


        // バリデーションチェック
        if ($name === '') {
            $errors[] = '名前を入力してください。';
        }
        if ($comment === '') {
            $errors[] = 'コメントを入力してください。';
        } elseif (mb_strlen($comment) > $maxComment) {
            $errors[] = "コメントは {$maxComment} 文字以内で入力してください。";
        }

        // バリデーションを通過した場合のみ登録
        if (empty($errors)) {
            $stmt = $pdo->prepare("INSERT INTO bbs_upload (name, comment, image) VALUES (?, ?, ?)");
            $stmt->execute([$name, $comment, $imageName]);
            $_SESSION['flash_msg'] = "投稿しました!";
            header("Location: " . $_SERVER['PHP_SELF']);
            exit;
        }
    }
    // 一覧表示
    $stmt = $pdo->query("SELECT * FROM bbs_upload ORDER BY id DESC");
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    echo "DBエラー:" . $e->getMessage();
    exit;
}
?>
<!DOCTYPE html>
<html lang="ja">
<link rel="stylesheet" href="style.css">

<head>
    <meta charset="UTF-8">
    <title>データベース版掲示板</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <header class="header">
        <h1>掲示板+画像のアップロード付</h1>
    </header>
    <main class="content">
        <!-- メッセージ -->
        <?php if (!empty($_SESSION['flash_msg'])): ?>
            <p><strong><?= h($_SESSION['flash_msg']) ?></strong></p>
            <?php unset($_SESSION['flash_msg']); ?>
        <?php endif; ?>

        <!-- 入力フォーム -->
        <section class="new-post">
            <h2>新規投稿</h2>
            <form method="post" enctype="multipart/form-data">
                <p><label for="name">名前</label>
                    <input type="text" name="name" id="name" required>
                </p>
                <p>
                    <label for="come">コメント(最大<?= h($maxComment) ?>文字)</label>
                    <textarea name="comment" id="come" required maxlength="<?= h($maxComment) ?>"></textarea>
                </p>
                <p>
                    <label>画像
                        <input type="file" name="image" accept=".jpg,.jpeg,.png,.gif">
                    </label>
                </p>
                <button type="submit">投稿する</button>
            </form>
        </section>


        <!-- エラーメッセージ -->
        <section>
            <?php if (!empty($errors)): ?>
                <ul class="error">
                    <?php foreach ($errors as $e): ?>
                        <li><?= h($e) ?></li>
                    <?php endforeach; ?>
                </ul>
            <?php endif; ?>
        </section>

        <section>
            <h2>投稿一覧(新しい順)</h2>
            <?php if (empty($rows)): ?>
                <p>まだ投稿はありません。</p>
            <?php else: ?>
                <?php foreach ($rows as $row): ?>
                    <article>
                        <strong><?= h($row['name']) ?></strong>
                        <time>(<?= h($row['created_at']) ?>)</time>
                        <p><?= nl2br(h($row['comment'])) ?></p>
                        <?php if (!empty($row['image'])): ?>
                            <p><img src="upload/<?= h($row['image']) ?>" alt="" width="200"></p>
                        <?php endif; ?>
                    </article>
                <?php endforeach; ?>
            <?php endif; ?>
        </section>

    </main>
</body>

</html>

プログラムの追加構成と解説

フォームに enctype とファイル選択を追加

<form method="post" enctype="multipart/form-data">
    ...
    <p>
        <label>画像
            <input type="file" name="image" accept=".jpg,.jpeg,.png,.gif">
        </label>
    </p>
</form>
  • enctype="multipart/form-data"
    ファイルをアップロードするときは必須の設定です。
  • accept 属性
    選べるファイルの種類を制限(jpg, png, gif のみ)

画像アップロード処理を追加

この処理では、アップロードされた画像を安全に保存するために
サイズチェック・拡張子チェックを行い、保存時には重複しないファイル名を作っています。

__DIR__ を使って絶対パスで保存しているので、スクリプトの場所が変わっても正しく動作します。

// 画像がアップロードされたら保存
if (!empty($_FILES['image']['name'])) {
    // サイズチェック(2MB超ならエラー)
    if ($_FILES['image']['size'] > 2 * 1024 * 1024) {
        $errors[] = '画像は2MB以下にしてください。';
    } else {
        $ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION));

        if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
            $imageName = uniqid() . '.' . $ext;
            if (!move_uploaded_file($_FILES['image']['tmp_name'], __DIR__ . '/upload/' . $imageName)) {
                $errors[] = '画像のアップロードに失敗しました。';
            }
        } else {
            $errors[] = '画像は jpg, jpeg, png, gif のいずれかを選択してください。';
        }
    }
}

画像アップロード処理の流れ

  1. アップロードされたか確認
    $_FILES['image']['name'] が空でなければ、画像が選択されていると判断。
  2. サイズチェック
    $_FILES['image']['size']2MB以下か確認。超えていればエラーを追加。
  3. 拡張子の取得とチェック
    pathinfo() でファイル名から拡張子を取り出し、strtolower() で小文字に変換して比較。
    in_array(探す値, 配列) で、拡張子が ['jpg','jpeg','png','gif'] のどれかかをチェック。
  4. 保存用のファイル名を作成
    uniqid() で一意のファイル名を作り、重複や上書きを防止。
  5. ファイルを移動して保存
    move_uploaded_file() を使い、サーバー内の /upload/ フォルダに移動。
    • __DIR__ は「このファイルのあるディレクトリ」を表す定数
      → 相対パスではなく絶対パスで保存するので安全。

データベースにファイル名を保存

$stmt = $pdo->prepare("INSERT INTO bbs_upload (name, comment, image) VALUES (?, ?, ?)");
$stmt->execute([$name, $comment, $imageName]);
  • 画像ファイル名を一緒にINSERT
  • 画像がなければ NULL が入る

一覧表示で画像を出力

<?php if (!empty($row['image'])): ?>
    <p><img src="upload/<?= h($row['image']) ?>" alt="" width="200"></p>
<?php endif; ?>

  • 画像があるときだけ <img> を表示
  • 表示サイズを固定(ここでは横幅200px)

ステップアップしてみよう!

アップロードフォルダの存在をチェックして自動作成

$uploadDir = __DIR__ . '/upload/';
if (!is_dir($uploadDir)) {
    mkdir($uploadDir, 0777, true);
}

→ フォルダが存在しない場合に自動で作る。

ファイルサイズの制限

if ($_FILES['image']['size'] > 2 * 1024 * 1024) { // 2MB
    $errors[] = "画像サイズは2MB以下にしてください。";
}

→ サーバーに大きすぎるファイルが送られないようにする。

ファイルサイズの制限

if ($_FILES['image']['size'] > 2 * 1024 * 1024) { // 2MB
    $errors[] = "画像サイズは2MB以下にしてください。";
}

→ サーバーに大きすぎるファイルが送られないようにする。