CLI

核心

CLI脚本基础结构

1. 可执行脚本规范

// 必须包含此行才能直接执行
echo "Hello CLI!";

2. 参数解析

$_SERVER['argv']
print_r($_SERVER['argv']);
/*
执行: php test.php arg1 arg2
输出:
Array
(
    [0] => test.php
    [1] => arg1
    [2] => arg2
)
*/
getopt
$options = getopt("f:hp:");  
var_dump($options);

执行命令php example.php -fvalue -h输出如下

array(2) {
  ["f"]=>
  string(5) "value"
  ["h"]=>
  bool(false)
}

I/O通道操作

1. 标准输入(STDIN)

// 从终端读取单行输入
echo "Name: ";
$name = fgets(STDIN); 
echo "Hello, $name!";

// 非阻塞读取(需安装扩展)
stream_set_blocking(STDIN, false);
$input = fread(STDIN, 1024);

底层原理STDIN对应php://stdin流,Zend引擎通过sapi_module_structub_write函数指针处理IO

2. 标准输出/错误(STDOUT/STDERR)

// 显式写入STDOUT
fwrite(STDOUT, "Normal message\n"); 
// 写入STDERR(不缓存,直接输出)
fwrite(STDERR, "Error occurred!\n"); 
// 重定向错误流示例
$ phpscript.php 2> errors.log

进程与系统交互

1. 后台守护进程

// 脱离终端成为守护进程
if (pcntl_fork() !== 0) exit; 
posix_setsid();

// 主循环
while (true) {
    file_put_contents('log.txt', date('Y-m-d H:i:s')."\n", FILE_APPEND);
    sleep(10);
}

2. 信号处理

// 注册SIGTERM信号处理器
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function($sig) {
    fwrite(STDERR, "Graceful shutdown...");
    exit(0); // 正常退出
});

3. 进程管理(需pcntl扩展)

$pid = pcntl_fork();
if ($pid == -1) die("Fork failed");
elseif ($pid) { // 父进程
    pcntl_wait($status); 
} else { // 子进程
    exec('php worker.php');
}

高级应用场景

1. 定时任务调度

// 简易定时任务引擎
set_time_limit(0);
while (true) {
    if (date('i') % 5 == 0) { // 每5分钟执行
        do_scheduled_task(); 
    }
    sleep(60);
}

2. 队列消费Worker

// Redis队列消费者
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

while ($job = $redis->brpop('jobs', 30)) {
    process_job(json_decode($job[1], true));
}

3. Swoole常驻服务(需swoole扩展)

$server = new Swoole\HTTP\Server("0.0.0.0", 9501);
$server->on('request', function ($req, $res) {
    $res->end("Hello Swoole!");
});
$server->start();

热更新配置:修改代码后需执行php think swoole restart重启服务

调试与优化

1. Opcache调优参数

php -d opcache.enable_cli=1 \
     -d opcache.jit_buffer_size=64M \
     -d opcache.jit=1235 \
     script.php

2. 内存泄漏检测

USE_ZEND_ALLOC=0 valgrind --leak-check=full php script.php

3. 生成Opcode(需vld扩展)

php -d vld.active=1 -d vld.execute=0 test.php

底层机制:vld通过覆盖zend_execute函数指针实现

附:CLI与Web模式差异总结

特性 CLI模式 Web模式
生命周期 单次执行(进程级) 多请求复用
超时控制 默认无限制 受max_execution_time限制
I/O通道 直接使用STDIN/STDOUT HTTP协议流
内存管理 脚本结束完全释放 需避免跨请求泄漏
$_SERVER内容 命令行环境信息 HTTP请求信息
掌握这些模式差异可避免踩坑,尤其涉及资源持久化或内存管理的场景。

笔记建议:实际使用时注意常驻进程的内存回收(定期unset大变量),并善用register_shutdown_function处理异常退出逻辑。

CLI 终端常用命令

