[PHP+MySQL]DSNの駄目なスペースはどれか

# はじめに
開発・サーバー保守メインで、補助的にプログラミング学習の質問対応も担当しているのですが、

- 「DBに接続出来ているのにデータが入りません！」
- 「接続でエラーは出てないんですけど…」
- 「var_dumpで見たらPDOのオブジェクトはできていたんですが…」

などの相談を受けて、

- 接続エラーになっていないが結果が得られていない。
- DSNのスペースを詰めると解消する。

という現象にしばしば遭遇します。
毎回「DSN情報にスペースがあると正しく動かない時があるので、スペースを無くしてみてください。」と曖昧に答えることになるので、スペースがあると**エラーにならないが正常に動作しない**のは具体的にどんな時なのかを調べてみました。

# テスト用プログラム
```php5:dsn_test.php
$user= "hoge";
$pass= "huga";
$dsn = "mysql:dbname=DBNAME;host=localhost;charset=utf8mb4";//パターンは後述
header('Content-type: text/plain; charset=utf-8');
try{
    $pdo = new PDO($dsn,$user,$pass);
}catch (PDOException $e) {
    echo '接続失敗: ' . $e->getMessage()."\n";
}
echo "----PDO var_dump結果----\n";
var_dump($pdo);
echo "----SHOW TABLESしてとりあえず1項目をvar_dump----\n";
$stmt = $pdo->query('SHOW TABLES');
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($result[0]);
```

## PHP 7.1 DSNの内容と挙動
```php:PHP7.1でDSN部分の試したもの一覧
$dsn = "mysql:dbname=DBNAME;host=localhost;charset=utf8mb4";
// 正常に接続・結果取得できる。
$dsn = "mysql:dbname=DBNAME; host=localhost;charset=utf8mb4";
// 正常に接続・結果取得できる。
$dsn = "mysql:dbname=DBNAME ;host=localhost;charset=utf8mb4";
// エラー。DB名にスペースが入ってしまい、接続に失敗する。
$dsn = "mysql:dbname= DBNAME;host=localhost;charset=utf8mb4";
// エラー。DB名にスペースが入ってしまい、接続に失敗する。
$dsn = "mysql:dbname =DBNAME;host=localhost;charset=utf8mb4";
// ※ 接続段階ではエラーにならず、PDOインスタンスは存在するが、動作しない。
// Fatal error: Uncaught Error: Call to a member function fetchAll() on boolean
// 接続後$pdo->exec('USE `DBNAME`');でデータベースを指定すると正常に動作する。
$dsn = "mysql:dbname=DBNAME;host =localhost;charset=utf8mb4";
// ※ 正常に接続・結果取得できる。
$dsn = "mysql:dbname=DBNAME;host= localhost;charset=utf8mb4";
// エラーになる。ホスト名が不適切。
$dsn = "mysql:dbname=DBNAME;host=localhost ";
// エラーになる。ホスト名が不適切。
$dsn = "mysql:dbname=DBNAME;host=localhost; charset=utf8mb4";
// 接続・結果取得はできる。他の挙動から想像するとcharsetも正常と思われる。
$dsn = "mysql:dbname=DBNAME;host=localhost;charset =utf8mb4";
// 接続・結果取得はできる。charsetが正しく認識されているのかは怪しい？
$dsn = "mysql:dbname=DBNAME;host=localhost;charset= utf8mb4";
// エラーになる。charsetが不適切。
$dsn = "mysql:dbname=DBNAME;host=localhost;charset=utf8mb4 ";
// エラーになる。charsetが不適切。

$dsn = "mysql:dbname=DBNAME;　host=localhost;　charset=utf8mb4";
// 意外にも接続・結果取得できる。
```

## PHP 5.2 DSNの内容と挙動
```php5:PHP5.2DSN部分の試したもの一覧
$dsn = "mysql:dbname=DATABASENAME;host=localhost";
// 正常に接続できる。
$dsn = "mysql:dbname=DATABASENAME; host=localhost";
// 正常に接続できる。
$dsn = "mysql:dbname=DATABASENAME ;host=localhost";
// エラー。DB名不適切で接続に失敗する。
$dsn = "mysql:dbname= DATABASENAME;host=localhost";
// エラー。DB名不適切で接続に失敗する。
$dsn = "mysql:dbname =DATABASENAME;host=localhost";
// 接続段階ではエラーにならず、PDOインスタンスは存在するが、動作しない。
$dsn = "mysql:dbname=DATABASENAME;host =localhost";
// 正常に接続できる。
$dsn = "mysql:dbname=DATABASENAME;host= localhost";
// エラー。ホスト名不適切で接続に失敗する。
$dsn = "mysql:dbname=DATABASENAME;host=localhost ";
// エラー。ホスト名不適切で接続に失敗する。
```

# 結果
- 「イコール=より後、セミコロン;または末尾まで」は、引用符で囲った文字列のように、空白も含めてその項目の内容扱いされる。このため、この区間にスペースがあると、内容の文字列自体が間違っている状態になりエラーになる。
- 「セミコロンから、次の項目名が始まるまで」の間にスペースがあるのは問題ない。~~やらないでほしいが~~全角スペースでも動く。
- 「**dbnameと直後のイコール=の間**」にスペースがある場合は、接続に成功するが操作できない。これはhostやcharsetの時は起きない。
    - 理由はデータベース選択部分が丸ごと無視されていて、```dbname=DBNAME;```を省略した場合と同様の状態になり、**データベース名を指定せずにDBサーバーに接続している**ため。
        - プログラムからDB未指定で接続する機会が無かったので、これに思い当たりませんでした。
 
## じゃあ、どう返答しようか？
- 「~~DSN情報の=の後はスペースも含めて内容の一部になるので、スペースが混ざったホストやDBになってしまい、エラーになります。また、dbname直後の=だけは、前にスペースがあるとデータベースサーバーへの接続自体は成功するものの、実はデータベース名の指定が無視されｔ(略)~~」
- 「dbnameはDBサーバー内のどのDBを使うかの指定ですが、ここの=の前にスペースがあると無視されてしまうので、選んでいるDBの操作として書かれた一般的なSQL文では操作に失敗します。」
- 「DSN情報の=の前後はスペースを入れないようにしましょう。詳しく書くと長いので割愛しますが、場所によっては上手く動きません。」
    - とりあえず質問者の理解度合いを見て「スペースを入れない」だけにして詳しい回答を求められたら答えるか、「失敗しているのはデータベース選択である」ことまで含むか使い分けることにします。

# 関連記事
- [PHPからMYSQLデータベースへアクセスできない - Qiita](https://qiita.com/SUGISHIA/items/cb103227904ea3073b38)
    - DSNのスペースで接続失敗事例の記事です。
- [PHPでデータベースに接続するときのまとめ - Qiita](https://qiita.com/mpyw/items/b00b72c5c95aac573b71)
    - この記事を久々に読み返していた際に「DB名を省略した場合」の状態であることに気づきました。
   
#関連リンク
- [PHP: MySQL (PDO) - Manual](http://php.net/manual/ja/ref.pdo-mysql.php)
- [PHP: PDO_MYSQL DSN - Manual](http://php.net/manual/ja/ref.pdo-mysql.connection.php)