PHPでメールフォーム作成

デモページ

https://internet.mints.ne.jp/contact-mt/index.php

作成手順

1)ローカルDocker環境でプロジェクトディレクトリ作成

project-dir
 ├ Dockerfile
 └ docker-compose.yml

project-dir\Dockerfile

FROM php:7.4-apache

RUN apt-get update && apt-get install -y \
    libzip-dev \
    zip \
&& docker-php-ext-install zip

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

RUN docker-php-ext-install mysqli pdo pdo_mysql

# SMTPサポートを有効化
RUN apt-get install -y msmtp msmtp-mta && \
    echo 'sendmail_path = "/usr/bin/msmtp -t"' > /usr/local/etc/php/conf.d/mail.ini

COPY . /var/www/html/

RUN composer install

project-dir\docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/html

Dockerサービスの起動

docker-compose up -d

2)PHPMailerのインストール:
Composerを使用してPHPMailerをインストールします。プロジェクトのルートディレクトリで以下のコマンドを実行します

composer require phpmailer/phpmailer

project-dir
 ├ Dockerfile
 ├ docker-compose.yml
 ├ vendor
 ├ composer.json
 └ composer.lock

3)設定ファイルの作成

config.php
.envファイルで環境変数の管理する方法もありますが、小規模なプロジェクトでシンプルな構成になるconfig.phpを使用した手順で行います

SMTPについての設定情報を定義

<?php
// 環境の判別
$is_local = ($_SERVER['SERVER_NAME'] == 'localhost' || $_SERVER['SERVER_ADDR'] == '127.0.0.1');

// デバッグモードの設定
define('DEBUG_MODE', $is_local); // ローカルではデバッグモードON、本番では OFF
// 本番環境で一時的にデバッグモードを有効化する場合
// define('DEBUG_MODE', true);  // コメントを外して使用

if ($is_local) {
    // ローカル環境(Docker)の設定
    define('SMTP_HOST', 'smtp.example.com');
    define('SMTP_USER', 'your_email@example.com');
    define('SMTP_PASS', 'your_password');
    define('SMTP_PORT', 587);
    define('SMTP_SECURE', 'tls');
} else {
    // さくらインターネット環境の設定
    define('SMTP_HOST', 'your_sakura_smtp_host');
    define('SMTP_USER', 'your_sakura_smtp_user');
    define('SMTP_PASS', 'your_sakura_smtp_password');
    define('SMTP_PORT', 587);
    define('SMTP_SECURE', 'tls');
}

.gitignore ファイルに secret_config.php を追加して、Git の管理対象から除外します。

.gitignore

config.php

別途 FTP でサーバーにアップロード

project-dir
 ├ Dockerfile
 ├ docker-compose.yml
 ├ vendor
 ├ composer.json
 ├ composer.lock
 ├ .gitignore
 └ config.php ※本番環境ではウェブルート外で、直接アクセスされない階層に配置します

4)表示ファイル作成

project-dir
 ├ Dockerfile
 ├ docker-compose.yml
 ├ vendor
 ├ composer.json
 ├ composer.lock
 ├ .gitignore
 ├ config.php
 ├ index.php
 ├ confirm.php
 ├ thanks.html
 └ contact.css

index.php

