国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

首頁 後端開發(fā) php教程 [翻譯][php擴展開發(fā)與嵌入式]第15章-php中流的實現(xiàn)

[翻譯][php擴展開發(fā)與嵌入式]第15章-php中流的實現(xiàn)

Feb 10, 2017 am 10:21 AM
php


實現(xiàn)流

php的流最強力的特性之一是它可以存取眾多資料來源: 普通檔案, 壓縮檔案, 網路透明通道, 加密網路, 命名管道以及域套接字, 它們對於用戶空間以及內部都是統(tǒng)一的API.

php流的表象之下

對於給定的流實例, 例如文件流和網絡流, 例如的不同在於上一對於給定的流實例, 例如文件流和網絡流, 例如章你使用的流建立函數回傳的php_stream結構體中的ops成員.

typedef struct _php_stream {
    ...
    php_stream_ops *ops;
    ...
} php_stream;
php_stream_ops結構體定義的是一個函數指標集合以及一個描述標記.

存取函數例如php_stream_read()被呼叫時, 流包裝層實際上解析調用了stream->ops中對應的函數, 這樣實際調用的就是當前流類型特有的read實現(xiàn). 例如, 普通文件的流ops結構體中的read函數實作如下(實際的實作比下面的範例複雜一點):

typedef struct _php_stream_ops {
    size_t (*write)(php_stream *stream, const char *buf,
                            size_t count TSRMLS_DC);
    size_t (*read)(php_stream *stream, char *buf,
                            size_t count TSRMLS_DC);
    int    (*close)(php_stream *stream, int close_handle
                            TSRMLS_DC);
    int    (*flush)(php_stream *stream TSRMLS_DC);

    const char *label;

    int (*seek)(php_stream *stream, off_t offset, int whence,
                            off_t *newoffset TSRMLS_DC);
    int (*cast)(php_stream *stream, int castas, void **ret
                            TSRMLS_DC);
    int (*stat)(php_stream *stream, php_stream_statbuf *ssb
                            TSRMLS_DC);
    int (*set_option)(php_stream *stream, int option,int value,
                            void *ptrparam TSRMLS_DC);
} php_stream_ops;

而compress.zlib流所使用的ops結構體中則是指的是如下的函數

size_t php_stdio_read(php_stream *stream, char *buf,
                                size_t count TSRMLS_DC)
{
    php_stdio_stream_data *data =
                (php_stdio_stream_data*)stream->abstract;
    return read(data->fd, buf, count);
}

這裡第一點需要注意的是ops結構體指向的函數指針常常是對數據源真正的讀取函數的一個瘦代理. 在上面兩個例子中, 標準I /O流使用posix的read()函數, 而zlib流使用的是libz的gzread()函數.

你可能還注意到了, 這裡使用了stream->abstract元素. 這是流實現(xiàn)的一個便利指標, 它可以被用來取得各種相關的捆綁資訊. 在上面的例子中, 指向自訂結構體的指標, 用於儲存底層read函數要使用的檔案描述子.

還有一件你可能注意到的事情是php_stream_ops結構體中的每個函數都期望一個已有的流實例, 但是怎樣得到實例呢? abstract成員是怎樣設置的以及什麼時候流指示使用哪個ops結構體? 答案就在你在上一章使用過的第一個打開流的函數(php_stream_open_wrapper())中.

當這個函數被調用時, php的流包裝層嘗試基於傳遞的URL中的scheme://部分確定請求的是什麼協(xié)定. 這樣它就可以在已註冊的php包裝器中查找對應的php_stream_wrapper項. 每個php_stream_wrapper結構體都可以取到自己的ops元素, 它指向一個php_stream_wrapper_ops結構體:

size_t php_zlib_read(php_stream *stream, char *buf,
                                size_t count TSRMLS_DC)
{
    struct php_gz_stream_data_t *data =
            (struct php_gz_stream_data_t *) stream->abstract;

    return gzread(data->gz_file, buf, count);
}

這裡, 流包裝層呼叫wrapper->ops->stream_opener(), 它將執(zhí)行包裝器特有的操作創(chuàng)建流實例, 賦值恰當的php_stream_ops結構體, 綁定相關的抽象資料.

dir_opener()函數和stream_opener()提供相同的基礎服務; 不過, 它是對php_stream_opendir()這個API調用的響應, 並且通常會綁定一個不同的php_stream_ops結構體到返回的實例. stat()和close()函數在這裡一層上是重複的, 這樣做是為了給包裝器的這些操作增加協(xié)議特有的邏輯.

其他的函數則允許執(zhí)行靜態(tài)流操作而不用實際的創(chuàng)建流實例. 回顧這些流API調用,它們並不實際返回php_stream物件, 你馬上就會看到它們的細節(jié).

儘管在php 4.3中引入流包裝層時, url_stat在內部作為一個包裝器的ops函數存在, 但直到php 5.0它才開始被使用. 此外, 最後的3個函數, rename(), stream_mkdir( )以及stream_rmdir()一直到php 5.0才引入, 在這個版本之前, 它們並不在包裝器的ops結構中.

包裝器操作

