PHP 中單引號(')與雙引號(")定義字串的差異是新手最常混淆、資深開發者也常忽視的細節。本文從底層解析機制、效能基準、變數插值規則、SQL 語句構建、轉義處理、HTML 混合輸出等維度全面解析,並給出現代 PHP 開發的最佳實踐建議。
| 特性 | 單引號 '...' | 雙引號 "..." |
|---|---|---|
| 變數解析 | 不解析,原樣輸出 | 解析變數(含陣列、物件屬性) |
| 轉義序列 | 僅 \' 和 \\ | 完整支援:\n \r \t \$ \" \\ 等 |
| 解析速度 | 極快(編譯期確定) | 稍慢(執行期解析) |
| 記憶體佔用 | 較小 | 稍大(需建立解析結構) |
| 適用場景 | 純靜態字串、不含變數、大量字串常數 | 需插值變數、含換行/定位字元、複雜表達式 |
<?php
$name = 'World';
echo 'Hello $name'; // Hello $name
echo "Hello $name"; // Hello World
?>
<?php
$arr = ['key' => 'value'];
echo "Value: $arr[key]"; // Value: value (僅限雙引號內,鍵名不加引號)
echo "Value: {$arr['key']}"; // Value: value (推薦寫法,清晰無歧義)
echo "Value: {$arr[\"key\"]}"; // Value: value
?>
<?php
class User { public $name = 'Tom'; }
$user = new User();
echo "Name: $user->name"; // Name: Tom
echo "Name: {$user->name}"; // Name: Tom (推薦)
?>
<?php
$arr = [['name' => 'A'], ['name' => 'B']];
$index = 1;
// 錯誤/歧義
// echo "Name: $arr[$index]['name']"; // Parse error 或錯誤結果
// 正確:大括號明確界定表達式邊界
echo "Name: {$arr[$index]['name']}"; // Name: B
// 方法呼叫、靜態屬性等
class Foo { const BAR = 'baz'; public static $static = 'static'; }
echo "Const: {Foo::BAR}"; // Const: baz
echo "Static: {Foo::$static}"; // Static: static
?>
<?php
$iterations = 5000000;
$str = 'Hello World';
// 靜態字串賦值
$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$a = 'static string';
}
$single = hrtime(true) - $start;
$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$a = "static string";
}
$double = hrtime(true) - $start;
// 變數插值
$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$a = "Hello $str";
}
$interp = hrtime(true) - $start;
echo "單引號靜態: " . ($single/1e6) . " ms\n";
echo "雙引號靜態: " . ($double/1e6) . " ms\n";
echo "雙引號插值: " . ($interp/1e6) . " ms\n";
// 典型結果:單引號靜態最快,雙引號靜態次之,插值最慢
// 差異在微秒級別,實際專案中通常非瓶頸
?>
原文範例使用字串拼接構建 SQL 極度危險(SQL 注入風險)。現代 PHP 必須使用預備語句:
<?php
// ❌ 危險:字串拼接(無論單雙引號)
// $sql = "SELECT * FROM users WHERE name = '$name'";
// ✅ 正確:PDO 預備語句
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute([':name' => $name]);
$results = $stmt->fetchAll();
// ✅ 正確:MySQLi 預備語句
$mysqli = new mysqli($host, $user, $pass, $db);
$stmt = $mysqli->prepare('SELECT * FROM users WHERE name = ?');
$stmt->bind_param('s', $name);
$stmt->execute();
$result = $stmt->get_result();
?>
若必須手動構建字串(如動態 WHERE 條件),使用陣列收集條件再 implode,並對值嚴格轉義:
<?php
function buildWhere(array $conditions, array &$params): string {
$clauses = [];
foreach ($conditions as $col => $val) {
if (is_array($val)) {
$placeholders = implode(',', array_fill(0, count($val), '?'));
$clauses[] = "$col IN ($placeholders)";
$params = array_merge($params, $val);
} elseif ($val === null) {
$clauses[] = "$col IS NULL";
} else {
$clauses[] = "$col = ?";
$params[] = $val;
}
}
return implode(' AND ', $clauses);
}
$params = [];
$where = buildWhere([
'status' => 'active',
'role' => ['admin', 'editor'],
'deleted_at' => null,
], $params);
// $where = "status = ? AND role IN (?,?) AND deleted_at IS NULL"
// $params = ['active', 'admin', 'editor']
$sql = "SELECT * FROM users WHERE $where";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
?>
| 序列 | 單引號 | 雙引號 | 說明 |
|---|---|---|---|
\' | ✅ | ❌ (單引號內無需轉義) | 單引號 |
\\ | ✅ | ✅ | 反斜線 |
\n | ❌ | ✅ | 換行 (LF) |
\r | ❌ | ✅ | 歸位 (CR) |
\t | ❌ | ✅ | 定位字元 |
\$ | ❌ | ✅ | 美元符號(防止變數解析) |
\" | ❌ | ✅ | 雙引號 |
\0 - \777 | ❌ | ✅ | 八進位字元 |
\x00 - \xFF | ❌ | ✅ | 十六進位字元 |
\u{XXXX} | ❌ | ✅ (PHP 7.0+) | Unicode 碼位 |
<?php
// Heredoc:雙引號行為(解析變數、轉義)
$sql = << '$startDate'
ORDER BY id DESC
LIMIT 10
SQL;
// Nowdoc:單引號行為(不解析、不轉義)PHP 5.3+
$template = <<<'TPL'
$title
$content
TPL;
// $title、$content 會原樣輸出,適合模板骨架
?>
<?php
// Controller
$user = getUser($id);
include __DIR__ . '/templates/user_profile.php';
<!-- templates/user_profile.php -->
<div class="profile">
<h1><?= htmlspecialchars($user->name) ?></h1>
<p><?= nl2br(htmlspecialchars($user->bio)) ?></p>
</div>
<?php
// 使用單引號包裹 HTML 區塊,變數用串接或 printf
echo '' .
'' . htmlspecialchars($title) . '
' .
'' . htmlspecialchars($content) . '
' .
'';
// 或使用 sprintf 可讀性較好
echo sprintf(
'%s
%s
',
htmlspecialchars($title),
htmlspecialchars($content)
);
// 雙引號版本(不推薦:易產生 XSS、難閱讀)
// echo "$title
$content
";
?>
\n \t 等控制字元。{$arr[$i]['key']}、{$obj->method()}。| 程式碼 | 結果 | 說明 |
|---|---|---|
'$var' | $var | 單引號不解析 |
"$var" | 變數值 | 雙引號解析 |
'\n' | \n (兩字元) | 單引號不轉義 |
"\n" | 換行字元 | 雙引號轉義 |
'\\' | \ | 單引號轉義反斜線 |
"\\" | \ | 雙引號轉義反斜線 |
'It\'s' | It's | 單引號內逃逸單引號 |
"It's" | It's | 雙引號內單引號無需逃逸 |