一、Cli 基本常识
Cli(Command Line Interface),即命令行接口,用于在命令行下执行PHP脚本,它是执行PHP脚本最简便的一种方式。
Cli模式通过执行编译的PHP二进制程序即可启动,它定义了很多命令行参数,不同的参数对应不同的处理,比如:
(1)执行PHP脚本文件、直接执行PHP代码(-r参数)
(2)输出PHP版本(-v参数)
(3)输出已安装的扩展(-m参数)
(4)指定php.ini配置(-c参数)
二、Cli 执行流程
Cli 是单进程模式,处理完请求后就直接关闭了,生命周期先后经历module startup、request startup、request shutdown、module shutdown,其执行流程比较简单,关键的处理过程如下:
main() -> php_cli_startup() -> do_cli() -> php_module_shutdown()
Cli SAPI的main函数位于 /sapi/cli/php_cli.c中,执行时首先解析命令行参数,然后初始化sapi_module_struct,它是记录SAPI信息的主要结构,这个结构中有几个函数指针,它们是内核定义的操作接口的具体实现,用来告诉内核如何读取、输出数据。
staticsapi_module_struct cli_sapi_module={
"cli",/* name */
"Command Line Interface",/* pretty name */
php_cli_startup,/* startup */
php_module_shutdown_wrapper,/* shutdown */
// 请求初始化函数
NULL,/* activate */
// 请求收尾处理函数,Cli中:fflush(stdout)
sapi_cli_deactivate,/* deactivate */
sapi_cli_ub_write,/* unbuffered write */
sapi_cli_flush,/* flush */
NULL,/* get uid */
NULL,/* getenv */
php_error,/* error handler */
// 调用header()函数的处理handle
sapi_cli_header_handler,/* header handler */
// 发送header时的函数
sapi_cli_send_headers,/* send headers handler */
sapi_cli_send_header,/* send header handler */
// 获取POST数据的函数
NULL,/* read POST data */
sapi_cli_read_cookies,/* read Cookies */
// 向$_SERVER中注册变量的函数
sapi_cli_register_variables,/* register server variables */
sapi_cli_log_message,/* Log message */
NULL,/* Get request time */
NULL,/* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
(1)在完成参数的解析及sapi_module_struct的基本初始化后,进入module_startup阶段。
if(sapi_module->startup(sapi_module)==FAILURE){
exit_status=1;
gotoout;
}
(2)前面定义的startup函数为php_cli_startup(),这个函数直接调用了php_module_startup()
staticintphp_cli_startup(sapi_module_struct*sapi_module)
{
if(php_module_startup(sapi_module,NULL,0)==FAILURE){
returnFAILURE;
}
returnSUCCESS;
}
(3)在module_startup阶段处理完后,接下来进入请求初始化阶段
zend_first_try{
if(sapi_module==&cli_sapi_module){
exit_status=do_cli(argc,argv);
}else{
exit_status=do_cli_server(argc,argv);
}
}zend_end_try();
(4)do_cli()将完成请求的处理,此函数一开始对使用到的命令行参数进行解析,如果是一些查询系统信息之类的请求(如-v,-m,-i),则不需要经历PHP请求的生命周期,这里会单独处理,下面看一下执行PHP脚本请求时的处理。
zend_file_handle file_handle;
...
if(script_file){
// fopen请求的脚本文件
if(cli_seek_file_begin(&file_handle,script_file,&lineno)!=SUCCESS){
gotoerr;
}else{
charreal_path[MAXPATHLEN];
if(VCWD_REALPATH(script_file,real_path)){
translated_path=strdup(real_path);
}
script_filename=script_file;
}
}else{
file_handle.filename="Standard input code";
file_handle.handle.fp=stdin;
}
// 输入类型为ZEND_HANDLE_FP,也就是FILE*
file_handle.type=ZEND_HANDLE_FP;
PHP 脚本执行时的输入形式有很多种,比如文件路径(filepath)、文件句柄(FILE)、文件描述符(fd)等,zend_file_handle结构就是用来定义不同输入形式的,这样可以统一PHP执行函数的输入参数。
Cli 中此处使用的是文件句柄,在Linux环境下也就是调用fopen()打开一个文件,这样内核就可以直接读取PHP脚本代码了,当然也可以直接把文件路径提供给内核。定义好请求的输入结构后,将进行请求初始化操作,即request startup阶段,然后开始PHP脚本的执行操作。
if(php_request_startup()==FAILURE){
*arg_excp=arg_free;
fclose(file_handle.handle.fp);
PUTS("Could not startup.\n");
gotoerr;
}
...
switch(behavior){
casePHP_MODE_STANDARD:
...
// 执行
php_execute_script(&file_handle);
...
break;
case...// 其他执行模式
}
(5)完成脚本的处理后进入request shutdown阶段:
out:
if(request_started){
php_request_shutdown((void*)0);
}
(6)do_cli() 完成后回到 main() 函数中,进入module shutdown阶段,最后进程退出,这就是 Cli 下执行一个脚本的生命周期。
out:
if(module_started){
php_module_shutdown();
}
if(sapi_started){
sapi_shutdown();
}