除了url_stat()函數, 包裝器操作中在const char *label元素之前的每個操作都可以用於激活的流實例上. 每個函數的意義如下:

stream_opener()實例化一個流實例. 當某個用戶空間的fopen()函數被呼叫時, 這個函

數將被呼叫. 這個函數傳回的php_stream實例是fopen()函數傳回的

檔案資源句柄file(), file_get_contents(),?

file_put_contents(), readfile()等等, 在請求包裝資源時, 都使用這個包

裝器ops.

stream_closer( )當一個流實例結束其生命週期時這個函數被呼叫. stream_opener()時

分配的所有資源都應該在這個函數中被釋放.

stream於使用者空間的fstat()函數, 這個函數應該填入ssb結構體(實際上

只包含一個struct statbuf sb結構體成員),?

dir_opener()和行為一致, 不過它是調用opendir()一族的用戶空間

函數時被調用的. 目錄流使用的底層流實現(xiàn)和文件流遵循相同的規(guī)則;?

。需要返回包含在打開的目錄中找到的文件名的記錄, 它

的大小為struct dirent這個結構體的大小.

靜態(tài)包裝器函數中的其他函數是在URI路徑上執(zhí)行原子操作, 取決於包裝器協(xié)定. 在php4.3的php_stream_wrapper_ops結構體中只有url_stat()和unlink(); 其他的方式是到php 5.0後才定義的, 編碼時應該適時的使用#ifdef區(qū)塊說明.

url_stat()

stat()族函數使用, 返回檔案元資料, 傳回授權, 大小, 類型; 以及

, 例如修改,創(chuàng)建時間. 雖然這個函數是在php 4.3引入流包裝層時出現(xiàn)

在php_stream_wrapper_ops結構體中的, 但直到php 5.0才被用戶空

間的stat()函數使用.

unlink()和posix文件系統(tǒng)的同名函數語義相同, 它執(zhí)行文件刪除. 如果對于當

前的包裝器刪除沒有意義, 比如內建的http://包裝器, 這個函數應該被

定義為NULL, 以便內核去引發(fā)適當的錯誤消息.

rename()當用戶空間的rename()函數的參數$from和$to參數指向的是相同的

底層包裝器實現(xiàn), php則將這個重命名請求分發(fā)到包裝器的rename函

數.

mkdir() & rmdir()這兩個函數直接映射到對應的用戶空間函數.

實現(xiàn)一個包裝器

為了演示包裝器和流操作的內部工作原理, 我們需要重新實現(xiàn)php手冊的stream_wrapper_register()一頁示例中的var://包裝器.

此刻, 首先從下面功能完整的變量流包裝實現(xiàn)開始. 構建他, 并開始檢查每一塊的工作原理.

譯注: 為了方便大家閱讀, 對代碼的注釋進行了適量補充調整, 此外, 由于phpapi的調整, 原著中的代碼不能直接在譯者使用的php-5.4.10中運行, 進行了適當的修改. 因此下面代碼結構可能和原著略有不同, 請參考閱讀.(下面opendir的例子也進行了相應的修改)

config.m4

PHP_ARG_ENABLE(varstream,whether to enable varstream support,
[  enable-varstream      Enable varstream support])

if test "$PHP_VARSTREAM" = "yes"; then
  AC_DEFINE(HAVE_VARSTREAM,1,[Whether you want varstream])
  PHP_NEW_EXTENSION(varstream, varstream.c, $ext_shared)
fi

php_varstream.h

#ifndef PHP_VARSTREAM_H
#define PHP_VARSTREAM_H

extern zend_module_entry varstream_module_entry;
#define phpext_varstream_ptr &varstream_module_entry

#ifdef PHP_WIN32
#   define PHP_VARSTREAM_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#   define PHP_VARSTREAM_API __attribute__ ((visibility("default")))
#else
#   define PHP_VARSTREAM_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

PHP_MINIT_FUNCTION(varstream);
PHP_MSHUTDOWN_FUNCTION(varstream);

#define PHP_VARSTREAM_WRAPPER       "var"
#define PHP_VARSTREAM_STREAMTYPE    "varstream"

/* 變量流的抽象數據結構 */
typedef struct _php_varstream_data {
    off_t   position;
    char    *varname;
    int     varname_len;
} php_varstream_data;

#ifdef ZTS
#define VARSTREAM_G(v) TSRMG(varstream_globals_id, zend_varstream_globals *, v)
#else
#define VARSTREAM_G(v) (varstream_globals.v)
#endif

#endif

varstream.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "ext/standard/url.h"
#include "php_varstream.h"

