不用 Web 服务器也能跑 PHP?这事比你想的有意思

不用 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,在终端试一下:

bash 复制代码
php -v

看到版本号了?那就能用。现在创建一个 hello.php

php 复制代码
<?php
echo "Hello from the command line!\n";

然后运行它:

bash 复制代码
php hello.php

就这么简单。你刚才直接运行了一个 PHP 脚本,全程没有 HTTP 请求,没有 Apache,没有 Nginx,没有任何 Web 服务器。PHP 就像其他脚本语言一样,直接读文件、执行代码。

底层的原理是这样的:PHP 有不同的 SAPI(Server API),其中一个叫 CLI SAPI(Command Line Interface),专门为命令行设计,完全不依赖 Web 服务器。

这个发现可能看起来很显而易见,但它会让你的认知发生转变:

PHP 不只是"Web 应用背后的那个东西",它是一个完整的、可以做任何事情的通用解释器。

命令行环境下 PHP 长什么样

在命令行运行 PHP,环境跟 Web 服务器下完全不一样。

首先,那些熟悉的东西不见了:

  • 没有 $_GET$_POST$_COOKIE
  • 没有 $_SERVER['REQUEST_METHOD']$_SERVER['HTTP_HOST']
  • 根本就没有 HTTP 请求和响应

