タイトル: エスケープ文字
SEOタイトル: PHP エスケープ文字 (シングル/ダブルクォート) 完全ガイド
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>ダブルクォート</strong>では <code>\n</code> / <code>\r</code> / <code>\t</code> / <code>\\</code> / <code>\$</code> / <code>\"</code> / <code>\xNN</code> / <code>\u{NNNN}</code> (PHP 7+) が使える</li><li><strong>シングルクォート</strong>は <code>\\</code> と <code>\'</code> の 2 つだけ。<code>\n</code> は<strong>そのまま文字列リテラル</strong>になる</li><li>HTML 出力時は <code>htmlspecialchars($str, ENT_QUOTES, "UTF-8")</code> で XSS 対策</li><li>SQL は <code>addslashes</code> や<strong>連結禁止</strong>。<code>PDO::prepare</code> + Prepared Statement で SQL Injection 防御</li><li>PHP コードを文字列に埋めるなら <code><?php</code> / <code>?></code> を <code><?php</code> 化、または heredoc/nowdoc を使う</li></ul></td></tr></table>
<h2>ダブルクォート vs シングルクォートの違い</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>エスケープ</th><th>シングル <code>'...'</code></th><th>ダブル <code>"..."</code></th><th>意味</th></tr></thead>
<tbody>
<tr><td><code>\n</code></td><td>そのまま \n</td><td>改行 (LF)</td><td>Line Feed (0x0A)</td></tr>
<tr><td><code>\r</code></td><td>そのまま \r</td><td>キャリッジリターン</td><td>CR (0x0D)</td></tr>
<tr><td><code>\t</code></td><td>そのまま \t</td><td>水平タブ</td><td>0x09</td></tr>
<tr><td><code>\v</code></td><td>そのまま</td><td>垂直タブ</td><td>0x0B</td></tr>
<tr><td><code>\f</code></td><td>そのまま</td><td>改ページ</td><td>0x0C</td></tr>
<tr><td><code>\0</code></td><td>そのまま</td><td>ヌル文字</td><td>0x00</td></tr>
<tr><td><code>\\</code></td><td>バックスラッシュ</td><td>バックスラッシュ</td><td>両方OK</td></tr>
<tr><td><code>\'</code></td><td>シングルクォート</td><td>そのまま \'</td><td>-</td></tr>
<tr><td><code>\"</code></td><td>そのまま \"</td><td>ダブルクォート</td><td>-</td></tr>
<tr><td><code>\$</code></td><td>そのまま \$</td><td>$ 記号(変数展開抑止)</td><td>-</td></tr>
<tr><td><code>\xNN</code></td><td>そのまま</td><td>16進文字 (例 \x41 → A)</td><td>-</td></tr>
<tr><td><code>\NNN</code></td><td>そのまま</td><td>8進文字</td><td>-</td></tr>
<tr><td><code>\u{NNNN}</code></td><td>そのまま</td><td>Unicode (PHP 7+)</td><td>例 \u{1F600} → 😀</td></tr>
</tbody>
</table>
<h2>具体例</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">// シングルクォート: \n はそのまま文字
echo 'Hello\nWorld';
// → Hello\nWorld (改行されない)
// ダブルクォート: \n は改行
echo "Hello\nWorld";
// → Hello
// World
// 変数展開
$name = 'Taro';
echo 'Hello, $name'; // Hello, $name (展開されない)
echo "Hello, $name"; // Hello, Taro
echo "Hello, \$name"; // Hello, $name (\$ でエスケープ)
// 配列・オブジェクト展開
$arr = ['name' => 'Taro'];
echo "Hello, {$arr['name']}"; // Hello, Taro
echo "Hello, $arr[name]"; // Hello, Taro (簡易記法、キーはクォートなし)
// Unicode (PHP 7+)
echo "\u{1F600}"; // 😀
echo "\u{3042}"; // あ
// 16進文字
echo "\x41\x42\x43"; // ABC</code></pre>
<h2>heredoc と nowdoc</h2>
<p>複数行の文字列リテラルは heredoc / nowdoc がベター:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">$name = 'Taro';
// heredoc - ダブルクォートと同じ動作(変数展開あり)
$html = <<<HTML
<div>
Hello, $name
</div>
HTML;
// nowdoc - シングルクォートと同じ動作(変数展開なし)
$raw = <<<'TEXT'
Path: C:\Users\$name
\nは改行しません
TEXT;
// インデント付き heredoc (PHP 7.3+)
function getHtml(): string {
return <<<HTML
<div>
<p>$name</p>
</div>
HTML;
// 終端タグのインデント分が全行から削除される
}</code></pre>
<h2>HTML エスケープ (XSS 対策)</h2>
<p>ユーザー入力をそのまま HTML に出すと XSS。必ず <code>htmlspecialchars()</code>:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">// ❌ XSS 脆弱
echo "<p>Hello, $name</p>";
// $name = "<script>alert(1)</script>" だと実行される
// ✅ htmlspecialchars で対策
echo "<p>Hello, " . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . "</p>";
// 第2引数のフラグ
// ENT_QUOTES - シングル+ダブル両方をエスケープ(推奨)
// ENT_HTML5 - HTML5 仕様(XHTML は ENT_XHTML)
// ENT_SUBSTITUTE - 不正バイト列を U+FFFD に置換
// ENT_DISALLOWED - 文書型で許可されない文字を置換
// 推奨フォーム
function h($s): string {
return htmlspecialchars($s ?? '', ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
}
echo "<p>" . h($name) . "</p>";
// htmlentities - htmlspecialchars + 全文字エンティティ化(重い、通常不要)
echo htmlentities('é', ENT_QUOTES, 'UTF-8'); // é</code></pre>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e8f5e9"><th>文字</th><th>エスケープ後</th></tr></thead>
<tbody>
<tr><td><code>&</code></td><td><code>&</code></td></tr>
<tr><td><code><</code></td><td><code><</code></td></tr>
<tr><td><code>></code></td><td><code>></code></td></tr>
<tr><td><code>"</code></td><td><code>"</code></td></tr>
<tr><td><code>'</code></td><td><code>'</code> (ENT_QUOTES)</td></tr>
</tbody>
</table>
<h2>SQL Injection 対策 (PDO Prepared Statement)</h2>
<p><strong>絶対に文字列連結で SQL を組まない</strong>のが鉄則:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">// ❌ SQL Injection 脆弱
$id = $_GET['id']; // "1 OR 1=1"
$pdo->query("SELECT * FROM users WHERE id = $id");
// ❌ addslashes だけでは不十分(マルチバイト攻撃に弱い)
$id = addslashes($_GET['id']);
$pdo->query("SELECT * FROM users WHERE id = '$id'");
// ✅ Prepared Statement で安全
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_GET['id']]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// ✅ 名前付きバインド
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
// LIKE 句のワイルドカードエスケープ
$keyword = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $keyword);
$stmt = $pdo->prepare("SELECT * FROM logs WHERE msg LIKE ? ESCAPE '\\\\'");
$stmt->execute(["%{$keyword}%"]);</code></pre>
<h2>JSON / URL エスケープ</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">// JSON
$arr = ['name' => 'Taro', 'msg' => 'Hello "World"'];
echo json_encode($arr); // {"name":"Taro","msg":"Hello \"World\""}
echo json_encode($arr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
// URL
$keyword = 'php 入門';
$url = 'https://example.com/search?q=' . urlencode($keyword);
// → ?q=php+%E5%85%A5%E9%96%80
// rawurlencode - スペースを + ではなく %20 に
$path = 'docs/' . rawurlencode($filename);
// パラメータ全部
$params = ['q' => $keyword, 'page' => 2];
$url = 'https://example.com/search?' . http_build_query($params);</code></pre>
<h2>非推奨: addslashes / stripslashes / Magic Quotes</h2>
<p>古い記事で出てくる <code>addslashes</code> 系は<strong>もう使わない</strong>でください:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">// ❌ 非推奨(古い解説に出てくる)
$safe = addslashes($input); // ' " \ \0 にバックスラッシュ追加
$orig = stripslashes($safe);
// ❌ Magic Quotes は PHP 5.4 で廃止
get_magic_quotes_gpc(); // 常に false (PHP 5.4+)、PHP 8 で関数自体削除
// ✅ 現代の対策
// SQL → PDO Prepared Statement
// HTML → htmlspecialchars
// JSON → json_encode
// シェル → escapeshellarg / escapeshellcmd</code></pre>
<h2>PHP コードを文字列に埋めたいとき</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php">// nowdoc が最強(変数展開もエスケープも不要)
$snippet = <<<'PHP'
<?php
$name = $_POST['name'] ?? '';
echo htmlspecialchars($name);
PHP;
// HTML として出すなら全てエスケープ
echo '<pre>' . htmlspecialchars($snippet, ENT_QUOTES, 'UTF-8') . '</pre>';</code></pre>
<h2>FAQ</h2>
<p><strong>Q: シングルとダブル、どちらを使うべき?</strong><br />
A: 変数展開・エスケープシーケンスが要らないなら<strong>シングル</strong>(わずかに高速)。展開したいときだけダブル。</p>
<p><strong>Q: 全角スペースもエスケープ必要?</strong><br />
A: 不要。HTML エスケープ対象は <code>&</code> / <code><</code> / <code>></code> / <code>"</code> / <code>'</code> のみ。</p>
<p><strong>Q: <code>str_replace</code> で自前エスケープを書くのは?</strong><br />
A: 危険。順序・抜けが起きやすい。必ず標準関数 <code>htmlspecialchars</code> / <code>json_encode</code> / <code>PDO::prepare</code> を使ってください。</p>