WordPress 外掛開發有一個獨特的問題:所有外掛跑在同一個 PHP process 裡。
你的外掛用了 guzzlehttp/guzzle 7.0,另一個外掛用了 guzzlehttp/guzzle 6.0。兩個外掛各自 composer install,但最後只有一個版本會被載入——先跑到的那個。如果版本不相容,就直接 fatal error。
這不是你能控制的,因為你不知道用戶裝了哪些外掛。
Mozart 的解法:把你的 vendor 依賴複製一份,全部加上你自己的 namespace 前綴,讓它們跟別人的完全不衝突。
問題的本質
PHP 的 class 是全域的。GuzzleHttp\Client 只能存在一個定義。
Mozart 把它改名成 YourPlugin\Dependencies\GuzzleHttp\Client,這樣就算別人也載入了原版的 GuzzleHttp\Client,兩者完全是不同的 class,互不干擾。
安裝
Mozart 本身有依賴,建議用 Docker 或 PHAR 隔離,避免 Mozart 自己的依賴污染你的 vendor:
| |
設定
在 composer.json 的 extra 加上 Mozart 設定:
| |
設定說明:
| 選項 | 說明 |
|---|---|
dep_namespace | 加在所有 namespace 前面的前綴 |
dep_directory | 處理後的檔案放哪裡 |
classmap_prefix | 沒有 namespace 的 class 加什麼前綴 |
packages | 要處理哪些套件(不填就處理全部 require) |
excluded_packages | 排除哪些套件 |
delete_vendor_directories | 處理完刪掉 vendor 裡的原始目錄 |
執行
| |
執行後,vendor-prefixed/ 目錄裡的程式碼全部改好了:
| |
所有 use 陳述式、型別提示、class_exists() 的字串都一起改,不會有漏網之魚。
搭配 Composer scripts 自動化
| |
這樣每次 composer install 或 composer update 之後,Mozart 自動執行。
在外掛裡使用
Mozart 處理完之後,你需要載入它產生的 autoloader,不再用原本 vendor 的版本:
| |
沒有 namespace 的 class
有些舊套件沒有用 namespace,例如:
| |
Mozart 把它改成:
| |
所有呼叫 new Container() 的地方也一起改成 new MyPlugin_Container()。
限制
不支援動態 class 名稱:如果程式碼裡有這種寫法,Mozart 追蹤不到:
| |
Mozart 自身的依賴問題:Mozart 本身也用了一些 library,如果 require 進專案可能跟你的依賴衝突,所以推薦用 Docker 或 PHAR 執行。
維護狀態:Mozart 還在維護(最新版 1.1.3),但社群已有不少人轉向 Strauss,它是從 Mozart fork 出來的,解決了幾個 Mozart 的已知問題。
小結
WordPress 外掛的依賴衝突問題沒有官方解法,Mozart 是目前最直接的工具。核心概念簡單:把依賴複製一份,namespace 加前綴,讓它跟別人的版本完全不是同一個 class。
如果你遇到 Mozart 的限制(常數前綴、file autoloader 支援、license 合規),可以考慮 Strauss,它在這幾個地方做了改進。
參考資源
- Mozart GitHub 儲存庫 — 原始碼與完整設定文件
- Strauss:Mozart 的 fork 繼承者 — 解決 Mozart 已知問題的替代方案
- Composer 官方文件:extra 欄位 — composer.json 的 extra 設定說明
- WordPress 外掛開發:依賴管理最佳實踐 — WordPress 官方外掛開發指引
