Contents
はじめに
PHP 8.2 では mysql_* 系関数は完全に削除され、代わりに mysqli と PDO (pdo_mysql) が推奨されています。両者はそれぞれ以下の特徴があります。
| 項目 | mysqli | PDO |
|---|---|---|
| API 形態 | 手続き型とオブジェクト指向の両方が利用可 | 完全にオブジェクト指向 |
| DB 抽象化 | MySQL 固有 | 複数データベースに対応(MySQL、PostgreSQL 等) |
| 例外処理 | mysqli_report() で例外化可能 |
デフォルトで例外 (PDOException) をスロー |
本稿では 安全性・可搬性・保守性 の観点から、両方の接続方法を示しつつ、実務で頻出するエラーとその対策を体系的に解説します。
mysqli と PDO の基本的な接続コード
1. mysqli(オブジェクト指向)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php $host = '127.0.0.1'; $port = 3306; $db = 'sample_db'; $user = 'app_user'; $pass = 'secret'; // エラーモードを例外に切り替える(推奨設定) mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); try { $mysqli = new mysqli(); // 文字列型と浮動小数点型の自動変換を有効化 $mysqli->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true); $mysqli->real_connect($host, $user, $pass, $db, $port); // 接続成功時はここにロジックを書く } catch (mysqli_sql_exception $e) { error_log( sprintf('MySQLi Connect Error [%d] %s', $e->getCode(), $e->getMessage()) ); exit('データベース接続に失敗しました。'); } ?> |
ポイント
real_connectの第5引数でポートを明示的に指定することで、Docker やカスタムポート環境でのミスを防げます。mysqli_report()を有効にすると、エラーが例外として捕捉できるため、if ($mysqli->connect_errno)の分岐を書き換える必要はありません。
2. PDO(MySQL 用 DSN)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php $dsn = 'mysql:host=127.0.0.1;port=3306;dbname=sample_db;charset=utf8mb4'; $user = 'app_user'; $pass = 'secret'; $options = [ // 例外でエラーを捕捉 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 取得結果は連想配列として返す PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // MySQL 固有属性:整数/浮動小数点のネイティブ型で取得 PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', ]; try { $pdo = new PDO($dsn, $user, $pass, $options); // 接続成功後はここにロジックを書く } catch (PDOException $e) { error_log( sprintf('PDO Connect Error [%s] %s', $e->getCode(), $e->getMessage()) ); exit('データベース接続に失敗しました。'); } ?> |
重要な修正点
charset=utf8mb4は 文字化け防止 と 正しいバイト列での比較 を保証しますが、直接的に SQL インジェクションを防ぐわけではありません。インジェクション対策は必ず プリペアドステートメント($stmt->prepare())や パラメータバインディング で実装してください。
よくある接続エラーと対策
| エラーコード | 主な原因 | 確認すべきポイント |
|---|---|---|
SQLSTATE[HY000] [2002] Connection refused |
MySQL が起動していない、ポートが閉じている、ホスト名が誤っている | netstat -tlnp/Docker のポートマッピング、my.cnf の bind-address |
SQLSTATE[HY000] [1045] Access denied for user … |
ユーザー名・パスワード不一致、ホスト制限、認証プラグインのミスマッチ | SELECT User, Host, plugin FROM mysql.user;、caching_sha2_password vs mysql_native_password |
SQLSTATE[HY000] [2006] MySQL server has gone away |
接続タイムアウト、max_allowed_packet 超過、サーバ再起動 |
wait_timeout、max_allowed_packet の設定値確認 |
対処フロー
- エラーメッセージを取得 → 例外オブジェクトのコード・メッセージをログに残す。
- カテゴリ判定(ネットワーク / 認証 / サーバ設定)で原因領域を絞る。
- 該当項目の設定を確認し、必要に応じて修正する。
環境別に注意すべき設定ミス
1. ローカル開発環境(MAMP / XAMPP 等)
| 誤り例 | 正しい対策 |
|---|---|
MySQL のデフォルトポートが 3306 と想定し、接続コードをそのまま使用する。 |
my.cnf で実際の port を確認し、PHP 側でも同じ番号に合わせる(例: XAMPP は 3307)。 |
.env に設定したパスワードと PHP のハードコーディングが食い違う。 |
環境変数を統一的に管理し、getenv() または vlucas/phpdotenv で読み込む。 |
2. Docker コンテナ
| 誤設定 | 正しい設定 |
|---|---|
docker run -p 3306:3306 mysql のみ実行し、コンテナ内部の MySQL が 127.0.0.1 にバインドしている。 |
my.cnf に bind-address = 0.0.0.0 を追加し、外部からの接続を許可する。 |
アプリ側 .env が空文字列になっているが、コンテナは環境変数でパスワードを設定している。 |
Docker Compose の environment: と PHP 側の .env を同一に保ち、docker compose exec php env で確認する。 |
3. クラウド(AWS RDS / Aurora 等)
| 誤り | 修正策 |
|---|---|
セキュリティグループでポート 3306 が閉じている、または DB インスタンスが「Publicly Accessible」になっていない。 |
SG のインバウンドに自分の IP/VPC CIDR を追加し、必要ならプライベートサブネットからのアクセスを許可する。 |
認証プラグインが caching_sha2_password だが、古いドライバーで接続できない。 |
PDO の場合は PHP 8.2 が対応済み。どうしても旧バージョンを使うなら ALTER USER … IDENTIFIED WITH mysql_native_password BY 'pwd'; で変更する。 |
トラブルシューティングの実践手順
1. PHP 拡張モジュールの有無確認
|
1 2 3 |
# CLI から確認 php -m | grep -E 'mysqli|pdo_mysql' |
phpinfo()の出力でも同様にmysqliとpdo_mysqlが表示されているかチェック。- 拡張が無効の場合は
php.iniにextension=mysqli、extension=pdo_mysqlを追加し、Web サーバを再起動。
2. MySQL 側設定項目の点検
| 項目 | 確認コマンド例 | 推奨値 / 注意 |
|---|---|---|
bind-address |
grep bind-address /etc/mysql/my.cnf |
0.0.0.0(外部接続許可) |
skip-networking |
同上 | コメントアウトまたは削除 |
max_connections |
SHOW VARIABLES LIKE 'max_connections'; |
必要に応じて増加 |
| SSL 設定 | SHOW VARIABLES LIKE '%ssl%'; |
本番環境は必ず有効化 |
| 認証プラグイン | SELECT User, Host, plugin FROM mysql.user WHERE User='app_user'; |
caching_sha2_password が推奨。旧バージョンが必要な場合は mysql_native_password に変更 |
3. ネットワーク診断
|
1 2 3 4 5 6 7 |
# Linux/macOS nc -zv 127.0.0.1 3306 # ポート到達性確認 telnet db.example.com 3306 # 接続テスト(結果が表示されれば OK) # Windows PowerShell Test-NetConnection -ComputerName db.example.com -Port 3306 |
pingは ICMP が許可されていないと失敗するため、必ず TCP ポート の到達性を確認してください。
4. 例外処理とロギングのベストプラクティス
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php use Monolog\Logger; use Monolog\Handler\StreamHandler; // Monolog 初期化(composer require monolog/monolog が前提) $log = new Logger('db'); $log->pushHandler(new StreamHandler(__DIR__.'/logs/db.log', Logger::ERROR)); try { $pdo = new PDO($dsn, $user, $pass, $options); } catch (PDOException $e) { // エラー情報を構造化してログに残す $log->error('DB Connect Error', [ 'code' => $e->getCode(), 'msg' => $e->getMessage(), 'file'=> __FILE__, 'line'=> __LINE__ ]); // エンドユーザーには汎用メッセージだけを表示 exit('データベース接続に失敗しました。管理者へお問い合わせください。'); } ?> |
mysqli_report()と組み合わせれば、mysqliでも同様の例外化が可能です。
5. デバッグ用ユーティリティ(開発時だけ使用)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php function dbg_mysqli($host, $user, $pass, $db, $port = 3306) { @$mysqli = new mysqli(); @$mysqli->real_connect($host, $user, $pass, $db, $port); if ($mysqli->connect_errno) { printf("MySQLi error (%d): %s\n", $mysqli->connect_errno, $mysqli->connect_error); } else { echo "MySQLi connection successful.\n"; } } function dbg_pdo($dsn, $user, $pass, array $opts = []) { try { $pdo = new PDO($dsn, $user, $pass, $opts); echo "PDO connection successful.\n"; } catch (PDOException $e) { printf("PDO error (%s): %s\n", $e->getCode(), $e->getMessage()); } } ?> |
- 本番環境にデプロイする際は必ず削除、もしくは
if (APP_DEBUG)で囲んで無効化してください。
接続エラー即応チェックリスト
| フェーズ | 実施項目 | 確認ポイント |
|---|---|---|
| A. エラーメッセージ取得 | mysqli_connect_error() / PDOException のコード・メッセージをログへ出力 |
SQLSTATE、errno が正しく記録されているか |
| B. 環境別原因特定 | ローカル/Docker/クラウドでそれぞれの設定項目を点検 | ポート・バインドアドレス・SG 設定が一致しているか |
| C. PHP 拡張確認 | php -m と phpinfo() で mysqli、pdo_mysql が有効か |
拡張が無い場合は extension= 行を追加し再起動 |
| D. MySQL 設定点検 | my.cnf の bind-address・skip-networking・認証プラグインを確認 |
外部接続が許可され、認証方式がクライアントと合致しているか |
| E. ネットワーク診断 | nc -zv host port や Test-NetConnection でポート到達性チェック |
ポートが開いていなければファイアウォール/Docker の EXPOSE を修正 |
| F. デバッグ実行 | 上記ユーティリティ関数で接続テストを実施 | エラーメッセージが出たら次の項目へ順に追う |
| G. ロギングと通知 | Monolog などでエラーを永続化し、CI/CD パイプラインで検知できるよう設定 | 本番環境ではスタックトレースや機密情報が漏れないようフィルタリング |
まとめと次のステップ
- 接続コードは例外化
-
mysqli_report()とPDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTIONを必ず設定し、エラーハンドリングを一元化します。 -
文字セットは正しく指定
-
charset=utf8mb4は文字化け防止とバイト列比較の正確性向上に寄与しますが、SQL インジェクション対策は必ず プリペアドステートメント で実装してください。 -
環境ごとの設定ミスをリスト化
-
ローカル、Docker、クラウドそれぞれの「ポート」「バインドアドレス」「アクセス権」のチェック項目をテンプレート化し、プロジェクト開始時に README に添付すると効果的です。
-
ロギングとモニタリング
-
Monolog でエラーログを永続化し、CI パイプラインや監視ツール(例: CloudWatch, Datadog)と連携させることで、接続失敗の早期検知が可能になります。
-
定期的な設定レビュー
- MySQL のメジャーバージョンアップや PHP バージョン更新時は、認証プラグイン・デフォルト文字セット・拡張モジュールの互換性を必ず確認してください。
今すぐできるアクション
| アクション | 内容 |
|---|---|
| チェックリスト作成 | 本稿の表をコピーし、プロジェクトごとの docs/DB_CONNECTION_CHECKLIST.md として保存。 |
| ロギング導入 | composer require monolog/monolog を実行し、上記サンプルコードをベースにエラーログ設定を追加。 |
| 環境変数統一 | .env.example に接続情報のキー(DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD)を列挙し、全開発者が同じ方式で取得できるようにする。 |
| Docker 設定見直し | docker-compose.yml の MySQL サービスに command: ["mysqld","--bind-address=0.0.0.0"] を追加し、外部からの接続を許可。 |
以上の手順とベストプラクティスを実装すれば、PHP 8.2 環境での MySQL 接続エラーは大幅に減少し、安定したサービス提供が可能になります。ぜひ本ガイドをチーム内で共有し、開発・運用フローに組み込んでください。