static size_t php_varstream_write(php_stream *stream,
                const char *buf, size_t count TSRMLS_DC)
{
    php_varstream_data *data = stream->abstract;
    zval **var;
    size_t newlen;

    /* 查找變量 */
    if (zend_hash_find(&EG(symbol_table), data->varname,
            data->varname_len + 1,(void**)&var) == FAILURE) {
        /* 變量不存在, 直接創(chuàng)建一個字符串類型的變量, 并保存新傳遞進來的內容 */
       zval *newval;
       MAKE_STD_ZVAL(newval);
       ZVAL_STRINGL(newval, buf, count, 1); 
       /* 將新的zval *放到變量中 */
       zend_hash_add(&EG(symbol_table), data->varname,
           data->varname_len + 1, (void*)&newval,
           sizeof(zval*), NULL);
       return count;
    }   
    /* 如果需要, 讓變量可寫. 這里實際上處理的是寫時復制 */
    SEPARATE_ZVAL_IF_NOT_REF(var);
    /* 轉換為字符串類型 */
    convert_to_string_ex(var);
    /* 重置偏移量(譯注: 相比于正常的文件系統(tǒng), 這里的處理實際上不支持文件末尾的空洞創(chuàng)建, 讀者如果熟悉*nix文件系統(tǒng), 應該了解譯者所說, 否則請略過) */
    if (data->position > Z_STRLEN_PP(var)) {
        data->position = Z_STRLEN_PP(var);
    }   
    /* 計算新的字符串長度 */
    newlen = data->position + count;
    if (newlen < Z_STRLEN_PP(var)) {
        /* 總長度不變 */
        newlen = Z_STRLEN_PP(var);
    } else if (newlen > Z_STRLEN_PP(var)) {
        /* 重新調整緩沖區(qū)大小以保存新內容 */
        Z_STRVAL_PP(var) =erealloc(Z_STRVAL_PP(var),newlen+1);
        /* 更新字符串長度 */
        Z_STRLEN_PP(var) = newlen;
        /* 確保字符串NULL終止 */
        Z_STRVAL_PP(var)[newlen] = 0;
    }   
    /* 將數據寫入到變量中 */
    memcpy(Z_STRVAL_PP(var) + data->position, buf, count);
    data->position += count;

    return count;
}

static size_t php_varstream_read(php_stream *stream,
                char *buf, size_t count TSRMLS_DC)
{
    php_varstream_data *data = stream->abstract;
    zval **var, copyval;
    int got_copied = 0;
    size_t toread = count;

    if (zend_hash_find(&EG(symbol_table), data->varname,
        data->varname_len + 1, (void**)&var) == FAILURE) {
        /* 變量不存在, 讀不到數據, 返回0字節(jié)長度 */
        return 0;
    }   
    copyval = **var;
    if (Z_TYPE(copyval) != IS_STRING) {
        /* 對于非字符串類型變量, 創(chuàng)建一個副本進行讀, 這樣對于只讀的變量, 就不會改變其原始類型 */
        zval_copy_ctor(?val);
        INIT_PZVAL(?val);
        got_copied = 1;
    }   
    if (data->position > Z_STRLEN(copyval)) {
        data->position = Z_STRLEN(copyval);
    }   
    if ((Z_STRLEN(copyval) - data->position) < toread) {
        /* 防止讀取到變量可用緩沖區(qū)外的內容 */
        toread = Z_STRLEN(copyval) - data->position;
    }   
    /* 設置緩沖區(qū) */
    memcpy(buf, Z_STRVAL(copyval) + data->position, toread);
    data->position += toread;

    /* 如果創(chuàng)建了副本, 則釋放副本 */
    if (got_copied) {
        zval_dtor(?val);
    }   

    /* 返回設置到緩沖區(qū)的字節(jié)數 */
    return toread;
}

static int php_varstream_closer(php_stream *stream,
                            int close_handle TSRMLS_DC)
{
    php_varstream_data *data = stream->abstract;

    /* 釋放內部結構避免泄露 */
    efree(data->varname);
    efree(data);

    return 0;
}

static int php_varstream_flush(php_stream *stream TSRMLS_DC)
{
    php_varstream_data *data = stream->abstract;
    zval **var;

    /* 根據不同情況, 重置偏移量 */
    if (zend_hash_find(&EG(symbol_table), data->varname,
                    data->varname_len + 1, (void**)&var)
                    == SUCCESS) {
        if (Z_TYPE_PP(var) == IS_STRING) {
            data->position = Z_STRLEN_PP(var);
        } else {
            zval copyval = **var;
            zval_copy_ctor(?val);
            convert_to_string(?val);
            data->position = Z_STRLEN(copyval);
            zval_dtor(?val);
        }
    } else {
        data->position = 0;
    }

    return 0;
}

static int php_varstream_seek(php_stream *stream, off_t offset,
                    int whence, off_t *newoffset TSRMLS_DC)
{
    php_varstream_data *data = stream->abstract;

    switch (whence) {
        case SEEK_SET:
            data->position = offset;
            break;
        case SEEK_CUR:
            data->position += offset;
            break;
        case SEEK_END:
        {
            zval **var;
           size_t curlen = 0;

           if (zend_hash_find(&EG(symbol_table),
                   data->varname,    data->varname_len + 1,
                   (void**)&var) == SUCCESS) {
              if (Z_TYPE_PP(var) == IS_STRING) {
                  curlen = Z_STRLEN_PP(var);
              } else {
                  zval copyval = **var;
                  zval_copy_ctor(?val);
                  convert_to_string(?val);
                  curlen = Z_STRLEN(copyval);
                  zval_dtor(?val);
              }
           }

           data->position = curlen + offset;
           break;
       }
    }

    /* 防止隨機訪問指針移動到緩沖區(qū)開始位置之前 */
    if (data->position < 0) {
        data->position = 0;
    }

    if (newoffset) {
        *newoffset = data->position;
    }

    return 0;
}