取而代之的是更"Unix"的环境:

  • 标准输入/输出(STDIN、STDOUT、STDERR)
  • 命令行参数($argv$argc
  • 不同的 php.ini 配置(很多系统会有单独的 php-cli.ini)

来看个实际的例子。

一个简单的问候脚本

创建 greet.php

php 复制代码
<?php
// $argv 是命令行参数数组
// $argv[0] 是脚本名
// $argv[1]、$argv[2]... 是参数
if ($argc < 2) {
    fwrite(STDERR, "用法: php greet.php <name>\n");
    exit(1);
}

$name = $argv[1];
echo "Hello, {$name}!\n";

运行:

bash 复制代码
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,代码可以共享。

2. 把 PHP 用于 DevOps 和自动化

一旦你接受 PHP 是个通用脚本语言,它就可以加入你的"自动化工具箱":

  • 文件系统操作
  • 调用 API
  • 解析日志
  • 转换 CSV 或 JSON 数据
  • 生成报告

如果你团队的主力语言是 PHP,这还能提高大家的参与度。不用为了写个部署脚本或自动化工具就切换语言。

3. 迫使你更深入理解 PHP 的运行时

不用 Web 服务器的工作方式,会暴露 PHP 实际上是怎么运行的:

  • 请求生命周期不再绑定 HTTP;它就是个进程
  • 你开始考虑长时间运行的脚本
  • 你会关心内存泄漏、资源管理、优雅关闭

这种更深的理解会反馈到你的 Web 开发技能上,因为你现在把 PHP 更多地看作一个进程,而不是"Apache背后那个神秘的东西"。

实际使用场景

说完理论,来看几个具体的例子。

1. 快速搞定自动化任务

比如你有一文件夹的 .log 文件,想把所有包含 ERROR 的行提取出来,写到 errors.txt 里。

当然可以用 grep,但如果你想做点更复杂的处理------解析时间戳、按错误码分组之类的------那写个轻量级的 PHP 脚本会更方便:

php 复制代码
<?php
// parse-logs.php
$inputDir  = $argv[1] ?? null;
$outputFile = $argv[2] ?? 'errors.txt';

if (!$inputDir || !is_dir($inputDir)) {
    fwrite(STDERR, "用法: php parse-logs.php <log-directory> [output-file]\n");
    exit(1);
}

$handle = fopen($outputFile, 'w');
foreach (scandir($inputDir) as $file) {
    if (!str_ends_with($file, '.log')) {
        continue;
    }
    $path = $inputDir . DIRECTORY_SEPARATOR . $file;
    $lines = file($path);
    
    foreach ($lines as $line) {
        if (str_contains($line, 'ERROR')) {
            fwrite($handle, $file . ': ' . $line);
        }
    }
}
fclose($handle);
echo "完成!错误已写入 {$outputFile}\n";

运行:

bash 复制代码
php parse-logs.php /var/log/myapp

PHP 瞬间变成日志处理工具。

2. Cron 任务和定时作业

Cron 最喜欢这种命令:

bash 复制代码
php /path/to/scripts/send-daily-report.php

send-daily-report.php 里可以:

  • 通过 PDO 连数据库
  • 生成昨天活动的摘要
  • 直接发邮件,或通过邮件服务商 API

这比为了定时任务专门搞个"隐藏 HTTP 端点"清爽多了。

3. 后台 Worker / 消费者

队列无处不在:

  • 处理图片上传
  • 发通知
  • 跑重计算

常见模式:

  1. Web 应用把任务入队(Redis、RabbitMQ、SQS 等)
  2. 一个长时间运行的 PHP 脚本作为 Worker,持续消费处理任务

伪代码示例:

php 复制代码
<?php
// worker.php(简化版,没真正的 Redis 代码)
while (true) {
    $job = get_next_job_from_queue(); // 你自己实现
    if ($job) {
        try {
            handle_job($job);
        } catch (Throwable $e) {
            log_error($e);
        }
    } else {
        // 没任务?短暂休眠避免 CPU 空转
        usleep(200000); // 0.2 秒
    }
}

虽然 PHP 以短生命周期 Web 请求闻名,但这种模式完全有效,生产环境在用。关键是仔细管理内存和资源。

4. 开发工具和脚手架

可以构建内部工具:

  • "创建新模块"脚本
  • "生成样板代码"脚本
  • 项目初始化命令(创建配置文件、数据填充等)

这些工具通常:

  • 提示用户输入
  • 操作文件和目录
  • 运行 shell 命令

小型脚手架脚本示例:

php 复制代码
<?php
// make-module.php
$moduleName = $argv[1] ?? null;

if (!$moduleName) {
    fwrite(STDERR, "用法: php make-module.php <ModuleName>\n");
    exit(1);
}

$baseDir = __DIR__ . '/modules/' . $moduleName;

if (is_dir($baseDir)) {
    fwrite(STDERR, "模块 {$moduleName} 已存在\n");
    exit(1);
}

mkdir($baseDir, 0777, true);
file_put_contents($baseDir . '/index.php', "<?php\n\n// {$moduleName} 模块入口\n");

echo "模块 {$moduleName} 已创建于 {$baseDir}\n";

构建真正的命令行应用

从玩具脚本到真正的 CLI 工具。

我们来构建一个简单的任务管理器:

bash 复制代码
php tasks.php add "买牛奶"
php tasks.php list
php tasks.php done 2

用 JSON 文件存储任务。

第 1 步:基础结构

创建 tasks.php

php 复制代码
<?php
const STORAGE_FILE = __DIR__ . '/tasks.json';

function loadTasks(): array
{
    if (!file_exists(STORAGE_FILE)) {
        return [];
    }
    $json = file_get_contents(STORAGE_FILE);
    $data = json_decode($json, true);
    return is_array($data) ? $data : [];
}

function saveTasks(array $tasks): void
{
    file_put_contents(STORAGE_FILE, json_encode($tasks, JSON_PRETTY_PRINT));
}

function printUsage(): void
{
    echo <<<USAGE
用法:
  php tasks.php list
  php tasks.php add "<描述>"
  php tasks.php done <id>
USAGE;
}

提供了:

  • 存储文件(tasks.json
  • 加载/保存任务的辅助函数
  • 打印用法说明的函数

第 2 步:处理命令

继续扩展 tasks.php

php 复制代码
<?php
// ... 前面的代码 ...

function listTasks(array $tasks): void
{
    if (empty($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,
        'done' => false,
    ];
    echo "任务已添加: {$description}\n";
}

function markDone(array &$tasks, int $id): void
{
    if (!isset($tasks[$id])) {
        echo "任务 {$id} 不存在\n";
        return;
    }
    $tasks[$id]['done'] = true;
    echo "任务 {$id} 已标记为完成\n";
}

// ---------- CLI 入口 ----------
$argvCopy = $argv;
array_shift($argvCopy); // 去掉脚本名
$command = $argvCopy[0] ?? null;

$tasks = loadTasks();

switch ($command) {
    case 'list':
        listTasks($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);
}

现在可以:

bash 复制代码
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

  1. 通过 Composer 安装:
bash 复制代码
composer require symfony/console
  1. 创建控制台脚本,启动 Console 应用并注册命令

  2. 把命令写成 PHP 类

你的命令会自动获得:

  • 自动生成 --help
  • 样式化输出
  • 输入验证
  • 嵌套命令(app:user:create 等)

重点不是记住这些 API,而是认识到:**PHP CLI 生态很成熟。**把 PHP 当 CLI 优先语言不是 hack,是主流的、被支持的模式。

不通过 HTTP 请求也能调 API 和数据库

"不用 Web 服务器"不等于"不用网络"。

CLI PHP 脚本仍然可以:

  • 调 REST API
  • 连数据库
  • 发消息到队列
  • 读云存储

示例:从 API 获取 JSON

file_get_contents 的简单例子:

php 复制代码
<?php
// fetch-user.php
$userId = $argv[1] ?? null;

if (!$userId) {
    fwrite(STDERR, "用法: php fetch-user.php <user-id>\n");
    exit(1);
}

$url = "https://jsonplaceholder.typicode.com/users/{$userId}";
$json = @file_get_contents($url);

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

$data = json_decode($json, true);
if (!is_array($data)) {
    fwrite(STDERR, "收到无效 JSON\n");
    exit(1);
}

echo "姓名: {$data['name']}\n";
echo "邮箱: {$data['email']}\n";

可以从终端运行,作为自动化流水线的一部分。

示例:用 PDO 操作数据库

连数据库和 Web 代码完全一样:

php 复制代码
<?php
// count-users.php
$dsn = 'mysql:host=localhost;dbname=myapp;charset=utf8mb4';
$user = 'myuser';
$pass = 'mypassword';

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

$stmt = $pdo->query('SELECT COUNT(*) FROM users');
$count = (int) $stmt->fetchColumn();

echo "总用户数: {$count}\n";

挂到 cron job,你的"报表系统"就是几个 PHP 脚本的事儿。

Web PHP 和 CLI PHP 的重要区别

不用 Web 服务器运行 PHP 感觉熟悉,但有些关键区别要记住。

1. 不同的超全局变量

Web 上下文会用:

  • $_GET$_POST$_REQUEST
  • $_COOKIE$_SESSION
  • $_SERVER['REQUEST_URI']

CLI 中:

  • 这些通常是空的或无关紧要
  • $argv$argcSTDIN

如果复用 Web 代码,可能需要重构逻辑,让它不依赖 HTTP 特定的全局变量。好的模式是把领域逻辑和"交付机制"(Web/CLI/API)分开。

2. 不同的配置(php.ini vs php-cli.ini)

很多系统对 CLI 有单独配置:

  • 启用/禁用扩展
  • 内存限制
  • 错误显示设置

好处是:

  • CLI 可能需要更详细的错误输出
  • CLI 脚本可能允许更长的执行时间

如果"浏览器里能跑"但 CLI 不行(反之亦然),留意这点。

3. 长时间运行脚本和内存

Web 请求通常短生命周期。请求结束后,PHP 进程结束,内存释放。

CLI 脚本,特别是 Worker,可能运行几小时或几天。这意味着:

  • 必须更小心内存泄漏(如永不清理的大数组)
  • 应该确保数据库连接被复用或正确关闭
  • 可能需要通过 supervisor 定期重启 Worker(如 supervisord、systemd)

查看内存的简单方法:

php 复制代码
echo "内存使用: " . memory_get_usage(true) . " bytes\n";

循环做大量处理时这很重要。

4. 没有"自动"的请求生命周期

Web 框架里,很多生命周期自动处理:

  • 中间件
  • 路由
  • 控制器
  • 响应

CLI 里,你自己掌控。既自由又多一点工作:

  • 你设计自己的"入口点"
  • 你决定如何处理失败和重试
  • 你实现自己的结构或依赖库

桥接 Web 和 CLI 世界

一个很好的模式是在 Web 和 CLI 入口之间共享同样的框架和领域代码。

比如:

  • Laravelphp artisan 就是应用的 CLI 前端,可以注册命令复用模型、服务等
  • Symfonybin/console 类似------启动 Symfony 内核的 CLI
  • 自定义应用 :可以创建 bootstrap.php 供两者使用:
php 复制代码
// bootstrap.php
<?php
require __DIR__ . '/vendor/autoload.php';

// 设置容器、配置、数据库等
$container = MyApp\Bootstrap::createContainer();

return $container;

CLI 脚本中:

php 复制代码
// cli-script.php
<?php
/** @var Psr\Container\ContainerInterface $container */
$container = require __DIR__ . '/bootstrap.php';

$reportService = $container->get(MyApp\Service\ReportGenerator::class);
$reportService->sendDailyReport();

这样:

  • 业务逻辑在可复用的类里
  • Web 控制器和 CLI 脚本只是适配器
  • Web 服务器变成触发 PHP 代码的一种方式------不是唯一方式

什么时候该这样用 PHP?

明确一点,我不是说你该放弃 Bash、Python 或 Go。但 PHP CLI 在几种特定情况下很出色:

团队主力语言是 PHP ,希望所有人都能贡献自动化工具

已有丰富的 PHP 代码库 ,想在 HTTP 之外复用逻辑

喜欢用一门语言搞定"应用"和"任务",不想折腾多种语言

相反,可能不选 PHP 的情况:

❌ 需要单个静态二进制(如无依赖分发的小 CLI)

❌ 需要极小运行时占用的边缘设备

❌ 现有团队/生态重度投资于另一种脚本语言

这不是竞争,而是认识到 PHP 比它的刻板印象更通用。

结论:PHP 不只是"Web 服务器背后的东西"

不用 Web 服务器运行 PHP 乍一听像个噱头,但试过之后会发现:

  1. PHP 是命令行的合格脚本语言
  2. 可以写真正的 CLI 工具:任务管理器、日志解析器、自动化脚本、部署助手
  3. 可以复用 Web 应用的领域逻辑用于 cron、Worker、后台任务
  4. 更深入理解 PHP 如何运行,超越 HTTP

如果从没这样用过 PHP,试试这个简单挑战:

  • 写个小 PHP 脚本,纯 CLI 干点实用的事(解析文件、调 API、重命名文件)
  • $argv 加参数
  • 把它变成可复用工具,放到你的 bin/ 目录

熟悉之后,进一步:

  • 引入 symfony/console 或其他 CLI 框架
  • 注册几个连接现有应用逻辑的命令
  • 让 PHP 同时处理 Web 和"非 Web"生活

你可能会发现,不用 Web 服务器的 PHP 不只是可行------它实际上是构建工具的愉快方式。这才是真正有意思的地方。

相关推荐
JaguarJack2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack3 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理3 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082853 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe3 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5
longxiangam4 天前
Composer 私有仓库搭建
php·composer
上海云盾-高防顾问4 天前
DNS异常怎么办?快速排查+解决指南
开发语言·php
ShoreKiten4 天前
关于解决本地部署sqli-labs无法安装低版本php环境问题
开发语言·php
liliangcsdn4 天前
深入探索TD3算法的推理过程
开发语言·php