PHP 是一門讓人又愛又恨的語言。它極其破碎、充滿陷阱,卻擁有龐大的使用者群體,許多受訓的業餘開發者對它讚譽有加。雖然 PHP 官方團隊試圖修補部分問題,但我選擇不再糾結,本文將系統性地梳理其根本性缺陷,作為我對這門語言的最後總結。
想像你拿到一個工具箱,裡面工具齊全,乍看正常。但當你拿起螺絲起子,發現它有三個頭;錘子兩頭都是尖爪;鉗子表面光滑無齒。每件工具都古怪難用,卻又不完全廢棄。許多木匠用這套工具蓋房子,房子門是五邊形、屋頂倒塌,敲門門就倒了,木匠還怪你弄壞門。這就是 PHP 的真實寫照:表面功能完備,實則處處是坑,生態系統還以此為榮。
一門優秀的語言應具備:可預見性(人類邏輯能正確映射到機器)、一致性(相似概念語法相似)、簡潔性(不繼承舊語言糟粕)、可靠性(不製造新陷阱)、可調試性(出錯時能精準定位)。PHP 在所有維度上大面積失守。
PHP 早期明確針對非專業程序員,導致核心設計原則錯位:「什麼都有總比沒有好」。這不是設計原則,而是堆砌功能。它借鑑 Perl、C、C++、Java 卻四不像,例如 (int) 像 C 卻無 int 類型,命名空間用 \,陣列字面量用 [key => value] 獨樹一幀。弱類型自動轉換極其複雜,大量功能靠函數而非語法實現,只有類系統被迫引入新關鍵字。
一行 @fopen('https://example.com/not-existing-file', 'r') 的行為,竟取決於編譯參數 --disable-url-fopen-wrapper、php.ini 的 allow_url_fopen、scream.enabled、error_reporting、display_errors 等多層配置。語言充滿全域隱性狀態:mbstring 用全域編碼,func_get_arg 只對當前函數生效,錯誤處理預設全域,register_tick_function 設全域鉤子。無執行緒支援,併發編程極難。
json_decode 解碼失敗回傳 null,但 null 也是合法 JSON,必須每次檢查 json_last_error。strpos、array_search 找到位置 0 回傳 0,找不到回傳 false。PHP 將 false 隱性轉為 0,導致邏輯錯誤卻不報錯,除非每處用 === 判斷。這是語言級地雷區。PHP 核心開發者極少,多為業餘愛好者,懂行的人遷往他平台,導致「盲目領導盲目」。這是 PHP 最大的系統性風險。
== 不可靠:"foo" == TRUE、"foo" == 0 為真,但 TRUE != 0。大十六進制字串(如密碼雜湊)可能誤判相等。=== 比較值與型別,但物件只有同一實例才為真;== 比較物件屬性卻像 ===。比較大小更亂:NULL < -1 但 NULL == 0,排序結果不確定。?: 是左結合,導致嵌套邏輯與直覺相反(印出 horse 而非 train)。null。global 宣告,否則讀取時會悄悄建立同名區域變數遮蔽它,這在主流語言中極罕見。array()、list() 是特殊結構非函數,無法動態呼叫。(int) 非獨立語彙,var_dump(int) 會解析錯誤。include 只是 C 式 #include,無模組系統,引入檔案變數進入當前作用域,函數/類卻進全域。empty() 只接受變數,empty($a || $b) 直接解析錯誤,解析器為何要認識它?@ 抑制錯誤,源自 DOS 遺風。T_PAAMAYIM_NEKUDOTAYIM)。E_STRICT 幾乎無保護且無文檔;E_ALL 竟不含 E_STRICT。2 < "foo" 隱藏,foreach(2 as $foo) 是 Warning;但存取不存在類常數卻是 Fatal Error,呼叫未定義函數也是 Fatal Error。trigger_error 不能 try/catch,Exception 不能觸發 set_error_handler。無 finally(舊版),資源清理極其醜陋。echo、empty)實為語言構造,無法當函數用。int、string 等標量(早期版本),且提示實為類名,string 類根本不存在。getFoo。instanceof 是運算子卻晚加入。clone 是運算子;屬性存取 $obj->foo,靜態屬性 $obj::foo,主流語言無此設計。new、private、public、protected、static 全照搬 Java,動態語言多餘。private 方法,測試 Mock 極其痛苦。list(因 list() 是特殊語法),但 $foo->list() 合法。__autoload 中拋異常導致 Fatal Error。__construct 僅初始化;無預設初始化,呼叫 parent::__construct() 父類無定義會 Fatal。ArrayIterator),無法一級物件操作。strpos/str_rot13,php_uname/phpversion。to vs 2:ascii2ebcdic、bin2hex、deg2rad、strtolower、strtotime。base64_decode vs str_shuffle,var_dump vs create_function。array_filter($input, $callback) vs array_map($callback, $input),strpos($haystack, $needle) vs array_search($needle, $haystack)。usleep vs microtime;大小寫不敏感函數隨 i 位置變化。sort、rsort、asort、arsort、ksort、krsort、natsort、natcasesort、usort、uasort、uksort、array_multisort……ereg、eregi、mb_ereg、preg_match、strstr、strchr、stristr、strrchr、strpos、stripos、strrpos、strripos、mb_strpos……strstr/strchr、is_int/is_integer/is_long、sizeof/count、chop/rtrim、implode/join、die/exit……printf/vprintf、sprintf/vsprintf 成對存在,只因 call_user_func_array 太難用。out 參數,明明可回傳陣列或多值。*_error/*_last_error 函數,明明有異常機制 8 年了。mysql_real_escape_string 因 C API 存在,與 mysql_escape_string 重複。dba_nextkey 未先呼叫 dba_firstkey 直接段錯誤。32 位平台整數為有號 32 位,無自動 bigint 昇級,數學結果隨 CPU 架構不同。唯一選項用 GMP/BC 封裝函數。八進位 012=10,但 08 變 0 並吞噬後續數字。
唯可靠 ASCII。mbstring 處理普通字串,單一全域預設編碼,函數可指定編碼但依賴所有參數回傳值。內建字串函數處理 UTF-8 風險極高。無 ASCII 外大小寫比較概念(é != É)。變數插值不支援 $foo['key'],${foo[0][0]} 語法錯誤。
同時扮演列表、雜湊、有序集合、堆疊,實作細節不透明。=> 非運算子,只存在 array() 和 foreach。負索引不工作(-1 是合法鍵)。無短語法(5.4 才加 [])。array_diff 視陣列為集合僅比值忽略鍵序,導致 array("foo"=>123, "bar"=>456) 與 array("foo"=>456, "bar"=>123) 判定相等。排序函數就地操作不回傳新陣列(array_reverse 除外)。ArrayObject 包裝陣列像物件,但內建函數不認得它。
一般函數非資料,var_dump(strstr) 視為字串字面量。create_function 是 eval 封裝,建全域函數永不 GC,無閉包語境。__lambda_func 命名衝突致命錯誤。
php.ini 控制一切,覆蓋規則複雜,共享主機無法隔離配置。?> 外空白直接輸出,導致 headers already sent,慣例省略關閉標籤。.svn、上傳檔。mod_php 讓所有檔案成潛在入口,應用伺服器僅單一入口。AddHandler 導致 foo.php.txt 被當 PHP 執行,真實駭客利用案例屢見不鮮。身為「Web 語言」卻無:模組系統、XSS 過濾器(htmlspecialchars 不夠)、CSRF 保護、統一資料庫 API(PDO 仍需包裝各驅動)、路由系統、認證授權、開發伺服器、互動除錯、一致部署機制。
PHP 的問題不在單一缺陷,而在於系統性的設計缺失與社區文化。它以「容易上手」為餌,實則挖滿坑洞,迫使開發者花費大量精力繞過語言陷阱而非解決業務問題。若你認同上述分析,請嘗試 Python、Node.js、Go 等現代替代方案;若不認同,請繼續用 PHP 建立高質量專案證明我錯。我不要求語言完美,只求揚長避短,工具服務於人。