static php_stream_ops php_varstream_ops = {
    php_varstream_write,
    php_varstream_read,
    php_varstream_closer,
    php_varstream_flush,
    PHP_VARSTREAM_STREAMTYPE,
    php_varstream_seek,
    NULL, /* cast */
    NULL, /* stat */
    NULL, /* set_option */
};

/* Define the wrapper operations */
static php_stream *php_varstream_opener(
            php_stream_wrapper *wrapper,
            char *filename, char *mode, int options,
            char **opened_path, php_stream_context *context
            STREAMS_DC TSRMLS_DC)
{
    php_varstream_data *data;
    php_url *url;

    if (options & STREAM_OPEN_PERSISTENT) {
        /* 按照變量流的定義, 是不能持久化的
         * 因為變量在請求結束后將被釋放
         */
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Unable to open %s persistently",
                                        filename);
        return NULL;
    }

    /* 標準URL解析: scheme://user:pass@host:port/path?query#fragment */
    url = php_url_parse(filename);
    if (!url) {
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Unexpected error parsing URL");
        return NULL;
    }
    /* 檢查是否有變量流URL必須的元素host, 以及scheme是否是var */
    if (!url->host || (url->host[0] == 0) ||
        strcasecmp("var", url->scheme) != 0) {
        /* Bad URL or wrong wrapper */
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Invalid URL, must be in the form: "
                     "var://variablename");
        php_url_free(url);
        return NULL;
    }

    /* 創(chuàng)建一個數據結構保存協(xié)議信息(變量流協(xié)議重要是變量名, 變量名長度, 當前偏移量) */
    data = emalloc(sizeof(php_varstream_data));
    data->position = 0;
    data->varname_len = strlen(url->host);
    data->varname = estrndup(url->host, data->varname_len + 1);
    /* 釋放前面解析出來的url占用的內存 */
    php_url_free(url);

    /* 實例化一個流, 為其賦予恰當的流ops, 綁定抽象數據 */
    return php_stream_alloc(&php_varstream_ops, data, 0, mode);
}

static php_stream_wrapper_ops php_varstream_wrapper_ops = {
    php_varstream_opener, /* 調用php_stream_open_wrapper(sprintf("%s://xxx", PHP_VARSTREAM_WRAPPER))時執(zhí)行 */
    NULL, /* stream_close */
    NULL, /* stream_stat */
    NULL, /* url_stat */
    NULL, /* dir_opener */
    PHP_VARSTREAM_WRAPPER,
    NULL, /* unlink */
#if PHP_MAJOR_VERSION >= 5
    /* PHP >= 5.0 only */
    NULL, /* rename */
    NULL, /* mkdir */
    NULL, /* rmdir */
#endif
};

static php_stream_wrapper php_varstream_wrapper = {
    &php_varstream_wrapper_ops,
    NULL, /* abstract */
    0, /* is_url */
};

