转载请注明文章出处:https://tlanyan.me/php-review...PHP复习系列目录PHP基础web请求cookieweb响应session数据库操作加解密Composer创建自己的ComposerpackagetosendMailIO第一篇《PHPReview的IO》中提到了读取文件、网络通信等操作,本质上都是在和“流”打交道。流机制是很多编程语言的重要机制。程序可以通过流的方式自由操作文件、内存、网络等设备的数据。本文先简单的追溯一下PHP底层流的原理,然后回到用户态流的使用。底层流我们知道PHP中的fopen函数可以打开本地文件、URL等并返回句柄,fread和fwrite函数可以读写资源句柄,fclose用于关闭资源。PHP如何使用一致的API来操作不同的数据源?答案是PHP在底层引入了“流”的概念来抽象操作,好处是在上层可以使用同一套API。为了理解PHP中的流程,我们先追溯一下PHP中fopen函数的调用过程。PHP底层是用C实现的,阅读本文代码需要有一定的C语言基础。如果你不熟悉C语言,只关注它的思想。ext/standard/file.c文件中定义了用户态的fopen函数,函数体如下:PHP_NAMED_FUNCTION(php_if_fopen){//一些初始化代码context=php_stream_context_from_zval(zcontext,0);stream=php_stream_open_wrapper_ex(filename,mode,(use_include_path?USE_PATH:0)|REPORT_ERRORS,NULL,context);如果(流==NULL){RETURN_FALSE;}php_stream_to_zval(stream,return_value);}PHP_NAMED_FUNCTION(php_if_fopen)定义了PHP中的fopen函数(区别于C中的fopen),有扩展开发基础的应该对这种写法不陌生。跳过初始化等无关代码,fopen的主要工作是获取流对象(stream),并将其转换为PHP值类型(zval)返回。流对象由php_stream_open_wrapper_ex函数返回,该函数位于main/php_streams.h中,是main/streams/streams.c中定义的_php_stream_open_wrapper_ex的别名:PHPAPIphp_stream*_php_stream_open_wrapper_ex(constchar*path,constchar*mode,intoptions,zend_string**opened_pa??th,php_stream_context*contextSTREAMS_DC){//初始化代码wrapper=php_stream_locate_url_wrapper(path,&path_to_open,options);if(options&STREAM_USE_URL&&(!wrapper||!wrapperUL->is_url)){phpref(Nerror_E_WARNING,"此函数只能用于URL");如果(解析路径){zend_string_release(解析路径);}返回空值;}if(wrapper){if(!wrapper->wops->stream_opener){php_stream_wrapper_log_error,wrapper(options^REPORT_ERRORS,"wrapperdoesnotsupportstreamopen");}else{stream=wrapper->wops->stream_opener(wrapper,path_to_open,模式,选项^REPORT_ERRORS,opened_pa??th,contextSTREAMS_REL_CC);}}//流检测等代码}_php_stream_open_wrapper_ex函数主要有两个工作点:1.调用php_stream_locate_url_wrapper函数获取协议包装器(wrapper);2.调用包装器打开资源并返回流对象然后查看在同一文件中获取包装器的函数php_stream_locate_url_wrapper:)*p)||*p=='+'||*p=='-'||*p=='.';p++){n++;}if((*p==':')&&(n>1)&&(!strncmp("//",p+1,2)||(n==4&&!memcmp("数据:",路径,5)))){协议=路径;}if(protocol){if(NULL==(wrapper=zend_hash_str_find_ptr(wrapper_hash,protocol,n))){char*tmp=estrndup(protocol,n);php_strtolower(tmp,n);if(NULL==(wrapper=zend_hash_str_find_ptr(wrapper_hash,tmp,n))){charwrapper_name[32];如果(n>=sizeof(wrapper_name)){n=sizeof(wrapper_name)-1;}PHP_STRLCPY(wrapper_name,protocol,sizeof(wrapper_name),n);php_error_docref(NULL,E_WARNING,"无法找到包装器\"%s\"-您是否在配置PHP时忘记启用它?",wrapper_name);包装器=NULL;协议=空;}efree(tmp);}}/*TODO:基于curl的流可能支持file://正确*/if(!protocol||!strncasecmp(protocol,"file",n)){/*回退到常规文件访问*/php_stream_wrapper*plain_files_wrapper=(php_stream_wrapper*)&php_plain_files_wrapper;//检测代码returnplain_files_wrapper;}//检测远程文件等returnwrapper;}在php_stream_locate_url_wrapper中,我们终于知道fopen支持本地文件、HTTP/FTP、php://等数据源谜底:函数首先检查路径是否以"开头http://","ftp://"或类似的协议,如果是,则从已注册的包装器列表中找到相应的包装器;如果它不以协议开头,它会退回到本地文件模式(php_plain_files_wrapper);fopen返回的流对象由包装器打开。跟踪上面的代码,fopen的玄机已经揭开,但是有两个关键点:1.什么是流对象(php_stream)?2.什么是包装器(php_stream_wrapper)?内核开发者在源码的README.STREAMS文件中解释了使用流的原因:允许扩展开发者像普通文件一样操作数据。为此,流操作的资源都是php_stream对象。在统一了资源接口之后,PHP也定义了一组文件操作对应的流函数:
