$this 是 PHP 物件導向的核心偽變數,代表「目前執行方法所屬的物件實例」。它不是普通變數,在引擎層有特殊處理。
<?php
$this = 'text'; // Fatal Error: Cannot re-assign $this
?>
編譯器在生成 ZEND_ASSIGN opcode 時檢測到目標為 $this,直接拋錯。
<?php
$name = 'this';
$$name = 'text'; // 執行期成功將 $this 設為 'text'
echo $this; // 輸出 'text' (但此時 $this 已非物件,後續呼叫方法會崩潰)
?>
詞法分析器只見 $$name,不認得是 $this;執行期 ZEND_ASSIGN_DIM / ZEND_ASSIGN_VAR 才解析實際變數名,故繞過編譯期保護。
每個函式/方法編譯產生一個 zend_op_array,其中 this_var 欄位標記:
0:非物件方法(全域函式、靜態方法)>0:編譯期偵測到方法內使用 $this,記錄其 CV (Compiled Variable) 索引靜態方法雖在類別內,但 this_var = 0,故無法使用 $this。
| 狀態 | 儲存位置 | 生命週期 |
|---|---|---|
| 全域傳遞 | EG(This) (Executor Globals) | 請求級,隨物件建立/銷毀更新 |
| 當前作用域 | EG(active_symbol_table)['this'] | 單次方法呼叫期間 |
| 現象 | 原因 | 解決 |
|---|---|---|
| Calling member function on null | 靜態呼叫非靜態方法:Foo::method() | 實例化後呼叫:(new Foo())->method() |
| Using $this when not in object context | Closure/箭頭函數未綁定 | $fn = fn() => $this->prop; (PHP 7.4+ 自動捕獲) 或 Closure::bind() |
| Cannot re-assign $this | 嘗試 $this = ... | 勿直接賦值;若需替換物件,重新實例化 |
| Property access on non-object | $this 被變數變數覆寫為非物件 | 避免在類別內使用 $$var 且 $var === 'this' |
// zend_compile.c - 編譯期檢查 void zend_check_this_assignment(zend_ast *ast) { if (ast->attr & ZEND_AST_IS_THIS) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot re-assign $this"); } } // zend_vm_execute.h - 執行期綁定 ZEND_VM_HANDLER(66, ZEND_INIT_METHOD_CALL, SPEC, SPEC) { zval *object = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R); Z_OBJ_P(object)->handlers->get_object_ptr(object); // 取得物件指標 EG(This) = Z_OBJ_P(object); // 存入全域 // ... 後續在 zend_execute_ex() 中複製到 active_symbol_table }
class Foo { public function bar(): self { return $this; } } 利用 IDE 靜態分析$this 給外部,可能導致不一致狀態$fn = function() { return $this->id; }; $bound = Closure::bind($fn, $obj, $obj::class);$this->db = $db ?? new RealDB(); 方便注入 Mock$this 看似簡單,實則貫穿 PHP 編譯器、虛擬機、物件模型核心。理解其「編譯期標記 → 執行期雙重綁定 → 作用域隔離」機制,能從根源解決「物件上下文遺失、靜態/非靜態混淆、Closure 綁定失效」等疑難雜症。