不用 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 服務器下完全不一樣。

首先,那些熟悉的東西不見了:

沒有 不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_POST、$_COOKIE 沒有 $_SERVER['REQUEST_METHOD'] 或 $_SERVER['HTTP_HOST'] 根本就沒有 HTTP 請求和響應 取而代之的是更”Unix” 的環境:

標準輸入 / 輸出(STDIN、STDOUT、STDERR) 命令行參數(不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_02argc) 不同的 php.ini 配置(很多系統會有單獨的 php-cli.ini) 來看個實際的例子。

一個簡單的問候腳本 創建 greet.php:

<?php // $argv 是命令行參數數組 // $argv[0] 是腳本名 // 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_03argv[2]... 是參數 if ($argc < 2) { fwrite(STDERR, "用法: php greet.php \n"); exit(1); }

$name = 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_04name}!\n"; 運行:

php greet.php Alice

輸出: Hello, Alice!

在這個例子裏,我們:

從 $argv 讀取命令行參數 輸出到 STDOUT 錯誤信息輸出到 STDERR 失敗時返回非零退出碼(exit(1)),這是標準的 CLI 行為 到這裏,PHP 表現得更像 Bash 或 Python,而不是”CMS 背後的那個東西” 了。

為什麼這事挺有意思 乍一看,你可能會想:” 好吧,能在終端跑 PHP 腳本了,所以呢?”

但其實這比看起來有趣。它至少從三個方面改變了你對 PHP 的認知。

  1. 可以在 HTTP 之外複用你的 Web 應用邏輯 如果你有一個 Laravel、Symfony 或者自己寫的 PHP 應用,那你已經有了:

驗證規則 領域邏輯(比如計費規則、內容規則) 數據庫訪問和模型 發郵件、調 API 等服務 當你在命令行運行 PHP 時,可以啓動同樣的代碼庫,跑一些任務,完全不用通過 HTTP。比如:

隊列 Worker Cron 定時任務 批量導入 / 導出腳本 維護命令 不用把這些邏輯用另一門語言(Python、Bash 等)重寫一遍,全都用 PHP,代碼可以共享。

  1. 把 PHP 用於 DevOps 和自動化 一旦你接受 PHP 是個通用腳本語言,它就可以加入你的” 自動化工具箱”:

文件系統操作 調用 API 解析日誌 轉換 CSV 或 JSON 數據 生成報告 如果你團隊的主力語言是 PHP,這還能提高大家的參與度。不用為了寫個部署腳本或自動化工具就切換語言。

  1. 迫使你更深入理解 PHP 的運行時 不用 Web 服務器的工作方式,會暴露 PHP 實際上是怎麼運行的:

請求生命週期不再綁定 HTTP;它就是個進程 你開始考慮長時間運行的腳本 你會關心內存泄漏、資源管理、優雅關閉 這種更深的理解會反饋到你的 Web 開發技能上,因為你現在把 PHP 更多地看作一個進程,而不是”Apache 背後那個神秘的東西”。

實際使用場景 説完理論,來看幾個具體的例子。

  1. 快速搞定自動化任務 比如你有一文件夾的 .log 文件,想把所有包含 ERROR 的行提取出來,寫到 errors.txt 裏。

當然可以用 grep,但如果你想做點更復雜的處理 —— 解析時間戳、按錯誤碼分組之類的 —— 那寫個輕量級的 PHP 腳本會更方便:

<?php // parse-logs.php $inputDir = 不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_05outputFile = $argv[2] ?? 'errors.txt';

if (!不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_06inputDir)) { fwrite(STDERR, "用法: php parse-logs.php [output-file]\n"); exit(1); }

不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_07outputFile, 'w'); foreach (scandir($inputDir) as 不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_08file, '.log')) { continue; } $path = $inputDir . DIRECTORY_SEPARATOR . $file; 不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_09path);

foreach ($lines as $line) {
    if (str_contains($line, 'ERROR')) {
        fwrite($handle, $file . ': ' . $line);
    }
}

} fclose(不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_10outputFile}\n"; 運行:

php parse-logs.php /var/log/myapp PHP 瞬間變成日誌處理工具。

  1. Cron 任務和定時作業 Cron 最喜歡這種命令:

php /path/to/scripts/send-daily-report.php 在 send-daily-report.php 裏可以:

通過 PDO 連數據庫 生成昨天活動的摘要 直接發郵件,或通過郵件服務商 API 這比為了定時任務專門搞個” 隱藏 HTTP 端點” 清爽多了。

  1. 後台 Worker / 消費者 隊列無處不在:

