舊版 PHP → PHP 8.3/8.4 現代化工具
使用時機
當舊版 PHP 專案(通常是為 PHP 5.x 撰寫的自訂 CMS)必須:
- 在 PHP 8.3 / 8.4 上執行,且不進行完整重寫,以及/或者
- 部署至 共享主機 (cPanel/Plesk) 並透過 FTP,不使用 Docker。
典型症狀:mysql_*、ereg/eregi、<? 短標籤、PHP4 風格建構子、
裸字陣列鍵名 ($row[Id])、$$var['key']、sizeof(null)、MyISAM 匯出、
TemplatePower 模板、config/defines*.php 中的純文字憑證。
此技能提供的資產
| 資產 | 用途 |
|---|---|
scripts/compat.php | 相容層:mysql_*→mysqli、ereg*/split→preg、userErrorHandler |
scripts/fix_barewords.php | 使用 Tokenizer 安全地為裸字陣列鍵名加上引號 |
scripts/fix_variable_variables.php | 修正 PHP 8 $$var['key'] 統一變數語法中斷問題 |
scripts/check_signatures.php | 靜態 LSP 方法簽章檢查器 |
scripts/lint-all.sh | php -l 全面掃描 |
scripts/smoke-test.sh | 對 URL 列表進行 HTTP 煙霧測試 |
templates/defines.php | .env 載入器 + 資料庫常數(支援與不支援 Docker 均可) |
templates/env.example | .env 範本 (DB_* + FTP_*) |
templates/htaccess | 現代 Apache/PHP-FPM .htaccess |
templates/user.ini | 共享主機的 PHP-FPM 覆寫設定 |
templates/timthumb.php | 安全的 GD 縮圖產生器(取代易受攻擊的 TimThumb) |
deploy/deploy.sh | FTP 或 rsync 部署;排除開發檔案;寫入遠端 .env |
黃金法則
- 安全第一:先
git init+ 基準線提交。備份資料庫。 - 重構前先解除封鎖:相容層能讓整個程式碼庫立即運作。
- 單一中央資料庫層:首先交換/限制
dbClass;相容層會涵蓋原始的mysql_*呼叫。 - 早期安全性:一旦網站能正常顯示,立即移除危險工具和 TimThumb。
- 持續驗證:每批次修改後執行
php -l,然後對所有 URL 進行煙霧測試。
流程
0. 基準線
git init -q && git add -A && git commit -qm "baseline: legacy PHP"
find . -name '*.php' -not -path './node_modules/*' | wc -l
從資料庫/站點地圖中儲存一份真實的公開 URL 列表,供 scripts/smoke-test.sh 使用。
1. 安裝相容層
- 將
scripts/compat.php複製到例如includes/compat.php。 - 在啟動程式的最頂端載入它,在任何舊版程式碼之前:
include_once(__DIR__.'/../includes/compat.php'); - 它提供了
mysql_*、ereg/eregi/ereg_replace/eregi_replace/split、mysqli_report(MYSQLI_REPORT_OFF)(SQL 錯誤回傳false而非拋出例外)、 以及userErrorHandler()(抑制 PHP 5 舊版 E_NOTICE「未定義索引/變數」)。 - 註冊處理器一次:
if (function_exists('userErrorHandler')) set_error_handler('userErrorHandler');
2. 機械式修正(腳本)
php scripts/fix_barewords.php # 試執行
php scripts/fix_barewords.php --apply
php scripts/fix_variable_variables.php # 試執行
php scripts/fix_variable_variables.php --apply
scripts/lint-all.sh
然後修正 lint 仍然報告的問題:
- 短標籤:
<?→<?php(grep<\(?!php|=|xml))。 - PHP4 建構子:
function ClassName()→function __construct()。 - 花括號字串偏移:
$s{0}→$s[0]。 - 不合法的
break:在迴圈/switch 之外(PHP 8 中為致命錯誤)。 ${...}中的變數變數:為裸字加上引號 / 改用${$var['k']}。
3. 簽章、靜態與空值安全
php scripts/check_signatures.php
對於每個 MISMATCH,在子方法中加上預設參數(例如 $tabla = null)。
然後在執行階段針對出現的致命錯誤進行修正:
| 錯誤 | 修正 |
|---|---|
ArgumentCountError: too few arguments | 為尾隨參數提供預設值 |
$this 在靜態方法中 | 使用 self::/static:: 或傳入應用程式上下文 |
method_exists(null, ...) | is_object($x) && method_exists($x, ...) |
Cannot access offset of type string on string | 在 $x['k'] 之前重新排序 is_array($x) |
Undefined constant "X" | 為裸字鍵名/常數加上引號 |
sizeof/count/array_keys(null) | is_array($x) ? ... : ...,或 (array)$x |
foreach(null) | foreach ((array)$x as ...) |
Accessing static property X as non static | $this->prop → self::$prop |
4. 對真實資料庫執行並反覆疊代
將設定指向資料庫,然後請求每個 URL。每次測試修復一個致命錯誤,直到
scripts/smoke-test.sh 報告零失敗且日誌中沒有警告。
5. 安全性(及早處理)
- 使用
templates/timthumb.php取代 TimThumb。 - 刪除
scanner.php、chmod777.php、info.php、散落的除錯腳本。 - 讓
.htaccess拒絕.env、.git、config/defines.php、點檔案。 - 輪換已提交的資料庫密碼;將機密移至
.env。 - 建議使用 CSRF 令牌、CSP/HSTS、速率限制、預處理語句。
6. 設定與部署
- 使用
templates/defines.php(.env載入器)和templates/env.example。 - 使用
templates/htaccess和templates/user.ini。 - 使用
deploy/deploy.sh部署。
.env 系統
config/defines.php 從專案根目錄載入 .env,若無則回退到 getenv()
並最終使用安全預設值。這讓 Docker 能繼續運作(使用相同的 DB_* 鍵名),同時讓
FTP 部署只需編輯一個檔案。
.env(永不提交):
DB_HOST=localhost
DB_NAME=cpaneluser_termospa
DB_USER=cpaneluser_termospa
DB_PASS=super-secret
DB_SUBDIRECTORIO=
部署(無 Docker)
共享主機 (cPanel/Plesk) 檢查清單
- PHP:選擇 8.3(或 8.1+);啟用
mysqli、gd、mbstring、pdo_mysql。 - 資料庫:建立資料庫 + 使用者,匯入匯出檔案。對於 MySQL 8:
預期會出現SET SESSION sql_mode='NO_ENGINE_SUBSTITUTION';utf8_spanish_ci→utf8mb3_spanish_ci警告(或執行 utf8mb4 遷移)。 - 檔案:上傳至
public_html/(排除docker/、tools/、router.php、_DOCS/、.git/)。 - 權限:目錄
755,檔案644;recursos/cache/、.cache/、upload/需可寫入(775/777)。 - 設定:建立遠端
.env(deploy.sh 會執行此操作)並驗證DB_*。 - 在
.htaccess中拒絕.env的存取。
deploy.sh
./deploy.sh ftp # lftp 反向鏡像
./deploy.sh ftp --dry-run
./deploy.sh rsync # 透過 SSH 的 rsync
它會讀取 .env(FTP_HOST/USER/PASS/PATH/TLS,或 SSH_*),僅暫存執行時期
檔案,寫入一個僅限遠端且僅包含 DB_* 的 .env(這樣 FTP 憑證永遠不會
落在伺服器上),上傳,然後印出權限/資料庫步驟。
驗證檢查清單
-
scripts/lint-all.sh→ 全部乾淨 -
scripts/smoke-test.sh urls.txt→ 0 失敗 - 首頁 / 產品 / 專案 / 新聞 / 聯絡我們頁面能正常顯示
-
/sitemap.xml和/rss回傳有效的 XML -
/imagenes/thumbnail/...回傳圖片 (GD + rewrite) -
/administracion/login/載入;登入 + 內容管理員能運作 - 聯絡表單能提交
- 管理員圖片上傳能寫入
upload/ - 錯誤日誌中沒有
PHP Warning/Fatal
總會踩到的雷
mysqli_query在 PHP 8.1+ 預設拋出例外;請呼叫mysqli_report(MYSQLI_REPORT_OFF)讓舊有的or die()/if($q=...)能繼續運作。.htaccess中的php_flag/php_value在 PHP-FPM 下會導致 HTTP 500 — 請改用.user.ini。- PHP 8 的統一變數語法會默默中斷
$$var['key'](寫入錯誤的變數)— 請執行fix_variable_variables.php。 include()的相對路徑會根據腳本目錄/當前工作目錄解析;建議使用__DIR__。- PHP 8 中已移除:
mysql_*、ereg*、split、create_function、each、魔術引號。 - OPcache 會快取舊檔案:編輯後請重新啟動 FPM/Apache。
- UTF-8:使用
SET NAMES utf8mb4;新的開發工作建議使用 InnoDB +utf8mb4_unicode_ci。
同系列專案
這些 CMS 專案共享相同的架構。請執行相同的流程,僅需調整
啟動路徑和縮圖重寫目標。將 scripts/ 複製到目標專案的
tools/,然後從專案根目錄執行。