IO及其它实用内置函数

使用流机制的I/O

在PHP中,大部分I/O操作(包括但不限于使用本地文件系统的I/O与使用HTTP协议的网络I/O)被一套通用的流机制封装了起来,这使得各种I/O都能使用同样的函数进行,例如 函数filegetcontent 可以获取保存在本地文件中的数据,也可以通过HTTP通信获取远程数据(注意,PHP中的很多内置I/O函数从名字上来看似乎只能处理文件系统的I/O,实则不然——这或许是历史遗留问题,通用的I/O流机制在PHP4中才被引入)。流机制的关键之处在于允许数据源 逐块(chunk) 地(而非一口气)产出数据,数据的消费者 逐块 地处理,因此可以规避内存不足的问题。

可以使用一个 <scheme>://<target> 形式的字符串(称为流标识符)来指称一个具体的流,用于传给I/O函数做参数 (根据上下文,它可能表示一个数据源或数据目的地) 。该字符串通常看上去像一个URL,例如php://stdinhttp://www.baidu.comfile:///path/to/file.ext

流标识符中的<scheme>部分称为流封装协议,指示了这个流的类型;而<target>部分指示了这个流的数据源/目的地。

有的I/O函数(例如fopen)只是简单地“打开”一个流,此时这个流会被表示为resource类型(即流的handler)。随后可以将这个流的handler传给其它I/O函数使用,或者对其进行一些相对底层的操作(例如手动令该流产出一些数据,等等)。

封装一种流