<?php
session_start();
$error = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    // フォームの入力値を取得
    // $post 配列は $_POST 配列とほぼ同じ構造を持ちます。
    // キーは同じで、値がフィルタリングされたものになります。
    $post = filter_input_array(INPUT_POST, $_POST);

    // フォームの送信時エラーチェック
    if ($post['name'] === '') {
        $error['name'] = 'blank';
    }
    if ($post['email'] === '') {
        $error['email'] = 'blank';
    } elseif (!filter_var($post['email'], FILTER_VALIDATE_EMAIL)) {
        $error['email'] = 'email';
    }
    if ($post['contact'] === '') {
        $error['contact'] = 'blank';
    }

    if (count($error) === 0) {
        // エラーがない場合、確認画面へリダイレクト
        $_SESSION['form'] = $post;
        header('Location: confirm.php');
        exit();
    }
} else {
    if (isset($_SESSION['form'])) {
        // 戻るボタンから戻ってきた場合、セッションに保存された値をフォームに表示
        $post = $_SESSION['form'];
    }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>お問合せフォーム</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="contact.css">
</head>
<body>
    <!-- お問合せフォーム画面 -->
    <div class="container">

        <!-- エラー時を考えると、formデータ送信後の遷移先は
        自身のファイルに再度遷移するほうが
        通常シンプルな作りになります -->
        <form action="" method="POST" novalidate>
            <p>お問い合わせ</p>
            <div class="form-group">
                <div class="row">
                    <div class="col-md-4">
                        <label for="inputName">お名前 <span class="require_item">必須</span></label>
                    </div>
                    <div class="col-md-8">
                        <input type="text" name="name" id="inputName" class="form-control" required autofocus
                        value="<?php echo htmlspecialchars($post['name']); ?>">
                        <?php if ($error['name'] === 'blank') : ?>
                            <p class="error_msg">※お名前をご記入下さい</p>
                        <?php endif; ?>
                    </div>
                </div>
            </div>

            <div class="form-group">
                <div class="row">
                    <div class="col-md-4">
                        <label for="inputEmail">メールアドレス <span class="require_item">必須</span></label>
                    </div>
                    <div class="col-md-8">
                        <input type="email" name="email" id="inputEmail" class="form-control" required
                        value="<?php echo htmlspecialchars($post['email']); ?>">
                        <?php if ($error['email'] === 'blank') : ?>
                            <p class="error_msg">※メールアドレスをご記入ください</p>
                        <?php endif; ?>
                        <?php if ($error['email'] === 'email') : ?>
                            <p class="error_msg">※メールアドレスを正しくご記入ください</p>
                        <?php endif; ?>
                    </div>
                </div>
            </div>

            <div class="form-group">
                <div class="row">
                    <div class="col-md-4">
                        <label for="inputContent">お問い合わせ内容 <span class="require_item">必須</span></label>
                    </div>
                    <div class="col-md-8">
                        <textarea name="contact" id="inputContent" rows="10" class="form-control" required><?php echo htmlspecialchars($post['contact']); ?></textarea>
                        <?php if ($error['contact'] === 'blank') : ?>
                            <p class="error_msg">※お問い合わせ内容をご記入ください</p>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
            <div class="row mt-5">
                <div class="col-8 offset-4">
                    <a href="javascript:history.back();" class="btn btn-secondary mr-2">戻る</a>
                    <button type="submit" class="btn btn-primary">確認画面へ</button>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

confirm.php

<?php
ob_start();  // 出力バッファリングを開始
session_start();

// 設定ファイルの読み込み
if (strpos(__DIR__, '/home/siennahare23') !== false) {
    // さくらインターネット環境
    require_once '/home/siennahare23/config/config.php';
} else {
    // ローカル環境
    require_once __DIR__ . '/config.php';
}

error_log('Current working directory: ' . getcwd());

// PHPMailer の読み込み
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

if (!isset($_SESSION['form'])) {
    header('Location: index.php');
    exit();
} else {
    $post = $_SESSION['form'];
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $mail = new PHPMailer(true);

    try {
        // デバッグ設定(問題解決後に削除してください)
        $mail->SMTPDebug = 3;
        $mail->Debugoutput = 'error_log';

        $mail->CharSet = 'UTF-8';
        $mail->Encoding = 'base64';

        // メールサーバの設定
        $mail->isSMTP();
        $mail->Host       = SMTP_HOST;
        $mail->SMTPAuth   = true;
        $mail->Username   = SMTP_USER;
        $mail->Password   = SMTP_PASS;
        $mail->SMTPSecure = SMTP_SECURE;
        $mail->Port       = SMTP_PORT;

        // ↓お問い合わせフォームの内容をメールで送信する処理↓

        // 送信元(システムのメールアドレス)
        $mail->setFrom(SMTP_USER, 'お問い合わせフォーム自動返信メール');

        // 送信元のメールアドレス
        // フォームに入力されたメールアドレスを送信元として
        // 使用することが原因でメールが拒否される可能性があるため
        // $toEmail = $is_local ? SMTP_USER : (defined('TO_EMAIL') ? TO_EMAIL : SMTP_USER);
        $toEmail = $post['email'];
        if (filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
            $mail->addAddress($toEmail);
        } else {
            throw new Exception('Invalid TO_EMAIL address: ' . $toEmail);
        }

        // 管理者へのコピー送信(オプション)
        $adminEmail = SMTP_USER;
        if (filter_var($adminEmail, FILTER_VALIDATE_EMAIL) && $adminEmail !== $toEmail) {
            $mail->addCC($adminEmail, '管理者');
        }

        $mail->isHTML(false);
        $mail->Subject = 'お問い合わせがありがとうございます';
        $mail->Body = <<<EOT
{$post['name']} 様

お問い合わせありがとうございます。
以下の内容で承りました。

お名前:{$post['name']}
メールアドレス:{$post['email']}
お問い合わせ内容:
{$post['contact']}

後ほど、担当者より回答させていただきます。
EOT;

        $mail->send();

        // セッションを破棄
        unset($_SESSION['form']);
        header('Location: thanks.html');
        exit();
    } catch (Exception $e) {
        ob_end_clean();
        error_log("メール送信エラー: " . $e->getMessage());
        error_log("PHPMailer エラー: " . $mail->ErrorInfo);
        echo "メッセージを送信できませんでした。エラー: " . $e->getMessage() . "<br>";
        echo "PHPMailer エラー: " . $mail->ErrorInfo;
    }
}
ob_end_flush();  // バッファの内容を出力
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>お問合せフォーム</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="contact.css">
</head>
<body>
    <!-- お問合せフォーム画面 -->
    <div class="container">
        <form action="" method="POST">
            <p>お問い合わせ</p>
            <div class="form-group">
                <div class="row">
                    <div class="col-3">
                        <label for="inputName">お名前</label>
                    </div>
                    <div class="col-9">
                        <p class="display_item"><?php echo htmlspecialchars($post['name']); ?></p>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <div class="row">
                    <div class="col-3">
                        <label for="inputEmail">メールアドレス</label>
                    </div>
                    <div class="col-9">
                        <p class="display_item"><?php echo htmlspecialchars($post['email']); ?></p>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <div class="row">
                    <div class="col-3">
                        <label for="inputContent">お問い合わせ内容</label>
                    </div>
                    <div class="col-9">
                        <p class="display_item">
                            <?php echo nl2br(htmlspecialchars($post['contact'])); ?>
                        </p>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-9 offset-3">
                    <a href="/index.php" class="btn btn-secondary mr-2">戻る</a>
                    <button type="submit" class="btn btn-primary">送信する</button>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

thanks.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>お問合せフォーム</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="contact.css">
</head>
<body>
    <div class="container">
        <p>お問い合わせ</p>
        <div class="thanks_msg">
            <p>
                お問い合わせありがとうございました。<br>
                後ほど、担当者よりご連絡をさせていただきます。<br>
            </p>
            <img src="./img/thanksimg.png" alt="Thank you" class="img-fluid mb-4" style="max-width: 40%;">
            <div>
                <a href="index.php" class="btn btn-primary">トップページに戻る</a>
            </div>
        </div>
    </div>
</body>
</html>

conatact.css

/* 共通 */
.container {
    padding: 1.5% 8%;
}

form > p, .container > p {
    text-align: center;
    font-size: 24px;
    padding-bottom: 20px;
}

label {
    padding-top: 7px;
}

.form-group {
    margin-bottom: 0;
}

/* contact.html */
.form-group label {
    display: flex;
    align-items: center;
}

.require_item {
    display: inline-block;
    background-color: #dc3545;
    color: #fff;
    font-size: 0.75rem;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    margin-left: 0.5rem;
    vertical-align: middle;
}

.error_msg {
    font-size: 14px;
    color: #ff0000;
    height: 14px;
}

/* confirm.html */
.display_item {
    padding: 7px 0;
    margin-bottom: 30px;
}

a {
    color: #000;
    border: solid 0.5px rgb(173, 173, 173);
    background-color: #f7f7f7;
    padding: 3.5px 20px;
}

a:hover {
    text-decoration: none;
    color: #000;
}

/* thanks.html */
.thanks_msg {
    text-align: center;
    background-color: #c8fbff;
    padding:20px 0 50px;
}

参考サイト