# --- PHP 基础信息查询 ---
php -v                       # 查看 PHP 版本号
php -m                       # 查看当前已安装并启用的模块
php -i                       # 查看完整的 PHP 配置信息 (CLI 模式下的 phpinfo)
php --ri swoole              # 查看 swoole 扩展的详细版本及编译参数
php --re swoole              # 查看 swoole 扩展提供的所有类、方法和常量
php -h                       # 查看 PHP 命令行参数帮助

# --- 启动内置 Web 服务器 ---
# 参数详解
# -S: 启动内置 Web 服务器的参数标识符
# 0.0.0.0:8838 - 服务器绑定地址和端口
# -t: 指定文档根目录的参数
# .: 当前目录作为文档根目录
php -S 0.0.0.0:8838 -t . 是一个 PHP 内置 Web 服务器的启动命令,用于快速启动一个开发环境下的 HTTP 服务器。

# --- 配置文件 (php.ini) 相关 ---
php --ini                    # 显示加载的 php.ini 路径及扫描到的附加 .ini 目录
php -i | grep php.ini        # 通过 phpinfo 过滤查看配置文件信息
php -i | grep "extension_dir" # 查看扩展存放目录的物理路径
php -i | grep "memory_limit" # 查看 PHP 脚本可使用的最大内存限制

# --- 代码执行与语法检查 ---
php -f <file>                # 解析并执行指定的 PHP 文件
php -r "phpinfo();"          # 在命令行中直接运行引号内的 PHP 代码
php -r "print_r(gd_info());" # 快速查看 GD 库支持信息
php -l <file>                # 语法检查 (Lint),仅检查语法是否有错,不运行代码
php -a                       # 进入交互式 Shell 模式

# --- 进程管理与性能诊断 (Linux) ---
ps aux | grep php-fpm        # 列出所有 php-fpm 进程详情
ps aux | grep -c php-fpm     # 统计 php-fpm 进程的总数
/usr/bin/php -i | grep mem   # 查看当前 PHP 运行环境的内存相关配置
top -p `pgrep -d , php-fpm`  # 实时监控所有 php-fpm 进程的 CPU/内存 占用
netstat -lntp | grep 9000    # 查看 php-fpm 默认端口 9000 是否在监听

# --- php-fpm 专项操作 ---
php-fpm -t                 # 测试 php-fpm 配置文件语法是否正确
php-fpm -v                 # 查看 php-fpm 版本
php-fpm -m                 # 查看 php-fpm 加载的模块 (可能与 CLI 模式不同)

生命周期

PHP CLI 底层生命周期(Zend Engine 视角)

graph TB
    A[SAPI_初始化] --> B[MINIT_模块初始化]
    B --> C[GINIT_全局变量初始化]
    C --> D[RINIT_请求初始化]
    D --> E[词法分析_语法分析]
    E --> F[Opcode_生成]
    F --> G[Zend_VM_执行]
    G --> H[RSHUTDOWN_请求关闭]
    H --> I[MSHUTDOWN_模块关闭]
    I --> J[进程终止]

阶段 1: SAPI 初始化 (sapi/cli/php_cli.c)

  1. 入口点main() 函数启动 (sapi/cli/php_cli.c)
  2. SAPI 结构注册
sapi_module_struct cli_sapi_module = {
   "cli",                     // 名称
   "Command Line Interface",  // 描述
   php_cli_startup,           // 初始化函数
   php_module_shutdown        // 关闭函数
   // ... 其他函数指针
};
  1. 参数解析
    • 处理 -f, -r, -B/-R/-F/-E 等命令行参数
    • 设置 argc/argv$_SERVER['argv']

阶段 2: 模块初始化 (MINIT)

  1. 扩展初始化
// 每个扩展的 MINIT 函数
PHP_MINIT_FUNCTION(extension_name) {
   REGISTER_INI_ENTRIES();    // 注册 INI 设置
   zend_register_functions(); // 注册函数
   zend_register_classes();   // 注册类
   return SUCCESS;
}
  1. 核心引擎初始化
    • 初始化 Zend 内存管理器 (zend_mm_init)
    • 启动垃圾回收器 (gc_init)
    • 注册核心常量 (TRUE/FALSE/NULL)

