- はじめに
- ファイル構成
- 各ファイルのコード
- config/config.php
- controllers/AppController.php
- controllers/ContactController.php
- controllers/CsrfController.php
- controllers/RecaptchaController.php
- models/ContactModel.php
- services/CSRFService.php
- services/CsvService.php
- services/DatabaseService.php
- services/EmailService.php
- services/RecaptchaService.php
- services/ValidationService.php
- routes.php
- まとめ
はじめに
本記事では、PHPとVue.jsを活用して構築した問い合わせフォームのシステムにおけるMVCアーキテクチャの完全版コードを紹介します。前回の記事では、MVCパターンへのリファクタリングについて解説しましたが、今回は掲載済みのコードに加えてその他のファイルもすべて紹介します。
これまでの詳細な説明は以下の記事を参照してください:
ファイル構成
以下が最終的なプロジェクト構成です。
api/
├── config/
│ └── config.php
├── controllers/
│ ├── AppController.php
│ ├── ContactController.php
│ ├── CsrfController.php
│ ├── ErrorController.php
│ └── RecaptchaController.php
├── logs/
├── models/
│ └── ContactModel.php
├── services/
│ ├── CSRFService.php
│ ├── CsvService.php
│ ├── DatabaseService.php
│ ├── EmailService.php
│ ├── RecaptchaService.php
│ └── ValidationService.php
├── storage/
├── .htaccess
├── routes.php
.htaccess
index.html
各ファイルのコード
config/config.php
<?php
return [
// reCAPTCHA 設定
'recaptcha' => [
'site_key' => 'your_site_key', // サイトキー
'secret_key' => 'your_secret_key', // シークレットキー
],
// メール設定
'email' => [
'admin' => 'admin@example.com', // 管理者メールアドレス
'from' => 'admin@example.com', // 差出人メールアドレス
],
// CSRF 設定
'csrf' => [
'token_expiry' => 3600, // トークン有効期限(秒単位)
],
// データベース接続設定
'database' => [
'host' => 'localhost', // データベースホスト
'username' => 'db_user', // ユーザー名
'password' => 'db_password', // パスワード
'name' => 'db_name', // データベース名
'charset' => 'utf8mb4', // 文字コード
],
// デバッグモード
'debug' => true, // デバッグモード(true:有効, false:無効)
// タイムゾーン設定
'timezone' => 'Asia/Tokyo', // タイムゾーン
// エラーログ設定
'log' => [
'error_log' => __DIR__ . '/../logs/error.log', // エラーログの保存先
],
// CORS設定
'cors' => [
'allowed_origins' => '*', // 許可するオリジン(* はすべて許可)
'allowed_methods' => ['GET', 'POST', 'OPTIONS'], // 許可するHTTPメソッド
'allowed_headers' => ['Content-Type', 'X-CSRF-TOKEN'], // 許可するヘッダー
],
];
controllers/AppController.php
<?php
class AppController {
/**
* @var array 設定データ
*/
protected $config;
/**
* コンストラクタ
* 設定の読み込みと初期ヘッダー設定を実施
*/
public function __construct() {
// 設定ファイルの読み込み
if (empty($this->config)) {
$this->config = require __DIR__ . '/../config/config.php';
}
$this->setCorsHeaders(); // CORS設定適用
$this->setSecurityHeaders(); // セキュリティヘッダー適用
$this->handlePreflight(); // OPTIONSリクエスト対応
}
/**
* 設定値取得メソッド
* - ドット記法でネストした設定値を取得可能
*
* @param string $key 設定キー(例: 'recaptcha.site_key')
* @param mixed $default デフォルト値(存在しない場合の返却値)
* @return mixed 設定値またはデフォルト値
*/
protected function getConfig($key, $default = null) {
$keys = explode('.', $key); // ドット記法に対応
$value = $this->config;
foreach ($keys as $k) {
if (isset($value[$k])) {
$value = $value[$k];
} else {
return $default;
}
}
return $value;
}
/**
* JSONレスポンス送信メソッド
* - APIからJSON形式でレスポンスを返す
*
* @param array $data レスポンスデータ
* @param int $statusCode HTTPステータスコード(デフォルト: 200)
*/
protected function sendJsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
/**
* エラーレスポンス送信メソッド
* - エラーメッセージとステータスコードを返す
* - デバッグモード時には追加情報を含める
*
* @param string $message エラーメッセージ
* @param int $statusCode HTTPステータスコード(デフォルト: 400)
* @param array $debugData デバッグ用追加情報
*/
protected function sendErrorResponse($message, $statusCode = 400, $debugData = []) {
$this->logError($message); // ログにエラーメッセージを記録
$response = ['success' => false, 'error' => $message];
if ($this->getConfig('debug', false) && !empty($debugData)) {
$response['debug'] = $debugData; // デバッグ情報追加
}
$this->sendJsonResponse(['success' => false, 'error' => $message], $statusCode);
}
/**
* 成功レスポンス送信メソッド
* - 成功フラグとデータを返す
*
* @param array $data レスポンスデータ
*/
protected function sendSuccessResponse($data = []) {
$this->sendJsonResponse(array_merge(['success' => true], $data));
}
/**
* CORSヘッダー設定
* - クロスオリジンリクエストを許可するヘッダーを設定
*/
protected function setCorsHeaders() {
header('Access-Control-Allow-Origin: ' . $this->getConfig('cors.allowed_origins', '*'));
header('Access-Control-Allow-Methods: ' . implode(',', $this->getConfig('cors.allowed_methods', ['GET', 'POST', 'OPTIONS'])));
header('Access-Control-Allow-Headers: ' . implode(',', $this->getConfig('cors.allowed_headers', ['Content-Type', 'X-CSRF-TOKEN'])));
}
/**
* セキュリティヘッダー設定
* - 基本的なセキュリティ対策ヘッダーを追加
*/
protected function setSecurityHeaders() {
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
}
/**
* HTTPメソッド制限
* - 許可されたHTTPメソッドのみを受け入れる
* - 不正なリクエストはエラーレスポンスで終了
*
* @param array $allowedMethods 許可するHTTPメソッド
*/
public function validateHttpMethod($allowedMethods) {
error_log('----------');
error_log('Method: ' . $_SERVER['REQUEST_METHOD']);
error_log('Allowed: ' . implode(', ', $allowedMethods));
error_log('Request URI: ' . $_SERVER['REQUEST_URI']);
error_log('Headers: ' . print_r(getallheaders(), true));
$input = file_get_contents('php://input');
error_log('Input: ' . $input);
if (!in_array(strtoupper($_SERVER['REQUEST_METHOD']), $allowedMethods)) {
error_log('拒否されたリクエスト: ' . $_SERVER['REQUEST_METHOD']);
$this->sendErrorResponse('許可されていないHTTPメソッドです', 405);
exit;
}
}
/**
* プリフライトリクエスト対応
* - OPTIONSメソッドに204ステータスで応答
*/
public function handlePreflight() {
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204); // No Content
exit;
}
}
/**
* エラーログ記録
* - エラーメッセージをログファイルに記録
*
* @param string $message エラーメッセージ
*/
protected function logError($message) {
$logPath = $this->getConfig('log.error_log', __DIR__ . '/../logs/error.log');
error_log('[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL, 3, $logPath);
}
/**
* デバッグログ記録
* - デバッグモード時のみログ出力
*
* @param string $message デバッグメッセージ
*/
protected function debugLog($message) {
if ($this->getConfig('debug', false)) {
error_log('[DEBUG][' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL);
}
}
}
controllers/ContactController.php
<?php
require_once 'AppController.php';
require_once __DIR__ . '/../models/ContactModel.php';
require_once __DIR__ . '/../services/EmailService.php';
require_once __DIR__ . '/../services/CSRFService.php';
require_once __DIR__ . '/../services/RecaptchaService.php';
require_once __DIR__ . '/../services/ValidationService.php';
require_once __DIR__ . '/../services/CsvService.php';
require_once __DIR__ . '/../services/DatabaseService.php';
/**
* 問い合わせフォーム コントローラ
* - 入力検証、CSRF対策、reCAPTCHA検証、データ保存、メール通知を管理
*/
class ContactController extends AppController {
/** @var ContactModel モデルインスタンス */
protected $contactModel;
/** @var EmailService メール送信サービス */
protected $emailService;
/** @var CSRFService CSRF対策サービス */
protected $csrfService;
/** @var RecaptchaService reCAPTCHA検証サービス */
protected $recaptchaService;
/** @var ValidationService 入力検証サービス */
protected $validationService;
/** @var CsvService CSV保存サービス */
protected $csvService;
/** @var DatabaseService データベース操作サービス */
protected $databaseService;
/**
* コンストラクタ
* - サービスやモデルの初期化
* - CORSとHTTPメソッド制限の設定
*/
public function __construct() {
parent::__construct(); // 親クラスの設定を適用
// OPTIONSリクエストを許可 (CORS対応)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-CSRF-TOKEN, Authorization');
http_response_code(204); // No Content
exit;
}
// HTTPメソッド検証 (POSTのみ許可)
if ($_SERVER['REQUEST_URI'] === '/api/contact') {
$this->validateHttpMethod(['POST']);
}
// サービスとモデルの初期化
$this->contactModel = new ContactModel($this->config);
$this->emailService = new EmailService($this->config);
$this->csrfService = new CSRFService();
$this->recaptchaService = new RecaptchaService($this->config);
$this->validationService = new ValidationService();
$this->csvService = new CsvService();
$this->databaseService = new DatabaseService($this->config);
}
/**
* 問い合わせフォームのリクエスト処理
* - 入力データの検証、保存、メール送信
*/
public function handleRequest() {
try {
// 入力データの取得
$data = json_decode(file_get_contents('php://input'), true);
// CSRFトークン検証
if (!$this->csrfService->validateCsrfToken($_SERVER['HTTP_X_CSRF_TOKEN'])) {
$this->sendErrorResponse('CSRFトークンが無効です。');
}
// reCAPTCHA検証
if (!$this->recaptchaService->validate($data['recaptcha_token'])) {
$this->sendErrorResponse('reCAPTCHA検証に失敗しました。');
}
// 入力データの検証とサニタイズ
$name = $this->validationService->sanitizeInput($data['name']);
$email = $this->validationService->validateEmail($data['email']);
$message = $this->validationService->sanitizeInput($data['message']);
if (!$email) {
$this->sendErrorResponse('メールアドレスが無効です。');
}
// ユーザー情報取得
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '不明';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '不明';
// CSV保存
$csvPath = __DIR__ . '/../storage/submissions.csv';
$timestamp = date('Y-m-d H:i:s');
$csvData = [$timestamp, $name, $email, $message, $ipAddress, $userAgent];
if (!$this->csvService->saveToCsv($csvPath, $csvData)) {
$this->sendErrorResponse('CSVファイルへの保存に失敗しました。');
}
// データベース保存
$sql = "INSERT INTO submissions (name, email, message, ip_address, user_agent) VALUES (?, ?, ?, ?, ?)";
$params = [$name, $email, $message, $ipAddress, $userAgent];
if (!$this->databaseService->insert($sql, $params)) {
$this->sendErrorResponse('データベース保存に失敗しました。');
}
// メール送信
$adminMailSent = $this->emailService->sendAdminNotification($name, $email, $message);
$userMailSent = $this->emailService->sendUserConfirmation($name, $email, $message);
if ($adminMailSent && $userMailSent) {
$this->sendSuccessResponse(['message' => 'お問い合わせを受け付けました。']);
} else {
$this->sendErrorResponse('メール送信に失敗しました。');
}
} catch (Exception $e) {
$this->sendErrorResponse('サーバーエラーが発生しました: ' . $e->getMessage(), 500);
}
}
}
controllers/CsrfController.php
<?php
require_once 'AppController.php';
/**
* CSRFトークン管理クラス
* - CSRFトークンの発行と取得を処理
*/
class CsrfController extends AppController
{
/**
* コンストラクタ
* - HTTPメソッドの制限を適用(GETおよびOPTIONSを許可)
*/
public function __construct()
{
parent::__construct();
}
/**
* CSRFトークンの取得メソッド
* - セッションを開始し、トークンが存在しない場合は新規生成
* - 生成されたトークンをJSONレスポンスで返却
*
* @return void JSON形式でCSRFトークンを返却
*/
public function getToken()
{
// HTTPメソッド制限をGETおよびOPTIONSに対応
$this->validateHttpMethod(['GET', 'OPTIONS']);
// セッションが開始されていない場合は開始
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// トークン生成
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // 32バイトのランダムデータを生成
}
// 成功レスポンスでトークンを返却
$this->sendSuccessResponse(['csrf_token' => $_SESSION['csrf_token']]);
}
}
controllers/RecaptchaController.php
<?php
require_once 'AppController.php';
/**
* reCAPTCHA管理クラス
* - サイトキーの取得とトークン検証の機能を提供
*/
class RecaptchaController extends AppController {
/**
* コンストラクタ
* - 親クラスの初期設定を適用
*/
public function __construct() {
parent::__construct(); // 親クラスの設定を適用
}
/**
* reCAPTCHAサイトキーを返すメソッド
* - フロントエンドでreCAPTCHAを利用するためのキーを提供
* - 許可されたHTTPメソッドはGETのみ
*
* @return void JSON形式でサイトキーを返却
*/
public function getSiteKey() {
// HTTPメソッド制限(GETのみ許可)
$this->validateHttpMethod(['GET']);
// 設定からreCAPTCHAサイトキーを取得
$siteKey = $this->getConfig('recaptcha.site_key');
if (!$siteKey) {
$this->sendErrorResponse('reCAPTCHAキーが設定されていません。');
}
// 成功レスポンスでキーを返却
$this->sendSuccessResponse(['site_key' => $siteKey]);
}
/**
* reCAPTCHAトークン検証メソッド
* - トークンの有効性をGoogleのAPIで確認
* - 許可されたHTTPメソッドはPOSTのみ
*
* @param string $token フロントエンドから提供されたreCAPTCHAトークン
* @return bool 検証成功時はtrue、それ以外はエラーレスポンスを返却
*/
public function validateRecaptcha($token) {
// HTTPメソッド制限(POSTのみ許可)
$this->validateHttpMethod(['POST']);
// シークレットキー取得
$secretKey = $this->getConfig('recaptcha.secret_key');
// Google API経由で検証
$response = @file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secretKey&response=$token");
// 通信エラーチェック
if (!$response) {
$this->sendErrorResponse('reCAPTCHAサーバーとの通信に失敗しました。');
}
// 結果解析
$result = json_decode($response, true);
if (!$result || !$result['success'] || $result['score'] < 0.5) {
$this->sendErrorResponse('reCAPTCHA検証に失敗しました。信頼スコアが低すぎます。');
}
// 検証成功
return true;
}
}
models/ContactModel.php
<?php
require_once __DIR__ . '/../services/DatabaseService.php';
/**
* 問い合わせデータモデルクラス
* - データベースへの問い合わせ情報の保存機能を提供
*/
class ContactModel {
/** @var DatabaseService データベース操作サービス */
protected $databaseService;
/**
* コンストラクタ
* - データベースサービスを初期化
*
* @param array $config データベース設定情報
*/
public function __construct($config) {
// データベースサービスを利用
$this->databaseService = new DatabaseService($config);
}
/**
* 問い合わせデータの保存
* - 問い合わせ情報をデータベースに挿入
*
* @param array $data 保存するデータ (name, email, message, ip_address, user_agent)
* @return bool 成功時はtrue、失敗時はfalseを返却
*/
public function saveContact($data) {
try {
// データ挿入SQL
$sql = "INSERT INTO contacts (name, email, message, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?)";
$params = [
$data['name'], // 名前
$data['email'], // メールアドレス
$data['message'], // メッセージ
$data['ip_address'], // IPアドレス
$data['user_agent'] // ユーザーエージェント
];
// データベースサービスを使用して挿入
return $this->databaseService->insert($sql, $params);
} catch (Exception $e) {
// エラーログ記録
error_log('ContactModel Error: ' . $e->getMessage());
return false;
}
}
}
services/CSRFService.php
<?php
class CSRFService {
/**
* CSRFトークン検証
* - セッション内のトークンとリクエストトークンを比較して一致を確認
*
* @param string $token クライアントから送信されたCSRFトークン
* @return bool トークンが一致すればtrue、不一致ならfalseを返却
*/
public function validateCsrfToken($token) {
// セッションが開始されていない場合は開始
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// デバッグ用ログ出力
error_log('セッションID: ' . session_id());
error_log('セッション内トークン: ' . ($_SESSION['csrf_token'] ?? '未設定'));
error_log('リクエストトークン: ' . $token);
// トークン比較
if (!isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $token)) {
error_log('CSRFトークンが不一致です');
error_log('セッションID: ' . session_id());
error_log('セッション内トークン: ' . ($_SESSION['csrf_token'] ?? '未設定'));
error_log('リクエストトークン: ' . $token);
return false;
}
return true;
}
/**
* CSRFトークン生成
* - セッション内にトークンが存在しない場合、新規に生成
*
* @return string 生成されたCSRFトークン
*/
public function generateCsrfToken() {
// セッションが開始されていない場合は開始
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// トークン生成
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // 32バイトのランダムデータを生成
}
return $_SESSION['csrf_token'];
}
}
services/CsvService.php
<?php
class CsvService {
/**
* CSVファイルへのデータ保存
* - 指定されたパスのCSVファイルにデータを追記
*
* @param string $filePath 保存先のCSVファイルパス
* @param array $data 保存するデータ (配列形式)
* @return bool 成功時はtrue、失敗時はfalseを返却
*/
public function saveToCsv($filePath, $data) {
try {
// CSVファイルを追記モードでオープン
$fileHandle = fopen($filePath, 'a');
if (!$fileHandle) {
throw new Exception('CSVファイルのオープンに失敗しました。');
}
// データをCSV形式で書き込み
if (fputcsv($fileHandle, $data) === false) {
throw new Exception('CSVファイルへの書き込みに失敗しました。');
}
// ファイルをクローズ
fclose($fileHandle);
return true;
} catch (Exception $e) {
// エラーログ記録
error_log('CSV保存エラー: ' . $e->getMessage());
return false;
}
}
}
services/DatabaseService.php
<?php
class DatabaseService {
/** @var PDO PDOインスタンス */
protected $pdo;
/**
* コンストラクタ
* - データベース接続を初期化
*
* @param array $config データベース設定情報
*/
public function __construct($config) {
// DSN(データソース名)の設定
$dsn = 'mysql:host=' . $config['database']['host'] . ';dbname=' . $config['database']['name'] . ';charset=utf8';
// PDOインスタンスの生成とエラーモード設定
$this->pdo = new PDO($dsn, $config['database']['username'], $config['database']['password']);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
/**
* データ挿入メソッド
* - SQLのINSERT文を実行
*
* @param string $sql 実行するSQLクエリ
* @param array $params バインドするパラメータ
* @return bool 実行結果(成功時はtrue、失敗時はfalse)
*/
public function insert($sql, $params) {
// SQLクエリの準備と実行
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
}
services/EmailService.php
<?php
class EmailService {
/** @var array 設定データ */
protected $config;
/**
* コンストラクタ
* - メール設定情報を初期化
*
* @param array $config メール設定情報
*/
public function __construct($config) {
$this->config = $config;
}
/**
* メール送信メソッド
* - 指定された宛先にメールを送信
*
* @param string $to 宛先メールアドレス
* @param string $subject メール件名
* @param string $message メール本文
* @return bool メール送信結果(成功時はtrue、失敗時はfalse)
*/
public function sendMail($to, $subject, $message) {
$headers = 'From: ' . $this->config['email']['from'];
return mail($to, $subject, $message, $headers);
}
/**
* 管理者への通知メール送信
* - 問い合わせ内容を管理者に通知
*
* @param string $name 送信者名
* @param string $email 送信者メールアドレス
* @param string $message 問い合わせ内容
* @return bool メール送信結果(成功時はtrue、失敗時はfalse)
*/
public function sendAdminNotification($name, $email, $message) {
$adminEmail = $this->config['email']['admin'];
$subject = 'お問い合わせフォームからのメッセージ';
$body = "名前: $name\nメールアドレス: $email\nメッセージ: $message";
return $this->sendMail($adminEmail, $subject, $body);
}
/**
* ユーザーへの確認メール送信
* - 問い合わせ受領確認をユーザーに送信
*
* @param string $name 送信者名
* @param string $email 送信者メールアドレス
* @param string $message 問い合わせ内容
* @return bool メール送信結果(成功時はtrue、失敗時はfalse)
*/
public function sendUserConfirmation($name, $email, $message) {
$subject = 'お問い合わせありがとうございます';
$body = "$name 様\n\nお問い合わせありがとうございます。\n以下の内容で受け付けました。\n\n----------------------\nお名前: $name\nメールアドレス: $email\nお問い合わせ内容:\n$message\n----------------------\n\n担当者より折り返しご連絡いたします。\n\nよろしくお願いいたします。\n";
return $this->sendMail($email, $subject, $body);
}
}
services/RecaptchaService.php
<?php
class RecaptchaService {
/** @var array 設定データ */
protected $config;
/**
* コンストラクタ
* - reCAPTCHA設定情報を初期化
*
* @param array $config reCAPTCHA設定情報
*/
public function __construct($config) {
$this->config = $config;
}
/**
* reCAPTCHAトークン検証
* - GoogleのAPIを利用してreCAPTCHAトークンの有効性を確認
*
* @param string $token クライアントから送信されたreCAPTCHAトークン
* @return bool トークン検証成功時はtrue、失敗時はfalseを返却
*/
public function validate($token) {
// GoogleのreCAPTCHA検証エンドポイント
$url = 'https://www.google.com/recaptcha/api/siteverify';
// APIリクエストを送信して結果を取得
$response = file_get_contents($url . '?secret=' . $this->config['recaptcha']['secret_key'] . '&response=' . $token);
$result = json_decode($response, true);
// 検証結果を返却
return $result['success'] ?? false;
}
}
services/ValidationService.php
<?php
class ValidationService {
/**
* 入力値のサニタイズ
* - HTMLタグ除去とエンティティ変換を行い、安全な文字列に変換
*
* @param string $input サニタイズ対象の入力値
* @return string サニタイズされた安全な文字列
*/
public function sanitizeInput($input) {
return htmlspecialchars(strip_tags($input), ENT_QUOTES, 'UTF-8');
}
/**
* メールアドレスの検証
* - 入力値が有効なメールアドレス形式かどうかを確認
*
* @param string $email 検証対象のメールアドレス
* @return bool|string 有効な場合はメールアドレス、無効な場合はfalseを返却
*/
public function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* 必須フィールドの検証
* - 入力値が空でないかを確認
*
* @param mixed $value 検証対象の値
* @return bool 空でない場合はtrue、空の場合はfalseを返却
*/
public function validateRequired($value) {
return !empty($value);
}
}
routes.php
<?php
// コントローラーの読み込み
require_once __DIR__ . '/controllers/ContactController.php';
require_once __DIR__ . '/controllers/CsrfController.php';
require_once __DIR__ . '/controllers/RecaptchaController.php';
require_once __DIR__ . '/controllers/ErrorController.php';
/**
* OPTIONSリクエストへの対応
* - CORSポリシー設定と事前検証リクエストに対応
*/
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-CSRF-TOKEN, Authorization');
http_response_code(204); // No Content
exit;
}
// HTTPメソッドとURIの取得
$method = $_SERVER['REQUEST_METHOD'];
$uri = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
// コントローラーのインスタンス化
$contactController = new ContactController();
$csrfController = new CsrfController();
$recaptchaController = new RecaptchaController();
$errorController = new ErrorController();
/**
* ルーティング処理
* - HTTPメソッドとURIの組み合わせによって適切な処理を呼び出す
*/
switch (true) {
// お問い合わせフォーム処理
case $method === 'POST' && $uri === 'api/contact':
$contactController->handleRequest();
break;
// CSRFトークン取得
case $method === 'GET' && $uri === 'api/csrf-token':
$csrfController->getToken();
break;
// reCAPTCHAキー取得
case $method === 'GET' && $uri === 'api/recaptcha-key':
$recaptchaController->getSiteKey();
break;
// reCAPTCHA検証
case $method === 'POST' && $uri === 'api/recaptcha-validate':
$recaptchaController->validateRecaptcha($_POST['token']);
break;
// 404エラーハンドリング
default:
$errorController->notFound();
break;
}
まとめ
本記事では、問い合わせフォームシステムの完全版コードを掲載しました。すべてのファイルを網羅することで、構築やカスタマイズに役立てることができます。今後は、テストコードやデプロイ手順についての記事も予定していますので、ぜひご期待ください!
コメント