不用 Web 服務器也能跑 PHP?這事比你想的有意思 如果你寫了一段時間 PHP,腦子裏大概是這個畫面:
瀏覽器 → Web 服務器(Apache/Nginx)→ PHP → 返回 HTML 這條路徑實在太經典了,以至於很多人心裏,PHP 就等於 Web 開發。寫個腳本,扔到 public/ 或 htdocs/ 目錄,配個虛擬主機,然後通過 HTTP 訪問 —— 好像這就是運行 PHP 的唯一方式。
但其實不是這樣的。PHP 可以完全脱離 Web 服務器運行。
不需要 Apache,不需要 Nginx,甚至不需要瀏覽器。就在你的終端裏,直接運行。而且,這樣用起來還挺強大的。
這篇文章會聊聊,當你把 PHP 當作通用腳本語言(就像 Python 或 Node 那樣)來用時,會發生什麼。我們會寫一些實用的命令行工具,討論什麼時候適合這麼幹,以及為什麼這個” 非 Web 的 PHP 世界” 其實比聽起來有趣得多。
原文鏈接 不用 Web 服務器也能跑 PHP?這事比你想的有意思
等等,PHP 不用 Web 服務器? 核心觀點很簡單:
運行 PHP 不需要 Web 服務器,只需要裝個 PHP 解釋器。
如果你機器上有 PHP,在終端試一下:
php -v 看到版本號了?那就能用。現在創建一個 hello.php:
<?php echo "Hello from the command line!\n"; 然後運行它:
php hello.php 就這麼簡單。你剛才直接運行了一個 PHP 腳本,全程沒有 HTTP 請求,沒有 Apache,沒有 Nginx,沒有任何 Web 服務器。PHP 就像其他腳本語言一樣,直接讀文件、執行代碼。
底層的原理是這樣的:PHP 有不同的 SAPI(Server API),其中一個叫 CLI SAPI(Command Line Interface),專門為命令行設計,完全不依賴 Web 服務器。
這個發現可能看起來很顯而易見,但它會讓你的認知發生轉變:
PHP 不只是”Web 應用背後的那個東西”,它是一個完整的、可以做任何事情的通用解釋器。
命令行環境下 PHP 長什麼樣 在命令行運行 PHP,環境跟 Web 服務器下完全不一樣。
首先,那些熟悉的東西不見了:
沒有 _POST、$_COOKIE 沒有 $_SERVER['REQUEST_METHOD'] 或 $_SERVER['HTTP_HOST'] 根本就沒有 HTTP 請求和響應 取而代之的是更”Unix” 的環境:
標準輸入 / 輸出(STDIN、STDOUT、STDERR) 命令行參數(argc) 不同的 php.ini 配置(很多系統會有單獨的 php-cli.ini) 來看個實際的例子。
一個簡單的問候腳本 創建 greet.php:
<?php // $argv 是命令行參數數組 // $argv[0] 是腳本名 // argv[2]... 是參數 if ($argc < 2) { fwrite(STDERR, "用法: php greet.php \n"); exit(1); }
$name = name}!\n"; 運行:
php greet.php Alice
輸出: Hello, Alice!
在這個例子裏,我們:
從 $argv 讀取命令行參數 輸出到 STDOUT 錯誤信息輸出到 STDERR 失敗時返回非零退出碼(exit(1)),這是標準的 CLI 行為 到這裏,PHP 表現得更像 Bash 或 Python,而不是”CMS 背後的那個東西” 了。
為什麼這事挺有意思 乍一看,你可能會想:” 好吧,能在終端跑 PHP 腳本了,所以呢?”
但其實這比看起來有趣。它至少從三個方面改變了你對 PHP 的認知。
- 可以在 HTTP 之外複用你的 Web 應用邏輯 如果你有一個 Laravel、Symfony 或者自己寫的 PHP 應用,那你已經有了:
驗證規則 領域邏輯(比如計費規則、內容規則) 數據庫訪問和模型 發郵件、調 API 等服務 當你在命令行運行 PHP 時,可以啓動同樣的代碼庫,跑一些任務,完全不用通過 HTTP。比如:
隊列 Worker Cron 定時任務 批量導入 / 導出腳本 維護命令 不用把這些邏輯用另一門語言(Python、Bash 等)重寫一遍,全都用 PHP,代碼可以共享。
- 把 PHP 用於 DevOps 和自動化 一旦你接受 PHP 是個通用腳本語言,它就可以加入你的” 自動化工具箱”:
文件系統操作 調用 API 解析日誌 轉換 CSV 或 JSON 數據 生成報告 如果你團隊的主力語言是 PHP,這還能提高大家的參與度。不用為了寫個部署腳本或自動化工具就切換語言。
- 迫使你更深入理解 PHP 的運行時 不用 Web 服務器的工作方式,會暴露 PHP 實際上是怎麼運行的:
請求生命週期不再綁定 HTTP;它就是個進程 你開始考慮長時間運行的腳本 你會關心內存泄漏、資源管理、優雅關閉 這種更深的理解會反饋到你的 Web 開發技能上,因為你現在把 PHP 更多地看作一個進程,而不是”Apache 背後那個神秘的東西”。
實際使用場景 説完理論,來看幾個具體的例子。
- 快速搞定自動化任務 比如你有一文件夾的 .log 文件,想把所有包含 ERROR 的行提取出來,寫到 errors.txt 裏。
當然可以用 grep,但如果你想做點更復雜的處理 —— 解析時間戳、按錯誤碼分組之類的 —— 那寫個輕量級的 PHP 腳本會更方便:
<?php // parse-logs.php $inputDir = outputFile = $argv[2] ?? 'errors.txt';
if (!inputDir)) { fwrite(STDERR, "用法: php parse-logs.php [output-file]\n"); exit(1); }
outputFile, 'w'); foreach (scandir($inputDir) as
file, '.log')) { continue; } $path = $inputDir . DIRECTORY_SEPARATOR . $file;
path);
foreach ($lines as $line) {
if (str_contains($line, 'ERROR')) {
fwrite($handle, $file . ': ' . $line);
}
}
} fclose(outputFile}\n"; 運行:
php parse-logs.php /var/log/myapp PHP 瞬間變成日誌處理工具。
- Cron 任務和定時作業 Cron 最喜歡這種命令:
php /path/to/scripts/send-daily-report.php 在 send-daily-report.php 裏可以:
通過 PDO 連數據庫 生成昨天活動的摘要 直接發郵件,或通過郵件服務商 API 這比為了定時任務專門搞個” 隱藏 HTTP 端點” 清爽多了。
- 後台 Worker / 消費者 隊列無處不在:
處理圖片上傳 發通知 跑重計算 常見模式:
Web 應用把任務入隊(Redis、RabbitMQ、SQS 等) 一個長時間運行的 PHP 腳本作為 Worker,持續消費處理任務 偽代碼示例:
<?php // worker.php(簡化版,沒真正的 Redis 代碼) while (true) { job) { try { handle_job($job); } catch (Throwable
e); } } else { // 沒任務?短暫休眠避免 CPU 空轉 usleep(200000); // 0.2 秒 } } 雖然 PHP 以短生命週期 Web 請求聞名,但這種模式完全有效,生產環境在用。關鍵是仔細管理內存和資源。
- 開發工具和腳手架 可以構建內部工具:
“創建新模塊” 腳本 “生成樣板代碼” 腳本 項目初始化命令(創建配置文件、數據填充等) 這些工具通常:
提示用户輸入 操作文件和目錄 運行 shell 命令 小型腳手架腳本示例:
<?php // make-module.php $moduleName = $argv[1] ?? null;
if (!$moduleName) { fwrite(STDERR, "用法: php make-module.php \n"); exit(1); }
$baseDir = DIR . '/modules/' . $moduleName;
if (is_dir(moduleName} 已存在\n"); exit(1); }
mkdir(baseDir . '/index.php', "<?php\n\n// {$moduleName} 模塊入口\n");
echo "模塊 {baseDir}\n"; 構建真正的命令行應用 從玩具腳本到真正的 CLI 工具。
我們來構建一個簡單的任務管理器:
php tasks.php add "買牛奶" php tasks.php list php tasks.php done 2 用 JSON 文件存儲任務。
第 1 步:基礎結構 創建 tasks.php:
<?php const STORAGE_FILE = DIR . '/tasks.json';
function loadTasks(): array { if (!file_exists(STORAGE_FILE)) { return []; } $json = file_get_contents(STORAGE_FILE); json, true); return is_array($data) ? $data : []; }
function saveTasks(array tasks, JSON_PRETTY_PRINT)); }
function printUsage(): void { echo <<<USAGE 用法: php tasks.php list php tasks.php add "<描述>" php tasks.php done USAGE; } 提供了:
存儲文件(tasks.json) 加載 / 保存任務的輔助函數 打印用法説明的函數 第 2 步:處理命令 繼續擴展 tasks.php:
<?php // ... 前面的代碼 ...
function listTasks(array tasks)) { echo "還沒有任務 🎉\n"; return; } foreach ($tasks as $id => $task) { $status = $task['done'] ? '[x]' : '[ ]'; echo sprintf("%d. %s %s\n", $id, $status, $task['description']); } }
function addTask(array &$tasks, string $description): void { $tasks[] = [ 'description' => description}\n"; }
function markDone(array &$tasks, int tasks[
id} 不存在\n"; return; }
id]['done'] = true; echo "任務 {$id} 已標記為完成\n"; }
// ---------- CLI 入口 ---------- $argvCopy = argvCopy); // 去掉腳本名 $command = $argvCopy[0] ?? null;
$tasks = loadTasks();
switch (tasks); break;
case 'add':
$description = $argvCopy[1] ?? null;
if (!$description) {
echo "請提供任務描述\n";
printUsage();
exit(1);
}
addTask($tasks, $description);
saveTasks($tasks);
break;
case 'done':
$id = isset($argvCopy[1]) ? (int)$argvCopy[1] : null;
if ($id === null) {
echo "請提供任務 ID\n";
printUsage();
exit(1);
}
markDone($tasks, $id);
saveTasks($tasks);
break;
default:
printUsage();
exit(1);
} 現在可以:
php tasks.php add "寫 PHP 文章" php tasks.php add "喝咖啡" php tasks.php list php tasks.php done 0 php tasks.php list 你剛構建了一個小但真實的應用:
持久化狀態 有命令和子命令 表現得像其他任何 CLI 工具 重點是,完全不需要 Web 服務器。
進階:使用庫(symfony/console、Laravel Zero 等) 上面的例子故意極簡。實際應用中通常需要:
彩色輸出 參數和選項解析 幫助信息 子命令 交互式提示 可以手寫這些,但沒必要。有專門的庫:
symfony/console Laravel Zero Robo 各框架的 CLI(Laravel Artisan 等) 比如用 symfony/console:
通過 Composer 安裝:
composer require symfony/console 創建控制枱腳本,啓動 Console 應用並註冊命令
把命令寫成 PHP 類
你的命令會自動獲得:
自動生成 --help 樣式化輸出 輸入驗證 嵌套命令(appcreate 等) 重點不是記住這些 API,而是認識到:PHP CLI 生態很成熟。把 PHP 當 CLI 優先語言不是 hack,是主流的、被支持的模式。
不通過 HTTP 請求也能調 API 和數據庫 “不用 Web 服務器” 不等於” 不用網絡”。
CLI PHP 腳本仍然可以:
調 REST API 連數據庫 發消息到隊列 讀雲存儲 示例:從 API 獲取 JSON 用 file_get_contents 的簡單例子:
<?php // fetch-user.php $userId = $argv[1] ?? null;
if (!$userId) { fwrite(STDERR, "用法: php fetch-user.php \n"); exit(1); }
userId}";
url);
if ($json === false) { fwrite(STDERR, "獲取用户失敗\n"); exit(1); }
json, true); if (!is_array($data)) { fwrite(STDERR, "收到無效 JSON\n"); exit(1); }
echo "姓名: {data['email']}\n"; 可以從終端運行,作為自動化流水線的一部分。
示例:用 PDO 操作數據庫 連數據庫和 Web 代碼完全一樣:
<?php // count-users.php user = 'myuser'; $pass = 'mypassword';
dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);
$stmt = count = (int) $stmt->fetchColumn();
echo "總用户數: {$count}\n"; 掛到 cron job,你的” 報表系統” 就是幾個 PHP 腳本的事兒。
Web PHP 和 CLI PHP 的重要區別 不用 Web 服務器運行 PHP 感覺熟悉,但有些關鍵區別要記住。
- 不同的超全局變量 Web 上下文會用:
_POST、
_COOKIE、
_SERVER['REQUEST_URI'] 等 CLI 中:
這些通常是空的或無關緊要 用 argc、STDIN 如果複用 Web 代碼,可能需要重構邏輯,讓它不依賴 HTTP 特定的全局變量。好的模式是把領域邏輯和” 交付機制”(Web/CLI/API)分開。
- 不同的配置(php.ini vs php-cli.ini) 很多系統對 CLI 有單獨配置:
啓用 / 禁用擴展 內存限制 錯誤顯示設置 好處是:
CLI 可能需要更詳細的錯誤輸出 CLI 腳本可能允許更長的執行時間 如果” 瀏覽器裏能跑” 但 CLI 不行(反之亦然),留意這點。
- 長時間運行腳本和內存 Web 請求通常短生命週期。請求結束後,PHP 進程結束,內存釋放。
CLI 腳本,特別是 Worker,可能運行幾小時或幾天。這意味着:
必須更小心內存泄漏(如永不清理的大數組) 應該確保數據庫連接被複用或正確關閉 可能需要通過 supervisor 定期重啓 Worker(如 supervisord、systemd) 查看內存的簡單方法:
echo "內存使用: " . memory_get_usage(true) . " bytes\n"; 循環做大量處理時這很重要。
- 沒有” 自動” 的請求生命週期 Web 框架裏,很多生命週期自動處理:
中間件 路由 控制器 響應 CLI 裏,你自己掌控。既自由又多一點工作:
你設計自己的” 入口點” 你決定如何處理失敗和重試 你實現自己的結構或依賴庫 橋接 Web 和 CLI 世界 一個很好的模式是在 Web 和 CLI 入口之間共享同樣的框架和領域代碼。
比如:
Laravel:php artisan 就是應用的 CLI 前端,可以註冊命令複用模型、服務等 Symfony:bin/console 類似 —— 啓動 Symfony 內核的 CLI 自定義應用:可以創建 bootstrap.php 供兩者使用: // bootstrap.php <?php require DIR . '/vendor/autoload.php';
// 設置容器、配置、數據庫等 $container = MyApp\Bootstrap::createContainer();
return $container; CLI 腳本中:
// cli-script.php <?php /** @var Psr\Container\ContainerInterface container = require DIR . '/bootstrap.php';
$reportService = reportService->sendDailyReport(); 這樣:
業務邏輯在可複用的類裏 Web 控制器和 CLI 腳本只是適配器 Web 服務器變成觸發 PHP 代碼的一種方式 —— 不是唯一方式 什麼時候該這樣用 PHP? 明確一點,我不是説你該放棄 Bash、Python 或 Go。但 PHP CLI 在幾種特定情況下很出色:
✅ 團隊主力語言是 PHP,希望所有人都能貢獻自動化工具 ✅ 已有豐富的 PHP 代碼庫,想在 HTTP 之外複用邏輯 ✅ 喜歡用一門語言搞定” 應用” 和” 任務”,不想折騰多種語言
相反,可能不選 PHP 的情況: ❌ 需要單個靜態二進制(如無依賴分發的小 CLI) ❌ 需要極小運行時佔用的邊緣設備 ❌ 現有團隊 / 生態重度投資於另一種腳本語言
這不是競爭,而是認識到 PHP 比它的刻板印象更通用。
結論:PHP 不只是”Web 服務器背後的東西” 不用 Web 服務器運行 PHP 乍一聽像個噱頭,但試過之後會發現:
PHP 是命令行的合格腳本語言 可以寫真正的 CLI 工具:任務管理器、日誌解析器、自動化腳本、部署助手 可以複用 Web 應用的領域邏輯用於 cron、Worker、後台任務 更深入理解 PHP 如何運行,超越 HTTP 如果從沒這樣用過 PHP,試試這個簡單挑戰:
寫個小 PHP 腳本,純 CLI 乾點實用的事(解析文件、調 API、重命名文件) 用 $argv 加參數 把它變成可複用工具,放到你的 bin/ 目錄 熟悉之後,進一步:
引入 symfony/console 或其他 CLI 框架 註冊幾個連接現有應用邏輯的命令 讓 PHP 同時處理 Web 和” 非 Web” 生活 你可能會發現,不用 Web 服務器的 PHP 不只是可行 —— 它實際上是構建工具的愉快方式。