阶段 3: 请求初始化 (RINIT)

  1. 创建全局符号表
zend_hash_init(&EG(symbol_table), 50, NULL, NULL, 0);
  1. 设置超全局变量
    • $_SERVER, $_ENV 填充 CLI 环境信息
    • $_GET, $_POST 在 CLI 中为空但结构存在
  2. 激活输出缓冲
    • 默认启用 output_buffering=0 (直接输出到 stdout)

阶段 4: 脚本编译与执行

编译阶段 (zend_compile)

  1. 词法分析re2c 生成的 zend_language_scanner.l
  2. 语法分析Bison 生成的 zend_language_parser.y
  3. 生成 Opcode
zend_op_array *op_array = compile_file(&file_handle, ...);
if (opcache && cached = opcache_get_script(hash)) {
	use cached op_array;
} else {
	compile_and_cache();
}

执行阶段 (zend_execute)

ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
{
    zend_vm_enter();  // 进入 Zend VM
    // ... 循环执行 opcodes
}

阶段 5: 请求关闭 (RSHUTDOWN)

  1. 执行顺序
zend_call_destructors();      // 调用 __destruct()
zend_objects_store_del_ref(); // 释放对象
shutdown_destructors();       // 调用 register_shutdown_function()
  1. 内存清理
    • 销毁全局符号表 (zend_hash_destroy(&EG(symbol_table)))
    • 释放请求级内存池 (efree(请求内存块))

阶段 6: 模块关闭 (MSHUTDOWN)

  1. 扩展卸载
PHP_MSHUTDOWN_FUNCTION(extension_name) {
   UNREGISTER_INI_ENTRIES();
   zend_unregister_functions();
   return SUCCESS;
}
  1. 核心引擎关闭
    • 销毁全局类表 (zend_hash_destroy(CG(class_table)))
    • 关闭内存管理器 (zend_mm_shutdown)

内存管理深度解析

  1. 内存池结构
struct _zend_mm_heap {
   zend_mm_segment *segments; // 内存段链表
   size_t size;                // 当前使用量
   // ... 其他统计信息
};
  1. 分配策略
    • 小内存 (< 2KB):使用预分配块 (small_buckets)
    • 大内存:直接从系统分配 (mmapmalloc)
  2. 垃圾回收
    • 引用计数为主
    • 周期回收器处理循环引用:
zend_gc_collect_cycles(); // 当缓冲区满时触发

CLI 特有优化技术

  1. Opcache 调优
php -d opcache.enable_cli=1 -d opcache.jit=1205 -d opcache.jit_buffer_size=64M script.php
  1. 持久化资源复用
// 连接池示例 (伪代码)
if (!isset($global_pool)) {
   $global_pool = new ConnectionPool(max: 5);
}
$conn = $global_pool->get();
  1. 信号处理最佳实践
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function() {
   // 清理资源后退出
   posix_kill(getmypid(), SIGKILL);
});

调试与性能分析

  1. GDB 调试示例
gdb --args php -r 'echo "test";'
(gdb) b zend_execute
(gdb) run
  1. Valgrind 内存检测
USE_ZEND_ALLOC=0 valgrind --leak-check=full php script.php
  1. JIT 反汇编
php -d opcache.jit=1205 -d opcache.jit_debug=1 script.php

⚠️ 高级注意事项

  1. 全局状态污染
// 危险:静态变量在长运行脚本中持续存在
function counter() {
   static $i = 0;
   return $i++;
}
  1. 扩展兼容性问题
    • 某些扩展(如 apc)在 CLI 中行为不同
    • 检查扩展的 PHP_MINFO_FUNCTION 输出
  2. ZTS (Zend Thread Safety) 影响
    • CLI 通常使用 NTS 构建
    • 线程安全版本有额外锁开销
  3. 内存泄漏预防