将针对某种数据源的数据读取操作封装成一种流类型,例如http流(该类流的标识符以http://开头)是对所有“通过HTTP协议获取的数据”的操作的封装。

为了封装出一种流,应当以StreamWrapper这个页面为模板 实现一个类。注意:

  • StreamWrapper类并不真实存在——没有这个类、抽象类或者接口,该页面只是一个模板
  • StreamWrapper类文档页中列出的方法并非都要实现(否则为何不提供一个可供implents的StreamWrapper接口呢?)。具体参见页面中列出的方法目录

实现好一个类后,必须通过函数streamwrapperregister将这种流与一个<scheme>关联起来。 官方文档有一个实现一种流的例子

内置的流类型

这个列表列出了PHP原生支持的流的类型。值得一提的是php://类型的流,其中标识符为php://inputphp://output的这两个具体的流对Web程序员来说很重要,通过用I/O函数操作这两个流,可以读取HTTP请求、发送HTTP响应。

涉及I/O操作的函数与语言结构

涉及到I/O的函数或语言结构都是在显式或隐式地使用流。它们包括:

  • 文件系统相关的各模块:注意,它们中的很多函数不是只能处理文件系统的I/O;很多函数接收的是流的handler而不是流标识符
  • 语言结构echo/print:隐式地往php://output处输出数据。反之,“往php://output处输出数据”和“使用echo/print”的效果是一样的
  • streamgetline
  • ...

使用流标识符时,如果省略<scheme >部分,则默认使用file//

可以通过Output Buffering Control模块中的函数来控制进行输出时的行为(这是对缓冲区的操作。见下一节)。

输出流的缓冲区

PHP 中任何涉及到输出流的操作都会用到缓冲区(用C实现的扩展则可以绕过)。缓冲区的实现是分层的:

PHP 允许程序员配置缓冲区行为,以及在缓冲区内进行操作。详见深入理解php的输出缓冲区(output buffer)一文

流的上下文

某些I/O函数在操作流前,允许程序员传入一个表示“流的上下文”的对象,它包含了很多参数,用于控制流的行为。不同类型的流使用的上下文参数不同,详见文档中的此处。该页面同样介绍了创建 “流的上下文”对象以及修改其参数的方法。

流过滤器

使用流过滤器

使用流过滤器,在数据被逐块接收/产出的过程中对数据块进行操作。PHP为我们提供了一些内置的流过滤器。函数streamfilterappend为一个流的handler添加过滤器:

<?php
$handle = fopen('data.txt');
stream_filter_append($handle, 'string.toupper'); // 并不推荐使用这个过滤器,此处仅为演示
while(feof($handle) !== true) {
    echo fgets($handle); // 输出的全是大写字母
 
}
fclose($handle);

调用函数streamgetfilters可以在运行时得知所有可用的过滤器(包括自定义的过滤器)。

使用某些I/O函数时没有获得一个流的handler的机会(例如函数filegetcontent,接收一个流标识符后直接返回流的完整内容),此时若要使用流过滤器,需要使用php://filter这一流封装协议:

<?php
$text = file_get_content('php://filter/read=string.toupper/data.txt'); // 等价于上一节的示例代码

自定义流过滤器

大部分情况下我们使用自定义的流过滤器。通过 继承 内置的phpuserfilter类 实现一个的自定义流过滤器类(需要实现filter、onCreate、onClose三个方法),并将它通过streamfilterregister函数注册,即可按上文所述方式使用。

流中的错误

使用内置类型的流且因为种种原因(例如网络不畅)这个流无法读取/写入数据块时,一般会触发E_WARNING级别的错误。

总结

包括上文介绍的所有以"stream_"开头的函数在内,所有和流机制本身有关的函数、类都记录在这个页面中。

内置实用函数

PHP the right way中记载了使用PHP内置函数以及库来确保令你的应用满足最佳实践的细节。

对来自用户的字符串的处理

  • htmlentities :将一个字符串内能够以“HTML实体”(见《HTML实体的研究》)形式表示的指定类型的字符都转换成HTML实体。使用它来转义来自外部的字符串(例如用户上传的文章)

  • filter_var:通过在第二个参数处传入不同的flag,可以将一个字符串滤出想要的内容,也可以用它进行判断。例如一个电邮地址、一个URL字符串、一串浮点数、特定范围内的ASCII字符等等

  • strip_tags:去掉字符串里的HTML标签

  • addslashes:为字符串中的单引号、双引号等字符前添加一个反斜杠(随后可以用于SQL查询等场合)

  • 使用PHP提供的Password Hashing相关函数将用户用于注册和登陆的密码转换为密文,储存和验证时均使用密文,而不是用户传来的原始密码

  • 多字节字符串:PHP的mbstring扩展能够安全地处理多字节字符串,例如使用utf-8编码的字符串。而PHP的原生字符串函数并不了解string类型的值使用的编码,因此在操作字符串的过程中可能会破坏字符串。尽量使用mbstring扩展中提供的函数来代替一般的字符串函数

正则表达式

PHP有两套正则表达式扩展

在打算使用正则表达式解决问题之前,先考虑一下以下扩展能否解决问题:

  • URLs:专门处理URL字符串的模块
  • filter_var:见上文
  • T okenizer :用于分析PHP源码字符串
  • DOM:以DOM对象的形式处理HTML字符串

HTTP协议相关

日期处理

  • 在运行时通过函数datedefaulttimezone_set来设置默认市区。也可以在php.ini的date.timezone选项中配置

  • DateTime类表示一个时间点, 使用它来进行各类与日期有关的操作

  • DateInterval类表示一段时间间隔(例如“2个小时”), 使用它来修改(例如推迟或提前)DateTime实例

  • DatePeriod类表示一个具体的时间段(例如“1917年11月1日到1917年12月1日”)。它是一个实现了Iterator接口的类,可以产出有一定间隔的DateTime实例

数据库

PHP具有操作不同品牌的数据库的扩展,还提供了一个PDO扩展用于把对不同品牌数据库的不同操作抽象成一套API。但该扩展并不对程序员提供的SQL语句做任何变化,也就是说程序员仍然得考虑SQL语句的兼容性问题。

内存存储

PHP提供了一些扩展,用于在内存中存储对象用于缓存,例如将某个耗时的SQL查询结果缓存在内存中。

  • APCu:APCu 的局限性表现在不能将缓存保存在其它服务器上;当你以 CGI(FastCGI) 的形式使用 PHP 时,每个进程将会有各自的缓存,比如说,APCu 缓存数据无法在多个工作进程中共享
  • memcached :高级的内存数据库。需要独立的 memcached 程序支持
  • WinCache :一种用于Windows系统的内存数据库

Session相关

用户的请求中的Cookie中会携带Session ID(如果你此前通过Cookie下发过一个Seesion ID的话)。PHP会自动根据Cookie中指示的Session ID,以超全局变量$_SESSION提供保存在服务器上的、与该Session ID有关的信息(下称Session信息)。Session信息通常包括身份信息、登陆状态、用户的个性配置、权限列表以及其他的一些通用数据(比如购物车)。

处理HTTP请求时,PHP将使用一个Session管理器对象(继承自SessionHandler类的实例,或实现了SessionHandlerInterface接口的类的实例)进行Session的读写以及Session ID的创建。默认的Session管理器对象使用文件系统进行Session信息的读写。

在一次执行过程中,PHP的Session机制流程如下:

  1. 根据session_name函数的指定,或配置文件中的session.name字段的指定,在收到的Cookies中获取相应字段的值作为Session ID。若不指定,则以PHPSESSID字段的值作为Session ID
  2. 根据sessionsetsave_handler函数 的指定,设置一个Session管理器对象。如果没有通过该函数指定,PHP提供一个默认的Session管理器对象
  3. 调用session_start函数 。这会令Session管理器对象自动依次调用:
    1. open方法:在该方法里为读写Session信息做准备,例如连接数据库/在文件系统里创建保存该Session信息的目录等
    2. read方法:在该方法里令Session管理器读取已有的Session信息,并返回一个其序列化字符串的表示。这些信息会被自动反序列化并存放在超全局变量 $_SESSION里(通过PHP对session_decode函数的自动调用。而该函数的行为在配置项 session.serialize_handler中定义)
  4. 现在可以读写$_SESSION以及使用读写session信息的函数了
  5. 调用sessionwriteclose函数,把第4步中对session信息的修改保存下来。这会令Session管理器对象自动依次调用:
    1. write方法:在该方法中把Session信息持久化。对于默认的Session管理器对象,它将把调用session_encode函数后得到的结果持久化到硬盘中(其行为在配置项session.serialize_handler中定义)
    2. close方法