變數作用域四大類型
| 類型 | 宣告位置 | 可見範圍 | 生命週期 | 關鍵字 |
| 區域變數 | 函數/方法內部 | 僅限該函數內部 | 函數執行期間 | 無 |
| 全域變數 | 函數/類外部 (腳本頂層) | 全腳本 (需 global 關鍵字引入函數) | 腳本執行期間 | global |
| 靜態變數 | 函數內部 | 僅限該函數內部 | 腳本執行期間 (保持值) | static |
| 超全域變數 | PHP 內建 | 任何位置直接可用 | 請求期間 | 無 (預定義) |
區域變數:函數的私有空間
<?php
$x = 10; // 全域變數
function demo() {
$x = 20; // 區域變數,與外部 $x 無關
echo "函數內: $x\n"; // 20
}
demo();
echo "函數外: $x\n"; // 10 (未被修改)
// 參數也是區域變數
function add($a, $b) { // $a, $b 為區域變數
return $a + $b;
}
全域變數:跨函數共享狀態
global 關鍵字引入
<?php
$counter = 0;
function increment() {
global $counter; // 引入全域變數參考
$counter++;
echo "計數器: $counter\n";
}
increment(); // 1
increment(); // 2
echo "最終: $counter\n"; // 2
$GLOBALS 超全域陣列 (替代方案)
<?php
$config = ['debug' => true, 'version' => '1.0'];
function getConfig(string $key) {
// 直接存取 $GLOBALS,不需 global 關鍵字
return $GLOBALS['config'][$key] ?? null;
}
echo getConfig('debug'); // 1 (true)
echo getConfig('version'); // 1.0
⚠️ global vs $GLOBALS 差異
<?php
$var = 1;
function testGlobal() {
global $var;
$var = 2; // 修改全域變數
}
function testGlobals() {
$GLOBALS['var'] = 3; // 修改全域變數
}
testGlobal();
echo $var; // 2
testGlobals();
echo $var; // 3
// 關鍵差異:global 建立別名,$GLOBALS 直接操作陣列元素
function testReference() {
global $var;
$var = &$GLOBALS['var']; // 這行其實多餘,global 已建立別名
}
靜態變數:函數的「記憶」
<?php
function counter() {
static $count = 0; // 僅初始化一次
$count++;
return $count;
}
echo counter(); // 1
echo counter(); // 2
echo counter(); // 3
// 實用場景:單例模式、快取、遞歸深度追蹤
function getDbConnection() {
static $pdo = null;
if ($pdo === null) {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
return $pdo;
}
// 遞歸深度計數
function recursiveDepth($n) {
static $depth = 0;
$depth++;
if ($n <= 0) {
$result = $depth;
$depth = 0; // 重置
return $result;
}
return recursiveDepth($n - 1);
}
超全域變數:PHP 內建的全域狀態
| 變數 | 用途 | 範例 |
$GLOBALS | 所有全域變數的陣列 | $GLOBALS['myVar'] |
$_SERVER | 伺服器與執行環境資訊 | $_SERVER['REQUEST_URI'], $_SERVER['HTTP_USER_AGENT'] |
$_GET | URL 查詢字串參數 | $_GET['id'] |
$_POST | POST 請求體表單資料 | $_POST['username'] |
$_REQUEST | $_GET + $_POST + $_COOKIE 合併 | $_REQUEST['token'] |
$_SESSION | 會話資料 | $_SESSION['user_id'] |
$_COOKIE | Cookie 資料 | $_COOKIE['remember_me'] |
$_FILES | 上傳檔案資訊 | $_FILES['avatar']['tmp_name'] |
$_ENV | 環境變數 | $_ENV['APP_ENV'] |
$php_errormsg | 最後一個錯誤訊息 (已棄用) | - |
$http_response_header | HTTP 回應標頭 (file_get_contents 等) | - |
作用域解析規則:LEGB 法則 (PHP 版)
PHP 變數查找順序:
- Local (區域) - 函數內部宣告、參數
- Enclosing (外層函數) - PHP 不支援巢狀函數閉包捕獲 (需用
use)
- Global (全域) -
global $var 或 $GLOBALS['var']
- Built-in (超全域/內建) -
$_GET, $_POST 等
<?php
$globalVar = 'global';
function outer() {
$outerVar = 'outer';
// PHP 5.3+ 匿名函數可用 use 捕獲外層變數
$closure = function() use ($outerVar) {
global $globalVar;
echo "Closure: outer=$outerVar, global=$globalVar\n";
};
$closure();
}
outer();
// 輸出: Closure: outer=outer, global=global
類別屬性 vs 變數作用域
<?php
class Demo {
public $publicProp = 'public';
protected $protectedProp = 'protected';
private $privateProp = 'private';
public function method() {
// 類內部:$this->property 存取屬性
echo $this->publicProp; // 可
echo $this->protectedProp; // 可
echo $this->privateProp; // 可
// 區域變數
$local = 'local';
}
}
$obj = new Demo();
echo $obj->publicProp; // 可
// echo $obj->protectedProp; // Fatal: Cannot access protected
// echo $obj->privateProp; // Fatal: Cannot access private
// 靜態屬性:類級別,類似全域但有命名空間
class Counter {
public static $count = 0;
public static function increment() {
self::$count++; // 或 static::$count (晚期靜態綁定)
}
}
Counter::increment();
echo Counter::$count; // 1
最佳實踐與避坑指南
✅ 推薦做法
- 避免全域變數:使用依賴注入、單例、服務容器傳遞共享狀態
- 函數參數化:需要外部資料時透過參數傳入,而非
global
- 靜態變數用於快取/單例:函數級單例、資料庫連線快取
- 超全域變數僅在入口處存取:控制器/處理器層,不在業務邏輯/模型層直接用
$_POST
❌ 反模式
<?php
// 反模式 1:濫用 global
$db = new PDO(...);
function getUser($id) {
global $db; // 難測試、隱性依賴、耦合
return $db->query("SELECT * FROM users WHERE id=$id")->fetch();
}
// 反模式 2:超全域變數滲透到核心邏輯
function calculatePrice() {
$qty = $_POST['qty']; // 業務邏輯耦合 HTTP 請求
return $qty * 100;
}
// 反模式 3:靜態變數洩露狀態
function processBatch($items) {
static $processed = []; // 跨呼叫殘留,導致 Bug
foreach ($items as $item) {
$processed[] = $item;
}
return $processed;
}
✅ 正確模式:依賴注入
<?php
// 服務類
class UserRepository {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function findById(int $id): ?array {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch() ?: null;
}
}
// 使用:在組合根/入口注入
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$repo = new UserRepository($pdo);
$user = $repo->findById(123);
// 易測試:可傳入 Mock PDO
總結
- 區域變數:預設、最安全、函數內部私有
- 全域變數:
global 或 $GLOBALS 引入,慎用,優先依賴注入
- 靜態變數:
static 關鍵字,函數級持久化,適合快取/單例
- 超全域變數:
$_GET, $_POST, $_SESSION 等,僅在應用邊界層使用
- 類別屬性:
public/protected/private + $this->,靜態屬性用 self::/static::
- 核心原則:最小作用域、顯式依賴、避免隱性共享狀態