處理圖片上傳 發通知 跑重計算 常見模式:

Web 應用把任務入隊(Redis、RabbitMQ、SQS 等) 一個長時間運行的 PHP 腳本作為 Worker,持續消費處理任務 偽代碼示例:

<?php // worker.php(簡化版,沒真正的 Redis 代碼) while (true) { 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_11job) { try { handle_job($job); } catch (Throwable 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_12e); } } else { // 沒任務?短暫休眠避免 CPU 空轉 usleep(200000); // 0.2 秒 } } 雖然 PHP 以短生命週期 Web 請求聞名,但這種模式完全有效,生產環境在用。關鍵是仔細管理內存和資源。

  1. 開發工具和腳手架 可以構建內部工具:

“創建新模塊” 腳本 “生成樣板代碼” 腳本 項目初始化命令(創建配置文件、數據填充等) 這些工具通常:

提示用户輸入 操作文件和目錄 運行 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(不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_13moduleName} 已存在\n"); exit(1); }

mkdir(不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_14baseDir . '/index.php', "<?php\n\n// {$moduleName} 模塊入口\n");

echo "模塊 {不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_15baseDir}\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); 不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_16json, true); return is_array($data) ? $data : []; }

function saveTasks(array 不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_17tasks, 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 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_18tasks)) { 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' => 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_19description}\n"; }

function markDone(array &$tasks, int 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_20tasks[不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_21id} 不存在\n"; return; } 不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_22id]['done'] = true; echo "任務 {$id} 已標記為完成\n"; }

// ---------- CLI 入口 ---------- $argvCopy = 不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_23argvCopy); // 去掉腳本名 $command = $argvCopy[0] ?? null;

$tasks = loadTasks();

switch (不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_24tasks); 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); }

不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_25userId}"; 不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_26url);

if ($json === false) { fwrite(STDERR, "獲取用户失敗\n"); exit(1); }

不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_16json, true); if (!is_array($data)) { fwrite(STDERR, "收到無效 JSON\n"); exit(1); }

echo "姓名: {不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_28data['email']}\n"; 可以從終端運行,作為自動化流水線的一部分。

示例:用 PDO 操作數據庫 連數據庫和 Web 代碼完全一樣:

<?php // count-users.php 不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_29user = 'myuser'; $pass = 'mypassword';

不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_30dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);

$stmt = 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_31count = (int) $stmt->fetchColumn();

echo "總用户數: {$count}\n"; 掛到 cron job,你的” 報表系統” 就是幾個 PHP 腳本的事兒。

Web PHP 和 CLI PHP 的重要區別 不用 Web 服務器運行 PHP 感覺熟悉,但有些關鍵區別要記住。

  1. 不同的超全局變量 Web 上下文會用:

不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_POST、不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_33_COOKIE、不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_34_SERVER['REQUEST_URI'] 等 CLI 中:

這些通常是空的或無關緊要 用 不用 Web 服務器也能跑 PHP?這事比你想的有意思_PHP_02argc、STDIN 如果複用 Web 代碼,可能需要重構邏輯,讓它不依賴 HTTP 特定的全局變量。好的模式是把領域邏輯和” 交付機制”(Web/CLI/API)分開。

  1. 不同的配置(php.ini vs php-cli.ini) 很多系統對 CLI 有單獨配置:

啓用 / 禁用擴展 內存限制 錯誤顯示設置 好處是:

CLI 可能需要更詳細的錯誤輸出 CLI 腳本可能允許更長的執行時間 如果” 瀏覽器裏能跑” 但 CLI 不行(反之亦然),留意這點。

  1. 長時間運行腳本和內存 Web 請求通常短生命週期。請求結束後,PHP 進程結束,內存釋放。

CLI 腳本,特別是 Worker,可能運行幾小時或幾天。這意味着:

必須更小心內存泄漏(如永不清理的大數組) 應該確保數據庫連接被複用或正確關閉 可能需要通過 supervisor 定期重啓 Worker(如 supervisord、systemd) 查看內存的簡單方法:

echo "內存使用: " . memory_get_usage(true) . " bytes\n"; 循環做大量處理時這很重要。

  1. 沒有” 自動” 的請求生命週期 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 不用 Web 服務器也能跑 PHP?這事比你想的有意思_php_36container = require DIR . '/bootstrap.php';

$reportService = 不用 Web 服務器也能跑 PHP?這事比你想的有意思_Web_37reportService->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 不只是可行 —— 它實際上是構建工具的愉快方式。