当前位置: 首页 > 后端技术 > PHP

PHP复习流程

时间:2023-03-29 18:11:18 PHP

转载请注明文章出处: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也定义了一组文件操作对应的流函数:stream函数的第一个参数始终是一个php_stream对象,例如fread对应的php_stream_read函数定义为:PHPAPIsize_tphp_stream_read(php_stream*stream,char*buf,size_tcount).stream操作的支持和具体操作由wrapper决定(streamwrapper实际上会调用php_stream中ops成员的具体函数,wrapper时正确赋值这些函数opensthestream).同样是读取数据(fread),从文件中读取和从内存中读取是不同的.另外有些操作对某些流是不适用的.比如http协议支持fread,但是不支持fwrite;普通文件可以大小相同e,但ssh2://协议的数据大小是不可知的(stat函数不可用)。内置协议包装器列表和可用操作请参考官方文档中的“SupportedProtocolsandWrappers”。关于流底层的更多操作,请参考官方文档开发中笔者的“流”章节,本文不深入。用户态流让我们回到PHP应用层,也就是用户态的流。PHP的官方手册有一章专门介绍用户态流,并提供了一系列以流开头的函数。由于fread/fputs等函数已经包含了常见的流操作,所以stream开头的函数主要分为三类:辅助过滤器filter和context上下文,wrapper和socket编程。网络编程将在后续文章中进行讲解,先关注wrapper。开发者可以注册一个flowwrapper来实现自定义的协议,只需通过协议即可正常解析flow的数据。比起我们为下面的小姐姐现实一个专业的协议secret://:classSecretStream{private$position;私人$文件;私人$密码=“aes-256-cbc”;private$key="小妹妹";函数stream_open($path,$mode,$options,&$opened_pa??th){$info=parse_url($path);$this->file=fopen($info["host"],$mode);$this->position=0;返回真;}functionstream_read($count){$line=fgets($this->file);$text=openssl_decrypt(base64_decode($line),$this->cipher,$this->key);$this->position+=strlen($text);返回$文本;}functionstream_write($data){$raw=@openssl_encrypt($data,$this->cipher,$this->key);$base64=base64_encode($raw);fwrite($this->file,$base64.PHP_EOL);$this->position+=strlen($data);返回strlen($数据);}functionstream_tell(){return$this->position;}函数tionstream_eof(){返回feof($this->file);}functionstream_close(){fclose($this->file);}}使用自定义协议需要先注册,然后才能正常使用://先注册自定义协议stream_wrapper_register("secret","SecretStream")ordie("Failedtoregisterprotocol");//写入数据$fp=fopen("secret://Akari","w+");fwrite($fp,"IPZ-985\n");fwrite($fp,"IPX-021\n");fwrite($fp,"IPZ-933\n");fclose($fp);//由于协议没有实现seek功能,文件指针无法通过rewind到达head,需要重新打开$fp=fopen("secret://Akari","r");while(!feof($fp)){echofgets($fp);}fclose($fp);通过简单的代码,我们安全的存储小姐姐的数据,保护小姐姐的秘密。内容。感觉好吗?小姐姐和你相亲相爱~总结本文先回顾了PHP流的底层细节,再回到流在应用层的使用,并给出了一个streamwrapper的简单例子(例子很简单,你可以使用流章节中的php_user_filter来完成)。有兴趣的读者可以为以下年轻女士创建自定义协议。例子可以是:SSNI-056、SSNI-014、SNIS-662等。本文感谢“广州伟通”的赞助。感谢阅读,欢迎指正!参考http://php.net/manual/en/book...http://php.net/manual/en/inte...https://blog.csdn.net/lgg201/...https://post.zz173.com/course...感谢阅读,还望指正!