PHP_MINIT_FUNCTION(varstream)
{
    /* 注冊流包裝器:
     * 1. 檢查流包裝器名字是否正確(符合這個正則: /^[a-zA-Z0-9+.-]+$/)
     * 2. 將傳入的php_varstream_wrapper增加到url_stream_wrappers_hash這個HashTable中, key為PHP_VARSTREAM_WRAPPER
     */
    if (php_register_url_stream_wrapper(PHP_VARSTREAM_WRAPPER,
            &php_varstream_wrapper TSRMLS_CC)==FAILURE) {
        return FAILURE;
    }
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(varstream)
{
    /* 卸載流包裝器: 從url_stream_wrappers_hash中刪除 */
    if (php_unregister_url_stream_wrapper(PHP_VARSTREAM_WRAPPER
                                TSRMLS_CC) == FAILURE) {
        return FAILURE;
    }
    return SUCCESS;
}

zend_module_entry varstream_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "varstream",
    NULL,
    PHP_MINIT(varstream),
    PHP_MSHUTDOWN(varstream),
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    "0.1",
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_VARSTREAM
ZEND_GET_MODULE(varstream)
#endif

在構建加載擴展后, php就可以處理以var://開始的URL的請求, 它的行為和手冊中用戶空間實現(xiàn)的行為一致.

內部實現(xiàn)

首先你注意到的可能是這個擴展完全沒有暴露用戶空間函數. 它所做的只是在MINIT函數中調用了一個核心PHPAPI的鉤子, 將var協(xié)議和我們定義的包裝器關聯(lián)起來:

static php_stream_wrapper php_varstream_wrapper = {
    &php_varstream_wrapper_ops,
    NULL, /* abstract */
    0, /* is_url */
}

很明顯, 最重要的元素就是ops, 它提供了訪問特定流包裝器的創(chuàng)建以及檢查函數. 你可以安全的忽略abstract屬性, 它僅在運行時使用, 在初始化定義時, 它只是作為一個占位符. 第三個元素is_url, 它告訴php在使用這個包裝器時是否考慮php.ini中的allow_url_fopen選項. 如果這個值非0, 并且將allow_url_fopen設置為false, 則這個包裝器不能被腳本使用.

在本章前面你已經知道, 調用用戶空間函數比如fopen將通過這個包裝器的ops元素得到php_varstream_wrapper_ops, 這樣去調用流的打開函數php_varstream_opener.

這個函數的第一塊代碼檢查是否請求持久化的流:

if (options & STREAM_OPEN_PERSISTENT) {

對于很多包裝器這樣的請求是合法的. 然而目前的情況這個行為沒有意義. 一方面用戶空間變量的定義就是臨時的, 另一方面, varstream的實例化代價很低, 這就使得持久化的優(yōu)勢很小.

像流包裝層報告錯誤很簡單, 只需要返回一個NULL值而不是流實例即可. 流包裝層透出到用戶空間的失敗消息并不會說明具體的錯誤, 只是說明不能打開URL. 要想給開發(fā)者暴露更多的錯誤信息, 可以在返回之前使用php_stream_wrapper_log_error()函數.

php_stream_wrapper_log_error(wrapper, options
    TSRMLS_CC, "Unable to open %s persistently",
                                filename);
return NULL;

URL解析

實例化varstream的下一步需要一個人類可讀的URL, 將它分塊放入到一個易管理的結構體中. 幸運的是它使用了和用戶空間url_parse()函數相同的機制. 如果URL成功解析, 將會分配一個php_url結構體并設置合適的值. 如果在URL中沒有某些值, 在返回的php_url中對應的將被設置為NULL. 這個結構體必須在離開php_varstream_opener函數之前被顯式釋放, 否則它的內存將會泄露:

typedef struct php_url {
    /* scheme://user:pass@host:port/path?query#fragment */
    char *scheme;
    char *user;
    char *pass;
    char *host;
    unsigned short port;
    char *path;
    char *query;
    char *fragment;
} php_url;

最后, varstream包裝器創(chuàng)建了一個數據結構, 保存了流指向的變量名, 讀取時的當前位置. 這個結構體將在流的讀取和寫入函數中用于獲取變量, 并且將在流結束使用時由php_varstream_close函數釋放.

opendir()

讀寫變量內容的實現(xiàn)可以再次進行擴展. 這里可以加入一個新的特性, 允許使用目錄函數讀取數組中的key. 在你的php_varstream_wrapper_ops結構體之前增加下面的代碼:

static size_t php_varstream_readdir(php_stream *stream,
                char *buf, size_t count TSRMLS_DC)
{
    php_stream_dirent *ent = (php_stream_dirent*)buf;
    php_varstream_dirdata *data = stream->abstract;
    char *key;
    int type, key_len;
    long idx;

    /* 查找數組中的key */
    type = zend_hash_get_current_key_ex(Z_ARRVAL_P(data->arr),
                    &key, &key_len, &idx, 0, &(data->pos));

    /* 字符串key */
    if (type == HASH_KEY_IS_STRING) {
        if (key_len >= sizeof(ent->d_name)) {
            /* truncate long keys to maximum length */
            key_len = sizeof(ent->d_name) - 1;
        }
        /* 設置到目錄結構上 */
        memcpy(ent->d_name, key, key_len);
        ent->d_name[key_len] = 0;
    /* 數值key */
    } else if (type == HASH_KEY_IS_LONG) {
        /* 設置到目錄結構上 */
        snprintf(ent->d_name, sizeof(ent->d_name), "%ld",idx);
    } else {
        /* 迭代結束 */
        return 0;
    }
    /* 移動數組指針(位置記錄到流的抽象結構中) */
    zend_hash_move_forward_ex(Z_ARRVAL_P(data->arr),
                                        &data->pos);
    return sizeof(php_stream_dirent);
}

static int php_varstream_closedir(php_stream *stream,
                            int close_handle TSRMLS_DC)
{
    php_varstream_dirdata *data = stream->abstract;

    zval_ptr_dtor(&(data->arr));
    efree(data);
    return 0;
}

static int php_varstream_dirseek(php_stream *stream,
                    off_t offset, int whence,
                    off_t *newoffset TSRMLS_DC)
{
    php_varstream_dirdata *data = stream->abstract;

    if (whence == SEEK_SET && offset == 0) {
        /* 重置數組指針 */
        zend_hash_internal_pointer_reset_ex(
                    Z_ARRVAL_P(data->arr), &(data->pos));
        if (newoffset) {
            *newoffset = 0;
        }
        return 0;
    }
    /* 不支持其他類型的隨機訪問 */
    return -1;
}

static php_stream_ops php_varstream_dirops = {
    NULL, /* write */
    php_varstream_readdir,
    php_varstream_closedir,
    NULL, /* flush */
    PHP_VARSTREAM_DIRSTREAMTYPE,
    php_varstream_dirseek,
    NULL, /* cast */
    NULL, /* stat */
    NULL, /* set_option */
};

static php_stream *php_varstream_opendir(
            php_stream_wrapper *wrapper,
            char *filename, char *mode, int options,
            char **opened_path, php_stream_context *context
            STREAMS_DC TSRMLS_DC)
{
    php_varstream_dirdata *data;
    php_url *url;
    zval **var;

    /* 不支持持久化流 */
    if (options & STREAM_OPEN_PERSISTENT) {
        php_stream_wrapper_log_error(wrapper, options
                TSRMLS_CC, "Unable to open %s persistently",
                filename);
        return NULL;
    }

    /* 解析URL */
    url = php_url_parse(filename);
    if (!url) {
        php_stream_wrapper_log_error(wrapper, options
                TSRMLS_CC, "Unexpected error parsing URL");
        return NULL;
    }
    /* 檢查請求URL的正確性 */
    if (!url->host || (url->host[0] == 0) ||
            strcasecmp("var", url->scheme) != 0) {
        /* Bad URL or wrong wrapper */
        php_stream_wrapper_log_error(wrapper, options
                TSRMLS_CC, "Invalid URL, must be in the form: "
                "var://variablename");
        php_url_free(url);
        return NULL;
    }

    /* 查找變量 */
    if (zend_hash_find(&EG(symbol_table), url->host,
                strlen(url->host) + 1, (void**)&var) == FAILURE) {
        php_stream_wrapper_log_error(wrapper, options
                TSRMLS_CC, "Variable $%s not found", url->host);
        php_url_free(url);
        return NULL;
    }

    /* 檢查變量類型 */
    if (Z_TYPE_PP(var) != IS_ARRAY) {
        php_stream_wrapper_log_error(wrapper, options
                TSRMLS_CC, "$%s is not an array", url->host);
        php_url_free(url);
        return NULL;
    }
    /* 釋放前面分配的URL結構 */
    php_url_free(url);

    /* 分配抽象數據結構 */
    data = emalloc(sizeof(php_varstream_dirdata));
    if ( Z_ISREF_PP(var) && Z_REFCOUNT_PP(var) > 1) {
        /* 全拷貝 */
        MAKE_STD_ZVAL(data->arr);
        *(data->arr) = **var;
        zval_copy_ctor(data->arr);
        INIT_PZVAL(data->arr);
    } else {
        /* 寫時拷貝 */
        data->arr = *var;
        Z_SET_REFCOUNT_P(data->arr, Z_REFCOUNT_P(data->arr) + 1);
    }
    /* 重置數組指針 */
    zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(data->arr),
            &data->pos);
    return php_stream_alloc(&php_varstream_dirops,data,0,mode);
}

現(xiàn)在, 將你的php_varstream_wrapper_ops結構體中的dir_opener的NULL替換成你的php_varstream_opendir函數. 最后, 將下面新定義的類型放入到你的php_varstream.h文件的php_varstream_data定義下面:

#define PHP_VARSTREAM_DIRSTREAMTYPE    "varstream directory"
typedef struct _php_varstream_dirdata {
    zval *arr;
    HashPosition pos;
} php_varstream_dirdata;

在你基于fopen()實現(xiàn)的varstream包裝器中, 你直接使用持久變量名, 每次執(zhí)行讀寫操作時從符號表中獲取變量. 而這里, opendir()的實現(xiàn)中獲取變量時處理了變量不存在或者類型錯誤的異常. 你還有一個數組變量的拷貝, 這就說明原數組的改變并不會影響后續(xù)的readdir()調用的結果. 原來存儲變量名的方式也可以正常工作, 這里只是給出另外一種選擇作為演示示例.

由于目錄訪問是基于成塊的目錄條目, 而不是字符, 因此這里需要一套獨立的流操作. 這個版本中, write沒有意義, 因此保持它為NULL. read的實現(xiàn)使用zend_hash_get_current_key_ex()函數將數組映射到目錄名. 而隨機訪問也只是對SEEK_SET有效, 用來響應rewinddir()跳轉到數組開始位置.

實際上, 目錄流并沒有使用SEEK_CUR, SEEK_END, 或者除了0之外的偏移量. 在實現(xiàn)目錄流操作時, 最好還是涉及你的函數能以某種方式處理這些情況, 以使得在流包裝層變化時能夠適應其目錄隨機訪問.

操縱

5個靜態(tài)包裝器操作中的4個用來處理不是基于I/O的流資源操作. 你已經看到過它們并了解它們的原型; 現(xiàn)在我們看看varstream包裝器框架中它們的實現(xiàn):

unlink

在你的wrapper_ops結構體中增加下面的函數, 它可以讓unlink()通過varstream包裝器, 擁有和unset()一樣的行為:

static int php_varstream_unlink(php_stream_wrapper *wrapper,
                        char *filename, int options,
                        php_stream_context *context
                        TSRMLS_DC)
{               
    php_url *url;   
                        
    url = php_url_parse(filename);
    if (!url) {         
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Unexpected error parsing URL");
        return -1;   
    }       
    if (!url->host || (url->host[0] == 0) ||
        strcasecmp("var", url->scheme) != 0) {
        /* URL不合法 */
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Invalid URL, must be in the form: "
                     "var://variablename");
        php_url_free(url);
        return -1;
    }
    
    /* 從符號表刪除變量 */
    //zend_hash_del(&EG(symbol_table), url->host, strlen(url->host) + 1);
    zend_delete_global_variable(url->host, strlen(url->host) + 1 TSRMLS_CC);
    
    php_url_free(url);                                      
    return 0;
}

