ローカルDocker環境でPHPMailerを使用する初心者向けの手順
1)プロジェクトディレクトリ
project-dir
├dockerfile
└docker-compose.yml
2)Dockerfile作成
Dockerfileについて
Dockerfileに記述された指示に従って、Dockerイメージ(設計図)を作成します
▽Dockerfile
# PHP 7.4 と Apache がインストールされた公式イメージを使用
FROM php:7.4-apache
# システムの更新とZIP関連のライブラリをインストール
# libzip-dev: ZIPファイルを扱うためのライブラリ
RUN apt-get update && apt-get install -y libzip-dev
# PHPにZIP拡張機能をインストール
# これにより、PHPでZIPファイルの操作が可能になります
RUN docker-php-ext-install zip
# Composerのインストール
# Composerは、PHPの依存関係管理ツールです
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
3)docker-compose.yml作成
version: '3'
services:
web:
build: .
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
4)project-dirにて「docker-compose build
」「docker-compose up -d
」
docker-compose build
:- このコマンドは Dockerfile に基づいてイメージをビルドします。
docker-compose up -d
:- このコマンドはコンテナを起動し、バックグラウンドで実行します。
- Docker Compose ファイルで定義されたボリュームをマウントします。
※もし指定されたホスト側のディレクトリ(この場合はsrc
)が存在しない場合、Docker は自動的にそれを作成します。
「docker desktop」でContainer作成が確認できます
5)index.php send_mail.phpを作成
project_root/
├── Dockerfile
├── docker-compose.yml
└── src/
├── index.php
└── send_mail.php
▽index.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHPMailerテスト</title>
</head>
<body>
<h1>PHPMailerテスト</h1>
<form action="send_mail.php" method="post">
<label for="to">宛先:</label>
<input type="email" id="to" name="to" required><br><br>
<label for="subject">件名:</label>
<input type="text" id="subject" name="subject" required><br><br>
<label for="message">本文:</label><br>
<textarea id="message" name="message" rows="4" cols="50" required></textarea><br><br>
<input type="submit" value="送信">
</form>
</body>
</html>
▽send_mail.php
<?php
use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
try {
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com'; // SMTPサーバーを指定
$mail->SMTPAuth = true;
$mail->Username = 'yourmail@gmail.com'; // SMTPユーザー
$mail->Password = 'your app pass'; // SMTPパスワード
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
//送信元、送信先の設定
$mail->setFrom('yourmail@gmail.com', 'Mailer');
$mail->addAddress($_POST['to']);
//メール本文
$mail->isHTML(true);
$mail->Subject = $_POST['subject'];
$mail->Body = $_POST['message'];
$mail->send();
echo 'メッセージが送信されました';
} catch (Exception $e) {
echo "メッセージを送信できませんでした。Mailer Error: {$mail->ErrorInfo}";
}
}
6)コンテナ内でPHPMailerをインストールします:
docker-compose exec web composer require phpmailer/phpmailer
7)Googleアプリパスワード生成
Google→セキュリティ→2段階認証プロセス→アプリパスワードから設定
send_mail.phpを編集
注意)本番環境では、これらの設定を環境変数や別の設定ファイルに移動し、Gitなどのバージョン管理システムにコミットしないようにすることをおすすめします。
セキュリティ面
サニタイズについて
サニタイズ(sanitize)とは、ユーザーから入力されたデータを安全で使用可能な形式に変換することを指します。
セキュリティヘッダ
セキュリティを強化するために使用される特別なHTTPヘッダ
- header(“X-XSS-Protection: 1; mode=block”); //ブラウザの組み込みのXSS対策フィルターを有効にし、攻撃を検出したら、ページの読み込みをブロックします。
- header(“X-Frame-Options: SAMEORIGIN”); //ページを<frame>、<iframe>、<embed>、<object>で表示することを許可します。ただし、同じオリジンの場合のみ。(クリックジャッキング対策)
- header(“X-Content-Type-Options: nosniff”); //ブラウザがコンテンツタイプをスニッフィングしないようにします。
- header(“Referrer-Policy: strict-origin-when-cross-origin”); //クロスオリジンのリクエストに対しては、Referer ヘッダーにはリクエスト元のオリジンのみを含めます。
- header(“Content-Security-Policy: default-src ‘self’; script-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’; style-src ‘self’ ‘unsafe-inline’;”); //様々な攻撃(XSS、データ注入など)からサイトを保護します。
サニタイズ、セキュリティヘッダを導入のためPHPファイルを修正する
▽send_mail.php
<?php
// ob_start(); は、「出力を一時的に裏側で溜めておく」という命令
// もし途中でエラーメッセージが表示されたり、予期せぬ出力があったりすると、セキュリティヘッダーを設定できなくなるため
ob_start();
// セッションが開始されていない場合のみ、設定を変更してセッションを開始
if (session_status() == PHP_SESSION_NONE) { // セッション関連の設定
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // セッションを開始
session_start();
} else {
// セッションが既に開始されている場合は、そのまま続行
session_start();
}
// CSRFトークンがセッションに存在しない場合は生成
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// セキュリティヘッダーの設定
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';");
use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// CSRFトークンの検証
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// エラーをログに記録
error_log("CSRF token mismatch. POST token: " . ($_POST['csrf_token'] ?? 'not set') . ", Session token: " . ($_SESSION['csrf_token'] ?? 'not set'));
die('セッションが期限切れか無効です。ページを更新して再度お試しください。');
}
// 入力のサニタイズとバリデーション
$to = filter_var($_POST['to'], FILTER_SANITIZE_EMAIL);
$subject = htmlspecialchars($_POST['subject'], ENT_QUOTES, 'UTF-8');
$message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8');
// メールアドレスの妥当性チェック
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
die('無効なメールアドレスです。');
}
$mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
try {
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com'; // SMTPサーバーを指定
$mail->SMTPAuth = true;
$mail->Username = 'yourmail@gmail.com'; // SMTPユーザー
$mail->Password = 'your app pass'; // SMTPパスワード
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
//送信元、送信先の設定
$mail->setFrom('yourmail@gmail.com', 'Mailer');
$mail->addAddress($to);
//メール本文
$mail->Subject = $subject;
$mail->Body = $message;
$mail->AltBody = strip_tags($message); //HTMLタグを除去
$mail->send();
echo 'メッセージが送信されました';
} catch (Exception $e) {
// 詳細なエラー情報を隠し、ログに記録します。
error_log("メール送信エラー: " . $mail->ErrorInfo);
echo "メッセージを送信できませんでした。管理者にお問い合わせください。";
}
// 新しいCSRFトークンの生成
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} else {
// POSTメソッド以外でアクセスされた場合の処理
die('不正なアクセスです。');
}
// 出力バッファリングを終了し、出力を送信
ob_end_flush();
▽index.php
<?php
session_start();
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} // CSRFトークンがセッションに存在しない場合は生成
?>
機密情報を含む設定ファイルを作成
メリット
- セキュリティ向上:機密情報(パスワードなど)をGitリポジトリにコミットせずに済みます。
(.gitignoreに追加すると) - ポータビリティの向上:異なるサーバーや環境への移行が容易
1)プロジェクトのルートディレクトリに configフォルダその中にconfig.php
を作成します
project-dir\config\config.php
<?php
define('SMTP_HOST', getenv('SMTP_HOST') ?: 'smtp.example.com');
define('SMTP_USER', getenv('SMTP_USER') ?: 'user@example.com');
define('SMTP_PASS', getenv('SMTP_PASS') ?: 'password');
2)上記修正に伴いsend_mail.php、docker-compose.ymlも修正
▽send_mail.php
…
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
require_once __DIR__ . '/../config/config.php';
…
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = SMTP_HOST; // SMTPサーバーを指定
$mail->Username = SMTP_USER; // SMTPユーザー
$mail->Password = SMTP_PASS; // SMTPパスワード
$mail->SMTPAuth = true;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
▽docker-compose.yml
volumes:
- ./src:/var/www/html
- ./config:/var/www/config.php
3)2)の修正を行った後、Dockerコンテナを再ビルドして起動
docker-compose down
docker-compose build
docker-compose up -d
2)プロジェクトのルートディレクトリに .gitignore
ファイルを作成
▽.gitignore
/config/config.php
本番環境のさくらインターネットへアップロード
ローカルDocker開発環境、さくらインターネットの本番環境の両環境併用できるよう下記の通り修正
▽config.php
<?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', getenv('SMTP_HOST') ?: 'smtp.gmail.com');
define('SMTP_USER', getenv('SMTP_USER') ?: 'xxxx@gmail.com');
define('SMTP_PASS', getenv('SMTP_PASS') ?: 'xxxx');
define('SMTP_PORT', 587);
define('SMTP_SECURE', 'tls');
} else {
// さくらインターネット環境の設定
define('SMTP_HOST', 'xxxx');
define('SMTP_USER', 'xxxx');
define('SMTP_PASS', 'xxxx');
define('SMTP_PORT', 587);
define('SMTP_SECURE', 'tls');
}
// サイトの URL
define('SITE_URL', $is_local ? 'http://localhost:8080' : 'xxxx');
さくらインターネットのメール情報の確認方法の参考サイト
下記のホスト、ユーザ名、パスワードについて
- define(‘SMTP_HOST’, ‘xxxx’);
- define(‘SMTP_USER’, ‘xxxx’);
- define(‘SMTP_PASS’, ‘xxxx’);
さくらのメールボックスを PHPMailer で SMTP + STARTTLS で送信する時の注意点https://qiita.com/ameyamashiro/items/c7283bd1ec5dd3146ef9
▽send_mail.php
<?php
// ob_start(); は、「出力を一時的に裏側で溜めておく」という命令
// もし途中でエラーメッセージが表示されたり、予期せぬ出力があったりすると、セキュリティヘッダーを設定できなくなるため
ob_start();
// 設定ファイルの読み込み
if (strpos(__DIR__, '/home/siennahare23') !== false) {
// さくらインターネット環境
require_once '/home/siennahare23/config/config.php';
} else {
// ローカル環境
require_once __DIR__ . '/../config/config.php';
}
// セッションが開始されていない場合のみ、設定を変更してセッションを開始
if (session_status() == PHP_SESSION_NONE) {
// セッション関連の設定
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
// セッションを開始
session_start();
} else {
// セッションが既に開始されている場合は、そのまま続行
session_start();
}
// CSRFトークンがセッションに存在しない場合は生成
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// セキュリティヘッダーの設定
header("X-XSS-Protection: 1; mode=block"); //ブラウザの組み込みのXSS対策フィルターを有効にし、攻撃を検出したら、ページの読み込みをブロックします。
header("X-Frame-Options: SAMEORIGIN"); //ページを<frame>、<iframe>、<embed>、<object>で表示することを許可します。ただし、同じオリジンの場合のみ。(クリックジャッキング対策)
header("X-Content-Type-Options: nosniff"); //ブラウザがコンテンツタイプをスニッフィングしないようにします。
header("Referrer-Policy: strict-origin-when-cross-origin"); //クロスオリジンのリクエストに対しては、Referer ヘッダーにはリクエスト元のオリジンのみを含めます。
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"); //様々な攻撃(XSS、データ注入など)からサイトを保護します。
use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// CSRFトークンの検証
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// エラーをログに記録
error_log("CSRF token mismatch. POST token: " . ($_POST['csrf_token'] ?? 'not set') . ", Session token: " . ($_SESSION['csrf_token'] ?? 'not set'));
die('セッションが期限切れか無効です。ページを更新して再度お試しください。');
}
// 入力のサニタイズとバリデーション
$to = filter_var($_POST['to'], FILTER_SANITIZE_EMAIL);
$subject = htmlspecialchars($_POST['subject'], ENT_QUOTES, 'UTF-8');
$message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8');
// メールアドレスの妥当性チェック
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
die('無効なメールアドレスです。');
}
$mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
// デバッグモードの設定
// $mail->SMTPDebug = 3; // デバッグ出力を有効化
$mail->Debugoutput = 'html'; // デバッグ出力形式をHTMLに設定
try {
//SMTPサーバー:Gmailの設定
$mail->isSMTP();
$mail->Host = SMTP_HOST; // SMTPサーバーを指定
$mail->Username = SMTP_USER; // SMTPユーザー
$mail->Password = SMTP_PASS; // SMTPパスワード
$mail->SMTPAuth = true;
$mail->SMTPSecure = SMTP_SECURE;
$mail->Port = SMTP_PORT;
//送信元、送信先の設定
$mail->setFrom(SMTP_USER, 'Mailer');
$mail->addAddress($to);
//メール本文
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $message;
$mail->AltBody = strip_tags($message); //HTMLタグを除去
$mail->send();
echo 'メッセージが送信されました';
} catch (Exception $e) {
if (DEBUG_MODE) {
echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}<br>";
echo "Detailed error: " . $e->getMessage();
} else {
echo "メッセージを送信できませんでした。管理者にお問い合わせください。";
}
error_log("メール送信エラー: " . $mail->ErrorInfo);
}
// 新しいCSRFトークンの生成
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
} else {
// POSTメソッド以外でアクセスされた場合の処理
die('不正なアクセスです。');
}
// 出力バッファリングを終了し、出力を送信
ob_end_flush();
アップロード
<ローカルのディレクトリ構成>
project_root/
│
├── src/
│ ├── index.php
│ ├── send_mail.php
│ └── vendor/ ...
├── config/
│ └── config.php
│
└── docker-compose.yml
<本番環境のディレクトリ構成>
/home/xxxx/www/xxxx/
│
├── contact/
│ │
│ ├── index.php
│ ├── send_mail.php
│ └── vendor/ ...
│
└── config/
└── config.php
注意)デバッグモードの無効化