PHP 允許用一個變數的「值」作為另一個變數的「名稱」。語法:在變數前加雙美元符號 $$var。
<?php
$a = 'hello';
$$a = 'world'; // 等同於 $hello = 'world';
echo "$a ${$a}"; // hello world
echo "$a $hello"; // hello world
?>
此時存在兩個變數:$a (值: 'hello')、$hello (值: 'world')。
$$a[1] 有兩種解讀:
${$a[1]}:$a[1] 的值作為變數名${$a}[1]:$a 的值作為變數名,取該陣列索引 1解決:用大括號明確指定
<?php
$a = ['foo', 'bar'];
${$a[1]} = 'baz'; // $bar = 'baz'
${$a}[1] = 'qux'; // $foo[1] = 'qux'
?>
<?php
class Foo {
public $bar = 'I am bar.';
public $baz = ['apple', 'banana', 'cherry'];
}
$obj = new Foo();
$prop = 'bar';
echo $obj->$prop . "\n"; // I am bar.
$idx = 1;
echo $obj->$baz[$idx] . "\n"; // banana
?>
可變屬性名從「呼叫處的區域變數」查找,不是從物件內部。
| 場景 | 是否支援 | 說明 |
|---|---|---|
Superglobals ($GLOBALS, $_GET...) | ❌ | 語法錯誤 |
$this | ❌ | 特殊變數,不可動態命名 |
| 函數/方法名稱 | ✅ | $func = 'strlen'; $func('abc'); 可呼叫 |
靜態屬性 Class::$var | ✅ | $class = 'Foo'; $var = 'bar'; $class::$$var |
<?php
$fields = ['username', 'email', 'phone'];
foreach ($fields as $field) {
${'form_' . $field} = $_POST[$field] ?? '';
// 產生 $form_username, $form_email, $form_phone
}
?>
<?php
$config = [
'db_host' => 'localhost',
'db_name' => 'myapp',
'db_user' => 'root',
];
foreach ($config as $key => $value) {
define(strtoupper($key), $value);
// 或 ${$key} = $value; // 產生區域變數
}
?>
<?php
function render($tpl, $data) {
foreach ($data as $k => $v) {
${$k} = $v; // 將資料展開為區域變數
}
ob_start();
include $tpl;
return ob_get_clean();
}
// template.php: 你好,!
echo render('template.php', ['name' => '張三']);
?>
可變變數 + 雙引號字串解析 = 程式碼注入風險。
<?php
// 惡意輸入:${phpinfo()}
$a = 'phpinfo';
$a(); // 直接呼叫函數
// 進階:嵌套可變變數
$a = "phpinfo";
${$a()}; // 仍會執行 phpinfo()
// 極簡單行版(Webshell 原型)
$a = "${${phpinfo()}}";
// 執行流程:
// 1. 雙引號解析 -> 遇到 ${...} 執行內部運算式
// 2. 內層 ${phpinfo()} -> 呼叫 phpinfo() 並取得回傳值 (true/1)
// 3. 外層 ${1} -> 存取 $1 (不存在) -> 但 phpinfo() 已經執行了!
?>
<?php
$a = 'phpinfo()';
echo $a; // 輸出字串 "phpinfo()"
echo '$a'; // 輸出字串 "$a"
echo "$a"; // 輸出字串 "phpinfo()" (變數解析)
// 但若:
$a = 'phpinfo';
echo "$a()"; // 輸出 "phpinfo()" -- 只是字串拼接,不會執行
// 唯有在 ${...} 或 ${...()} 等「運算式語境」才會真正執行
?>
eval()、preg_replace('/e') 等動態執行語境if (in_array($input, $allowed)) { $$input = ... }allow_url_include、enable_dl 等危險設定| 需求 | 舊寫法 (可變變數) | 現代寫法 |
|---|---|---|
| 動態屬性存取 | $obj->$prop | $obj->{$prop} (明確大括號) + 型別提示 |
| 動態方法呼叫 | $obj->$method() | $obj->{$method}() + method_exists() 檢查 |
| 動態陣列鍵 | ${$key} | $data[$key] (陣列天生支援動態鍵) |
| 設定管理 | 可變變數展開 | 單一 Config 物件 / 陣列 / 環境變數 |
可變變數是 PHP 獨特的動態特性,理解其運作機制有助於閱讀舊代碼、除錯奇異錯誤。但新專案應盡量避免使用,改用陣列、物件、依賴注入等結構化方式,並嚴格防範動態執行注入風險。