這個函數的編碼量和php_varstream_opener差不多. 唯一的不同在于這里你需要傳遞變量名給zend_hash_del()去刪除變量.

譯注: 譯者的php-5.4.10環(huán)境中, 使用unlink()刪除變量后, 在用戶空間再次讀取該變量名的值會導致core dump. 因此上面代碼中譯者進行了修正, 刪除變量時使用了zend_delete_global_variable(), 請讀者參考閱讀zend_delete_global_variable()函數源代碼, 考慮為什么直接用zend_hash_del()刪除, 會導致core dump. 下面是譯者測試用的用戶空間代碼:

<?php
$fp = fopen(&#39;var://hello&#39;, &#39;r&#39;);
fwrite($fp, &#39;world&#39;);
var_dump($hello);
unlink(&#39;var://hello&#39;);
$a  = $hello;

這個函數的代碼量應該和php_varstream_opener差不多. 唯一的不同是這里是傳遞變量名給zend_hash_del()去刪除變量.

rename, mkdir, rmdir

為了一致性, 下面給出rename, mkdir, rmdir函數的實現(xiàn):

static int php_varstream_rename(php_stream_wrapper *wrapper,
        char *url_from, char *url_to, int options,
        php_stream_context *context TSRMLS_DC)
{
    php_url *from, *to;
    zval **var;

    /* 來源URL解析 */
    from = php_url_parse(url_from);
    if (!from) {
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Unexpected error parsing source");
        return -1;
    }
    /* 查找變量 */
    if (zend_hash_find(&EG(symbol_table), from->host,
                strlen(from->host) + 1,
                (void**)&var) == FAILURE) {
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "$%s does not exist", from->host);
        php_url_free(from);
        return -1;
    }
    /* 目標URL解析 */
    to = php_url_parse(url_to);
    if (!to) {
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "Unexpected error parsing dest");
        php_url_free(from);
        return -1;
    }
    /* 變量的改名 */
    Z_SET_REFCOUNT_PP(var, Z_REFCOUNT_PP(var) + 1);
    zend_hash_update(&EG(symbol_table), to->host,
                strlen(to->host) + 1, (void*)var,
                sizeof(zval*), NULL);
    zend_hash_del(&EG(symbol_table), from->host,
                strlen(from->host) + 1);
    php_url_free(from);
    php_url_free(to);
    return 0;
}

static int php_varstream_mkdir(php_stream_wrapper *wrapper,
                char *url_from, int mode, int options,
                php_stream_context *context TSRMLS_DC)
{
    php_url *url;

    /* URL解析 */
    url = php_url_parse(url_from);
    if (!url) {
       php_stream_wrapper_log_error(wrapper, options
           TSRMLS_CC, "Unexpected error parsing URL");
       return -1;
    }

    /* 檢查變量是否存在 */
    if (zend_hash_exists(&EG(symbol_table), url->host,
                    strlen(url->host) + 1)) {
        php_stream_wrapper_log_error(wrapper, options
            TSRMLS_CC, "$%s already exists", url->host);
        php_url_free(url);
        return -1;
    }
    /* EG(uninitialized_zval_ptr)通常是IS_NULL的zval *, 引用計數無限大 */
    zend_hash_add(&EG(symbol_table), url->host,
            strlen(url->host) + 1,
            (void*)&EG(uninitialized_zval_ptr),
            sizeof(zval*), NULL);
    php_url_free(url);
    return 0;
}

static int php_varstream_rmdir(php_stream_wrapper *wrapper,
                char *url, int options,
                php_stream_context *context TSRMLS_DC)
{
    /* 行為等價于unlink() */
    wrapper->wops->unlink(wrapper, url, options,
                                context TSRMLS_CC);
}

檢查

并不是所有的流操作都涉及到資源的操縱. 有時候也需要查看活動的流在某個時刻的狀態(tài), 或檢查潛在可打開的資源的狀態(tài).

這一節(jié)流和包裝器的ops函數都是在相同的數據結構php_stream_statbuf上工作的, 它只有一個元素: posix標準的struct statbuf. 當本節(jié)的某個函數被調用時, 將嘗試填充盡可能多的statbuf元素的成員.

stat

如果設置, 當請求激活流實例的信息時, 將會調用wrapper->ops->stream_stat(). 如果沒有設置, 則對應的stream->ops->stat()將會被調用. 無論哪個函數被調用, 都應該盡可能多的向返回的statbuf結構體ssb->sb中填充盡可能多流實例的有用信息. 在普通文件I/O的用法中, 它對應fstat()的標準I/O調用.

url_stat

在流實例外部調用wrapper->ops->url_stat()取到流資源的元數據. 通常來說, 符號鏈接和重定向都應該被解析, 直到找到一個真正的資源, 對其通過stat()系統(tǒng)調用這樣的機制讀取統(tǒng)計信息. url_stat的flags參數允許是下面PHP_STREAM_URL_STAT_*系列的常量值(省略PHP_STREAM_URL_STAT_前綴):

LINK不解析符號鏈接和重定向. 而是報告它碰到的第一個節(jié)點的信息, 無論是連

接還是真正的資源.

QUIET不報告錯誤. 注意, 這和許多其他流函數中的REPORT_ERRORS邏輯恰恰

相反本地資料來源的串流資源, 都允許你的擴展在核心資料上掛在操縱函數的鉤子, 避免重新實現(xiàn)單調的描述符管理和I/O緩衝區(qū)工作. 這使得它在用戶空間環(huán)境中更加有用,更強大.

下一章將透過對過濾器和上下文的學習結束流包裝層的學習, 過濾器和上下文可以用於選擇默認的流行為, 甚至過程中修改數據.

以上就是[翻譯][php擴充開發(fā)與嵌入式]第15章-php中流的實作的內容,更多相關內容請關注PHP中文網(miracleart.cn)!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發(fā)現(xiàn)涉嫌抄襲或侵權的內容,請聯(lián)絡admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

我如何了解最新的PHP開發(fā)和最佳實踐? 我如何了解最新的PHP開發(fā)和最佳實踐? Jun 23, 2025 am 12:56 AM

TostaycurrentwithPHPdevelopmentsandbestpractices,followkeynewssourceslikePHP.netandPHPWeekly,engagewithcommunitiesonforumsandconferences,keeptoolingupdatedandgraduallyadoptnewfeatures,andreadorcontributetoopensourceprojects.First,followreliablesource

什麼是PHP,為什麼它用於Web開發(fā)? 什麼是PHP,為什麼它用於Web開發(fā)? Jun 23, 2025 am 12:55 AM

PHPbecamepopularforwebdevelopmentduetoitseaseoflearning,seamlessintegrationwithHTML,widespreadhostingsupport,andalargeecosystemincludingframeworkslikeLaravelandCMSplatformslikeWordPress.Itexcelsinhandlingformsubmissions,managingusersessions,interacti

如何設置PHP時區(qū)? 如何設置PHP時區(qū)? Jun 25, 2025 am 01:00 AM

tosetTherightTimeZoneInphp,restate_default_timezone_set()functionAtthestArtofyourscriptWithavalIdidentIdentifiersuchas'america/new_york'.1.usedate_default_default_timezone_set_set()

我如何驗證PHP中的用戶輸入以確保其符合某些標準? 我如何驗證PHP中的用戶輸入以確保其符合某些標準? Jun 22, 2025 am 01:00 AM

TovalidateuserinputinPHP,usebuilt-invalidationfunctionslikefilter_var()andfilter_input(),applyregularexpressionsforcustomformatssuchasusernamesorphonenumbers,checkdatatypesfornumericvalueslikeageorprice,setlengthlimitsandtrimwhitespacetopreventlayout

什麼是php(serialize(),Unserialize())中的數據序列化? 什麼是php(serialize(),Unserialize())中的數據序列化? Jun 22, 2025 am 01:03 AM

thephpfunctionserize()andunSerialize()redustoconvertComplexdatStructDestoresToroStoroStoroSandaBackagagain.1.Serialize()

如何將PHP代碼嵌入HTML文件中? 如何將PHP代碼嵌入HTML文件中? Jun 22, 2025 am 01:00 AM

可以將PHP代碼嵌入HTML文件中,但需確保文件以.php為擴展名,以便服務器能正確解析。使用標準的標籤包裹PHP代碼,可在HTML中任意位置插入動態(tài)內容。此外,可在同一文件中多次切換PHP與HTML,實現(xiàn)條件渲染等動態(tài)功能。務必注意服務器配置及語法正確性,避免因短標籤、引號錯誤或遺漏結束標籤導致問題。

編寫清潔和可維護的PHP代碼的最佳實踐是什麼? 編寫清潔和可維護的PHP代碼的最佳實踐是什麼? Jun 24, 2025 am 12:53 AM

寫乾淨、易維護的PHP代碼關鍵在於清晰命名、遵循標準、合理結構、善用註釋和可測試性。 1.使用明確的變量、函數和類名,如$userData和calculateTotalPrice();2.遵循PSR-12標準統(tǒng)一代碼風格;3.按職責拆分代碼結構,使用MVC或Laravel式目錄組織;4.避免麵條式代碼,將邏輯拆分為單一職責的小函數;5.在關鍵處添加註釋並撰寫接口文檔,明確參數、返回值和異常;6.提高可測試性,採用依賴注入、減少全局狀態(tài)和靜態(tài)方法。這些做法提升代碼質量、協(xié)作效率和後期維護便利性。

如何使用PHP執(zhí)行SQL查詢? 如何使用PHP執(zhí)行SQL查詢? Jun 24, 2025 am 12:54 AM

Yes,youcanrunSQLqueriesusingPHP,andtheprocessinvolveschoosingadatabaseextension,connectingtothedatabase,executingqueriessafely,andclosingconnectionswhendone.Todothis,firstchoosebetweenMySQLiorPDO,withPDObeingmoreflexibleduetosupportingmultipledatabas

See all articles