Heim > Artikel > Backend-Entwicklung > [Übersetzung][Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 14 – Zugriff auf Streams in PHP
Zugriffsfluss
Die gesamte Datei-E/A-Verarbeitung im PHP-Benutzerbereich erfolgt über PHP. Verarbeitet von Der in 4.3 eingeführte PHP-Stream-Wrapper. Intern kann der Erweiterungscode wählen, ob er die stdio- oder posix-Dateiverarbeitung verwendet, um mit dem lokalen Dateisystem oder dem Berkeley-Domänen-Socket zu kommunizieren, oder er kann dieselbe API wie User Space Stream I/O aufrufen.
Übersicht über Streams
Im Allgemeinen sind direkte Dateideskriptoren kostengünstiger als der Aufruf der CPU und des Speichers der Stream-Wrapper-Schicht ; Dies erlegt Ihnen als Erweiterungsentwickler jedoch die gesamte Arbeit zur Implementierung eines bestimmten Protokolls auf. Durch die Einbindung in die Stream-Wrapper-Ebene kann Ihr Erweiterungscode transparent die verschiedenen integrierten Stream-Wrapper wie HTTP, FTP und deren verwenden Durch die Einbindung spezifischer PEAR- oder PECL-Module kann Ihr Code auch auf andere Protokolle wie SSH2, WebDav und sogar Gopher zugreifen In diesem Kapitel wird die grundlegende API vorgestellt, die intern auf der Grundlage von Streams funktioniert. Später in Kapitel 16 „Interessante Streams“ werden wir Dinge wie das Anwenden von Filtern, die Verwendung kontextbezogener Optionen und Parameter usw. sehen. Erweiterte Konzepte.
Öffnen eines Streams
Obwohl es sich um eine einheitliche API handelt, hängt sie tatsächlich von den erforderlichen Stream-Typen ab. Es gibt vier verschiedene Pfade zum Öffnen eines Streams. Aus Sicht des Benutzerbereichs lauten diese vier verschiedenen Kategorien wie folgt (die Funktionsliste stellt nur Beispiele dar, keine vollständige Liste):
Egal welche Art von Stream Sie öffnen , sie werden alle in einer gemeinsamen Struktur php_stream gespeichert.
<?php /* fopen包装 * 操作文件/URI方式指定远程文件类资源 */ $fp = fopen($url, $mode); $data = file_get_contents($url); file_put_contents($url, $data); $lines = file($url); /* 传输 * 基于套接字的顺序I/O */ $fp = fsockopen($host, $port); $fp = stream_socket_client($uri); $fp = stream_socket_server($uri, $options); /* 目录流 */ $dir = opendir($url); $files = scandir($url); $obj = dir($url); /* "特殊"的流 */ $fp = tmpfile(); $fp = popen($cmd); proc_open($cmd, $pipes); ?>
fopen-Wrapper
Wir beginnen zunächst mit der Implementierung des fopen ()-Funktion. Jetzt sollten Sie mit dem Erstellen von Erweiterungsgerüsten vertraut sein. Wenn nicht, gehen Sie bitte zurück zu Kapitel 5 „Ihre erste Erweiterung“. „Zur Überprüfung finden Sie hier die von uns implementierte Funktion fopen():
Der Zweck von php_stream_open_wrapper() sollte darin bestehen, den Dateinamen oder die URL, die gelesen und geschrieben werden soll, vollständig zu umgehen. Das Lese- und Schreibverhalten hängt vom Wert von mode ab
PHP_FUNCTION(sample5_fopen) { php_stream *stream; char *path, *mode; int path_len, mode_len; int options = ENFORCE_SAFE_MODE | REPORT_ERRORS; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &path, &path_len, &mode, &mode_len) == FAILURE) { return; } stream = php_stream_open_wrapper(path, mode, options, NULL); if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
Optionen ist ein Satz von Tag-Werten im Bitfeld. Hier wird er auf einen Satz fester Werte festgelegt, die unten eingeführt werden:
USE_PATHWenden Sie den include_path in der php.ini-Datei auf den relativen Pfad
an. Eingebaute Funktion fopen() Diese Option wird sein Wird festgelegt, wenn der dritte Parameter als TRUE angegeben wird
.
STREAM_USE_URLNach dem Festlegen dieser Option Für
php://, file://, zlib://, bzip2 :// werden diese URL-Wrapper geöffnet
betrachten sie nicht als Remote-URLs.
ENFORCE_SAFE_MODE Obwohl diese Konstante benannt ist Auf diese Weise wird diese Option tatsächlich festgelegt
ist nur eine obligatorische Prüfung, um den abgesicherten Modus zu aktivieren (die
safe_mode-Direktive in der php.ini-Datei) . Wenn diese Option
nicht gesetzt wird, wird die Safe_Mode-Prüfung übersprungen (unabhängig von der INI-Einstellung
). So stellen Sie den sicheren_Modus in ein)
REPORT_ERRORSWenn beim Öffnen der angegebenen Ressource ein Fehler auftritt, wenn Sie
Wenn diese Option verwendet wird, wird ein Fehlerbericht generiert.
STREAM_MUST_SEEKFür einige Streams Dies ist beispielsweise bei Sockets nicht möglich (zufälliger
-Zugriff).
seek. Wenn der aufrufende Bereich diese Option angibt und der Wrapper
erkennt, dass er dies nicht garantieren kann suchen kann, wird es sich weigern, die
Streams zu öffnen.
STREAM_WILL_CASTDer Stream kann in stdio umgewandelt werden, wenn der aufrufende Bereich dies erfordert, oder in den
Posix-Dateideskriptor. Sie sollten diese Option an die open_wrapper-Funktion
, um einen Fehler zu garantieren, bevor der E/A-Vorgang auftritt STREAM_ONLY_GET_HEADERS
identifiziert nur die Metadaten, die angefordert werden müssen aus dem Stream. Tatsächlich wird dies für
HTTP-Wrapper verwendet und ruft die globale Variable http_response_headers
ohne tatsächlich den Inhalt der Remote-Datei abzurufen.STREAM_DISABLE_OPEN_BASEDIR
ähnelt der Safe_Mode-Prüfung. Wenn diese Option nicht festgelegt ist, wirdINI legt open_basedir fest. Wenn Sie diese Option angeben, können Sie diese Standardprüfung
umgehen
STREAM_OPEN_PERSISTENT Informiert die Stream-Wrapper-Schicht darüber, dass der gesamte intern zugewiesene Speicherplatz dauerhaft zugewiesen ist
und die zugehörigen Ressourcen werden in der Persistenzliste registriert.
IGNORE_PATHWenn nicht angegeben, wird der Standard-Include-Pfad durchsucht. Die meisten URLs
Wrapper Alle Browser ignorieren diese Option.
IGNORE_URL提供这个选项时, 流包装层只打开本地文件. 所
有的is_url包装器都将被忽略.
最后的NULL参数是char **类型, 它最初是用来设置匹配路径, 如果path指向普通文件URL, 则去掉file://部分, 保留直接的文件路径用于传统的文件名操作. 这个参数仅仅是以前引擎内部处理使用的.
此外, 还有php_stream_open_wrapper()的一个扩展版本:
php_stream *php_stream_open_wrapper_ex(char *path, char *mode, int options, char **opened_path, php_stream_context *context);
最后一个参数context允许附加的控制, 并可以得到包装器内的通知. 你将在第16章看到这个参数的细节.
传输层包装
尽管传输流和fopen包装流是相同的组件组成的, 但它的注册策略和其他的流不同. 从某种程度上来说, 这是因为用户空间对它们的访问方式的不同造成的, 它们需要实现基于套接字的其他因子.
从扩展开发者角度来看, 打开传输流的过程是相同的. 下面是对fsockopen()的实现:
PHP_FUNCTION(sample5_fsockopen) { php_stream *stream; char *host, *transport, *errstr = NULL; int host_len, transport_len, implicit_tcp = 1, errcode = 0; long port = 0; int options = ENFORCE_SAFE_MODE; int flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &host, &host_len, &port) == FAILURE) { return; } if (port) { int implicit_tcp = 1; if (strstr(host, "://")) { /* A protocol was specified, * no need to fall back on tcp:// */ implicit_tcp = 0; } transport_len = spprintf(&transport, 0, "%s%s:%d", implicit_tcp ? "tcp://" : "", host, port); } else { /* When port isn't specified * we can safely assume that a protocol was * (e.g. unix:// or udg://) */ transport = host; transport_len = host_len; } stream = php_stream_xport_create(transport, transport_len, options, flags, NULL, NULL, NULL, &errstr, &errcode); if (transport != host) { efree(transport); } if (errstr) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %s", errcode, errstr); efree(errstr); } if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
这个函数的基础构造和前面的fopen示例是一样的. 不同在于host和端口号使用不同的参数指定, 接着为了给出一个传输流URL就必须将它们合并到一起. 在产生了一个有意义的"路径:后, 将它传递给php_stream_xport_create()函数, 方式和fopen()使用的php_stream_open_wrapper()API一样. php_stream_xport_create()的原型如下:
php_stream *php_stream_xport_create(char *xport, int xport_len, int options, int flags, const char *persistent_id, struct timeval *timeout, php_stream_context *context, char **errstr, int *errcode);
每个参数的含义如下:
xport基于URI的传输描述符. 对于基于inet的套接字流, 它可以是
tcp://127.0.0.1:80, udp://10.0.0.1:53, ssl://169.254.13.24:445等. 此
外, UNIX域传输协议unix:///path/to/socket,
udg:///path/to/dgramsocket等都是合法的. xport_len指定了xport的长
度, 因此xport是二进制安全的.
options这个值是由前面php_stream_open_wrapper()中介绍的选项通过按位
或组成的值.
flags由STREAM_XPORT_CLIENT或STREAM_XPORT_SERVER之一
与下面另外一张表中将列出的STREAM_XPORT_*常量通过按位或
组合得到的值.
persistent_idWenn der angeforderte Transportstrom zwischen Anforderungen beibehalten werden muss, kann der aufrufende Bereich einen
Schlüssel bereitstellen Name, der die Verbindung beschreibt. Wenn Sie diesen Wert als NULL angeben, wird eine nicht persistente Verbindung erstellt. Wenn ein eindeutiger Zeichenfolgenwert angegeben wird, wird zunächst versucht, aus dem Persistenzpool nachzuschlagen. Wenn ein Transportstrom vorhanden ist oder kein
vorhanden ist, erstellen Sie einen neuen Persistenz-Stream, wenn er gefunden wird
Timeout Wie lange dauert es, bis eine Verbindung fehlschlägt? Die Übergabe dieses Werts als NULL führt dazu, dass
den in php.ini angegebenen Standardwert verwendet. Dieser Parameter hat für serverseitige Transportströme keine Bedeutung.
errstrWenn beim Erstellen, Verbinden, Binden oder Abhören des ausgewählten Sockets ein Fehler auftritt, wird der hier übergebene char *-Referenzwert
wird zunächst auf eine Zeichenfolge gesetzt, die den Grund für den Fehler beschreibt.
sollte zunächst auf NULL zeigen Es wird bei der Rückgabe auf einen Wert festgelegt. Der aufrufende Bereich ist
Verantwortlich für die Freigabe des mit dieser Zeichenfolge verbundenen Speichers.
errcodeDer numerische Fehlercode, der der von errstr zurückgegebenen Fehlermeldung entspricht.
Die STREAM_XPORT_*-Konstantenfamilie, die im Flags-Parameter von php_stream_xport_create() verwendet wird sind wie folgt definiert:
STREAM_XPORT_CLIENTDas lokale Ende stellt über die Transportschicht eine Verbindung mit der Remote-Ressource her
-Tag wird normalerweise in Verbindung mit STREAM_XPORT_CONNECT oder
STREAM_XPORT_CONNECT_ASYNC
Verwenden Sie .STREAM_XPORT_SERVER
Die lokale Seite akzeptiert die Verbindung über die Transportschicht. Dieses Tag lautet normalerweisewird mit STREAM_XPORT_BIND und
STREAM_XPORT_LISTEN.
STREAM_XPORT_CONNECT wird verwendet, um zu veranschaulichen, dass das Herstellen einer Remote-Ressourcenverbindung Teil der Erstellung des Transportstroms ist. Es ist zulässig, dieses Tag wegzulassen
. Dies erfordert jedoch den manuellen Aufruf von
php_stream_xport_connect().
STREAM_XPORT_CONNECT_ASYNC尝试连接到远程资源, 但不阻塞.
STREAM_XPORT_BIND将传输流绑定到本地资源. 用在服务端传输流时,
这将使得accept连接的传输流准备端口, 路径或
特定的端点标识符等信息.
STREAM_XPORT_LISTEN在已绑定的传输流端点上监听到来的连接. 这通
常用于基于流的传输协议, 比如: tcp://, ssl://,
unix://.
目录访问
fopen包装器支持目录访问, 比如file://和ftp://, 还有第三种流打开函数也可以用于目录访问, 下面是对opendir()的实现:
PHP_FUNCTION(sample5_opendir) { php_stream *stream; char *path; int path_len, options = ENFORCE_SAFE_MODE | REPORT_ERRORS; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &path, &path_len) == FAILURE) { return; } stream = php_stream_opendir(path, options, NULL); if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
同样的, 也可以为某个特定目录打开一个流, 比如本地文件系统的目录名或支持目录访问的URL格式资源. 这里我们又看到了options参数, 它和原来的含义一样, 第三个参数NULL原型是php_stream_context类型.
在目录流打开后, 和文件以及传输流一样, 返回给用户空间.
特殊流
还有一些特殊类型的流不能归类到fopen/transport/directory中. 它们中每一个都有自己独有的API:
php_stream *php_stream_fopen_tmpfile(void); php_stream *php_stream_fopen_temporary_file(const char *dir, const char *pfx, char **opened_path);
创建一个可seek的缓冲区流用于读写. 在关闭时, 这个流使用的所有临时资源, 包括所有的缓冲区(无论是在内存还是磁盘), 都将被释放. 使用这一组API中的后一个函数, 允许临时文件被以特定的格式命名放到指定路径. 这些内部API调用被用户空间的tmpfile()函数隐藏.
php_stream *php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id); php_stream *php_stream_fopen_from_file(FILE *file, const char *mode); php_stream *php_stream_fopen_from_pipe(FILE *file, const char *mode);
这3个API方法接受已经打开的FILE *资源或文件描述符ID, 使用流API的某种操作包装. fd格式的接口不会搜索匹配你前面看到过的fopen函数打开的资源, 但是它会注册持久化的资源, 后续的fopen可以使用到这个持久化资源.
访问流
在你打开一个流之后, 就可以在它上面执行I/O操作了. 使用那种协议包装API创建了流并不重要, 它们都使用相同的访问API.
读
流的读写可以使用下面的API函数组合完成, 它们多数都是遵循POSIX I/O中对应的API规范的:
int php_stream_getc(php_stream *stream);
从数据流中接收一个字符. 如果流上再没有数据, 则返回EOF.
size_t php_stream_read(php_stream *stream, char *buf, size_t count);
从指定流中读取指定字节的数据. buf必须预分配至少count字节的内存空间. 这个函数将返回从数据流实际读到缓冲区中的数据字节数.
php_stream_read()不同于其他的流读取函数. 如果使用的流不是普通文件流, 哪怕数据流中有超过请求字节数的数据, 并且当前也可以返回, 它也只会调用过一次底层流实现的read函数. 这是为了兼容基于包(比如UDP)的协议的这种做法.
char *php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, size_t *returned_len); char *php_stream_gets(php_stream *stream, char *buf, size_t maxlen);
这两个函数从stream中读取最多maxlen个字符, 直到碰到换行符或流结束. buf可以是一个指向预分配的至少maxlen字节的内存空间的指针, 也可以是NULL, 当它是NULL时, 则会自动的创建一个动态大小的缓冲区, 用从流中实际读出的数据填充, 成功后函数返回指向缓冲区的指针, 失败则返回NULL. 如果returned_len传递了非NULL值, 则在返回时它将被设置为实际从流中读取的字节数.
char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC);
和php_stream_get_line()类似, 这个函数将读取最多maxlen, 或到达EOF/行结束第一次出现的位置. 但是它也有和php_stream_get_line()的不同指出, 这个函数允许指定任意的停止读取标记.
读取目录项
从php流中读取目录项和上面从普通文件中读取普通数据相同. 这些数据放到了固定大小的dirents块中. 内部的php_stream_dirent结构体如下, 它与POSIX定义的dirent结构体一致:
typedef struct _php_stream_dirent { char d_name[MAXPATHLEN]; } php_stream_dirent;
实际上你可以直接使用php_stream_read()函数读取数据到这个结构体中:
{ struct dirent entry; if (php_stream_read(stream, (char*)&entry, sizeof(entry)) == sizeof(entry)) { /* 成功从目录流中读取到一项 */ php_printf("File: %s\n", entry.d_name); } }
由于从目录流中读取是很常见的操作, php流包装层暴露了一个API, 它将记录大小的检查和类型转换处理封装到了一次调用中:
php_stream_dirent *php_stream_readdir(php_stream *dirstream, php_stream_dirent *entry);
如果成功读取到目录项, 则传入的entry指针将被返回, 否则返回NULL标识错误. 使用这个为目录流特殊构建的函数而不是直接从目录流读取非常重要, 这样做未来流API改变时就不至于和你的代码冲突.
写
和读类似, 向流中写数据只需要传递一个缓冲区和缓冲区长度给流.
size_t php_stream_write(php_stream *stream, char *buf, size_t count); size_t php_stream_write_string(php_stream *stream, char *stf);
write_string的版本实际上是一个提供便利的宏, 它允许写一个NULL终止的字符串,而不用显式的提供长度. 返回的是实际写到流中的字节数. 要特别小心的是尝试写大数据的时候可能导致流阻塞, 比如套接字流, 而如果流被标记为非阻塞, 则实际写入的数据量可能会小于传递给函数的期望大小.
int php_stream_putc(php_stream *stream, int c); int php_stream_puts(php_string *stream, char *buf);
还有一种选择是, 使用php_stream_putc()和php_stream_puts()写入一个字符或一个字符串到流中. 要注意, php_stream_puts()不同于php_stream_write_string(), 虽然它们的原型看起来是一样的, 但是php_stream_puts()会在写出buf中的数据后自动的追加一个换行符.
size_t php_stream_printf(php_stream *stream TSRMLS_DC, const char *format, ...);
功能和格式上都类似于fprintf(), 这个API调用允许在写的同时构造字符串而不用去创建临时缓冲区构造数据. 这里我们能够看到的一个明显的不同是它需要TSRMLS_CC宏来保证线程安全.
随机访问, 查看文件偏移量以及缓存的flush
基于文件的流, 以及另外几种流是可以随机访问的. 也就是说, 在流的一个位置读取了一些数据之后, 文件指针可以向前或向后移动, 以非线性顺序读取其他部分.
如果你的流应用代码预测到底层的流支持随机访问, 在打开的时候就应该传递STREAM_MUST_SEEK选项. 对于那些原本就可随机访问的流来说, 这通常不会有什么影响, 因为流本身就是可随机访问的. 而对于那些原本不可随机访问的流, 比如网络I/O或线性访问文件比如FIFO管道, 这个暗示可以让调用程序有机会在流的数据被消耗掉之前, 优雅的失败.
在可随机访问的流资源上工作时, 下面的函数可用来将文件指针移动到任意位置:
int php_stream_seek(php_stream *stream, off_t offset, int whence); int php_stream_rewind(php_stream *stream);
offset是相对于whence表示的流位置的偏移字节数, whence的可选值及含义如下:
SEEK_SEToffset相对于文件开始位置. php_stream_rewind()API调用实际上是一个宏,
展开后是php_stream_seek(stream, 0, SEEK_SET), 表示移动到文件开始
位置偏移0字节处. 当使用SEEK_SET时, 如果offset传递负值被认为是错误
的, 将会导致未定义行为. 指定的位置超过流的末尾也是未定义的, 不过结果
通常是一个错误或文件被扩大以满足指定的偏移量.
SEEK_CURoffset相对于文件的当前偏移量. 调用php_stream_seek(steram, offset,
SEEK_CUR)一般来说等价于php_stream_seek(stream, php_stream_tell()
+ offset, SEEK_SET);
SEEK_ENDoffset是相对于当前的EOF位置的. 负值的offset表示在EOF之前的位置, 正
值和SEEK_SET中描述的是相同的语义, 可能在某些流实现上可以工作.
int php_stream_rewinddir(php_stream *dirstream);
在目录流上随机访问时, 只有php_stream_rewinddir()函数可用. 使用php_stream_seek()函数将导致未定义行为. 所有的随机访问一族函数返回0标识成功或者-1标识失败.
off_t php_stream_tell(php_stream *stream);
如你之前所见, php_stream_tell()将返回当前的文件偏移量.
int php_stream_flush(php_stream *stream);
调用flush()函数将强制将流过滤器此类内部缓冲区中的数据输出到最终的资源中. 在流被关闭时, flush()函数将自动调用, 并且大多数无过滤流资源虽然不进行任何内部缓冲, 但也需要flush. 显式的调用这个函数很少见, 并且通常也是不需要的.
int php_stream_stat(php_stream *stream, php_stream_statbuf *ssb);
调用php_stream_stat()可以获取到流实例的其他信息, 它的行为类似于fstat()函数. 实际上, php_stream_statbuf结构体现在仅包含一个元素: struct statbuf sb; 因此, php_stream_stat()调用可以如下面例子一样, 直接用传统的fstat()操作替代, 它只是将posix的stat操作翻译成流兼容的:
int php_sample4_fd_is_fifo(int fd) { struct statbuf sb; fstat(fd, &sb); return S_ISFIFO(sb.st_mode); } int php_sample4_stream_is_fifo(php_stream *stream) { php_stream_statbuf ssb; php_stream_stat(stream, &ssb); return S_ISFIFO(ssb.sb.st_mode); }
关闭
所有流的关闭都是通过php_stream_free()函数处理的, 它的原型如下:
int php_stream_free(php_stream *stream, int options);
这个函数中的options参数允许的值是PHP_STREAM_FREE_xxx一族常量的按位或的结果, 这一族常量定义如下(下面省略PHP_STREAM_FREE_前缀):
CALL_DTOR流实现的析构器应该被调用. 这里提供了一个时机对特定的流
进行显式释放.
RELEASE_STREAM释放为php_stream结构体分配的内存
PRESERVE_HANDLE指示流的析构器不要关闭它的底层描述符句柄
RSRC_DTOR流包装层内部管理资源列表的垃圾回收
PERSISTENT作用在持久化流上时, 它的行为将是永久的而不局限于当前请
求.
CLOSECALL_DTOR和RELEASE_STREAM的联合. 这是关闭
非持久化流的一般选项.
CLOSE_CASTEDCLOSE和PRESERVE_HANDLE的联合.
CLOSE_PERSISTENTCLOSE和PERSISTENT的联合. 这是永久关闭持久化流的一
般选项.
实际上, 你并不需要直接调用php_stream_free()函数. 而是在关闭流时使用下面两个宏的某个替代:
#define php_stream_close(stream) \ php_stream_free((stream), PHP_STREAM_FREE_CLOSE) #define php_stream_pclose(stream) \ php_stream_free((stream), PHP_STREAM_FREE_CLOSE_PERSISTENT)
通过zval交换流
因为流通常映射到zval上, 反之亦然, 因此提供了一组宏用来简化操作, 并统一编码(格式):
#define php_stream_to_zval(stream, pzval) \ ZVAL_RESOURCE((pzval), (stream)->rsrc_id);
要注意, 这里并没有调用ZEND_REGISTER_RESOURCE(). 这是因为当流打开的时候, 已经自动的注册为资源了, 这样就可以利用到引擎内建的垃圾回收和shutdown系统的优点. 使用这个宏而不是尝试手动的将流注册为新的资源ID是非常重要的; 这样做的最终结果是导致流被关闭两次以及引擎崩溃.
#define php_stream_from_zval(stream, ppzval) \ ZEND_FETCH_RESOURCE2((stream), php_stream*, (ppzval), \ -1, "stream", php_file_le_stream(), php_file_le_pstream()) #define php_stream_from_zval_no_verify(stream, ppzval) \ (stream) = (php_stream*)zend_fetch_resource((ppzval) \ TSRMLS_CC, -1, "stream", NULL, 2, \ php_file_le_stream(), php_file_le_pstream())
从传入的zval *中取回php_stream *有一个类似的宏. 可以看出, 这个宏只是对资源获取函数(第9章"资源数据类型")的一个简单封装. 请回顾ZEND_FETCH_RESOURCE2()宏, 第一个宏php_stream_from_zval()就是对它的包装, 如果资源类型不匹配, 它将抛出一个警告并尝试从函数实现中返回. 如果你只是想从传入的zval *中获取一个php_stream *, 而不希望有自动的错误处理, 就需要使用php_stream_from_zval_no_verify()并且需要手动的检查结果值.
静态资源操作
一个基于流的原子操作并不需要实际的实例. 下面这些API仅仅使用URL执行这样的操作:
int php_stream_stat_path(char *path, php_stream_statbuf *ssb);
和前面的php_stream_stat()类似, 这个函数提供了一个对POSIX的stat()函数协议依赖的包装. 要注意, 并不是所有的协议都支持URL记法, 并且即便支持也可能不能报告出statbuf结构体中的所有成员值. 一定要检查php_stream_stat_path()失败时的返回值, 0标识成功, 要知道, 不支持的元素返回时其值将是默认的0.
int php_stream_stat_path_ex(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context);
这个php_stream_url_stat()的扩展版本允许传递另外两个参数. 第一个是flags, 它的值可以是下面的PHP_STERAM_URL_STAT_*(下面省略PHP_STREAM_URL_STAT_前缀)一族常量的按位或的结果. 还有一个是context参数, 它在其他的一些流函数中也有出现, 我们将在第16章去详细学习.
LINK原始的php_stream_stat_path()对于符号链接或目录将会进行解析直到碰到
协议定义的结束资源. 传递PHP_STREAM_URL_STAT_LINK标记将导致
php_stream_stat_path()返回请求资源的信息而不会进行符号链接的解析.
(译注: 我们可以这样理解, 没有这个标记, 底层使用stat(), 如果有这个标记,
底层使用lstat(), 关于stat()和lstat()的区别, 请查看*nix手册)
QUIET默认情况下, 如果在执行URL的stat操作过程中碰到错误, 包括文件未找到错
误, 都将通过php的错误处理机制触发. 传递QUIET标记可以使得
php_stream_stat_path()返回而不报告错误.
int php_stream_mkdir(char *path, int mode, int options, php_stream_context *context); int php_stream_rmdir(char *path, int options, php_stream_context *context);
创建和删除目录也会如你期望的工作. 这里的options参数和前面的php_stream_open_wrapper()函数的同名参数含义一致. 对于php_stream_mkdir(), 还有一个参数mode用于指定一个八进制的值表明读写执行权限.
小结
本章中你接触了一些基于流的I/O的内部表象. 下一章将演示做呢样实现自己的协议包装, 甚至是定义自己的流类型.
以上就是[翻译][php扩展开发和嵌入式]第14章-php中流的访问的内容,更多相关内容请关注PHP中文网(www.php.cn)!