流Streams這個概念是在php4.3引進的,是對流式資料的抽象,用於統一資料操作,比如檔案資料、網路資料、壓縮資料等,以使可以共用同一套函數, php的檔案系統函數就是這樣的共用,比如file_get_contents()函數即可開啟本地檔案也可以訪問url就是這一體現。簡單點講,流就是表現出流式資料行為的資來源物件。
以線性方式進行讀寫,並可以在流裡面任意位置進行搜尋。
流有點類似資料庫抽象層,在資料庫抽象層方面,不管使用何種資料庫,在抽象層之上都使用相同的方式操作資料,
而流是對資料的抽象,它不管是本地檔案還是遠程檔案還是壓縮檔等等,只要來的是流式資料,那麼操作方式就是一樣的
有了流這個概念就引申出了封裝器wrapper這個概念,每個流都對應一種封裝器,
流是從統一操作這個角度產生的一個概念,而封裝器呢是從理解流資料內容出發產生的一個概念,也就是這個統一的操作方式怎麼操作或配置不同的內容;
這些內容都是以流的方式呈現,但內容規則是不一樣的,比如http協議傳來的資料是流的方式,但只有http封裝器才理解http協議傳來的資料的意思,
可以這麼理解,流就是一根流水的管子,只不過它流出的是資料,封裝器就是套在流這根管子外層的一個解釋者,它理解流出的資料的意思,並能操作它
官方手冊說:“一個封裝器是告訴流怎麼處理特殊協議或編碼的附加代碼”明白這句話的意思了嗎。
封裝器可以嵌套,一個流外麵包裹了一個封裝器後,還可以在外層繼續包裹封裝器,這個時候裡層的封裝器相對於外層的封裝器充當流的角色
在php自身底層實現的c語言開發文檔有這樣的解釋:
流API操作一對不同層級:在基本層級,api定義了php_stream對象表示流式資料來源,在稍微高一點的層級,api定義了php_stream_wrapper對象
它包裹低一層級的php_stream對象,以提供取回URL的內容和中繼資料、添加上下文參數的能力,調整封裝器行為;
每一種流開啟後都可以應用任意數量的過濾器在上面,流資料會經過過濾器的處理,筆者認為過濾器這個詞用得有點不準確,有些誤導人
從字面意思看好像是去掉一些資料的感覺,應該稱為資料調整器,因為它既可去掉一些資料,也可以添加,還可以修改,但曆史原因約定俗成,
也就稱為過濾器了,大家心裡明白就好。
我們經常看到下面的詞,來解釋下他們的區別:
資源和資料:資源是比較宏觀的說法,通常包含資料,而資料是比較具象的說法,在開發程式的時候經常說是資料,而在軟體規劃時說是資源,他們是近義詞,就像軟體設計和程式開發的區別一樣。
上下文和參數:上下文是比較宏觀的說法,經常用在溝通上面,具體點講就是一次溝通本身的參數,而參數這個說法往往用在比較具體的事情上面,比如說函數
上面解釋了概念性的東西,下面來看看具體內容:
php支援的協議和封裝器請看這裡:http://php.net/manual/zh/wrappers.php: (筆者註:原標題是:支援的協議和封裝協議,中文翻譯有點誤導,準確的講就是支援的協議和封裝器,從英文版面就很清楚)
預設的支援了一些協議和封裝器,請用stream_get_wrappers()函數查看.也可以自訂一個封裝器,用stream_wrapper_register()註冊
儘管RFC 3986裡面可以使用:做分割符,但php只允許://,所以url請使用"scheme://target"這樣的格式
file:// — 訪問本地檔案系統,在用檔案系統函數時預設就使用該封裝器
http:// — 訪問 HTTP(s) 網址
ftp:// — 訪問 FTP(s) URLs
php:// — 訪問各個輸入/輸出流(I/O streams)
zlib:// — 壓縮流
data:// — 資料(RFC 2397)
glob:// — 尋找匹配的檔案路徑模式
phar:// — PHP 歸檔
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音頻流
expect:// — 處理互動流
如何?一個自訂的封裝器: 在用fopen、fwrite、fread、fgets、feof、rewind、file_put_contents、file_get_contents等等檔案系統函數操作流時,資料是先傳給定義的封裝器類對象,封裝器再去操作流。
如何?一個自訂的流封裝器呢。php提供了一個類原型,只是原型而已,不是介面也不是類,不能用於繼承:
streamWrapper {/* 屬性 */public resource $context ;/* 方法 */__construct ( void )__destruct ( void )public bool dir_closedir ( void )public bool dir_opendir ( string $path , int $options )public string dir_readdir ( void )public bool dir_rewinddir ( void )public bool mkdir ( string $path , int $mode , int $options )public bool rename ( string $path_from , string $path_to )public bool rmdir ( string $path , int $options )public resource stream_cast ( int $cast_as )public void stream_close ( void )public bool stream_eof ( void )public bool stream_flush ( void )public bool stream_lock ( int $operation )public bool stream_metadata ( string $path , int $option , mixed $value )public bool stream_open ( string $path , string $mode , int $options , string &$opened_path )public string stream_read ( int $count )public bool stream_seek ( int $offset , int $whence = SEEK_SET )public bool stream_set_option ( int $option , int $arg1 , int $arg2 )public array stream_stat ( void )public int stream_tell ( void )public bool stream_truncate ( int $new_size )public int stream_write ( string $data )public bool unlink ( string $path )public array url_stat ( string $path , int $flags )}
在這個原型裡面定義的方法,根據自己需要去定義,並不要求全部實現,這就是為什麼不定義成介面的原因,因為有些實現根本用不著某些方法,
這帶來很多靈活性,比如封裝器是不支援刪除目錄rmdir功能的,那麼就不需要實現streamWrapper::rmdir
由於未實現它,如果使用者在封裝器上調用rmdir將有錯誤拋出,要自訂這個錯誤那麼也可以實現它並在其內部拋出錯誤
streamWrapper也不是一個預定義類,測試class_exists("streamWrapper")就知道,它只是一個指導開發人員的原型
官方手冊提供了一個例子:http://php.net/manual/zh/stream.streamwrapper.example-1.php 本部落格提供一個從drupal8系統中抽取修改過的封裝器例子,請看drupal8源碼分析關於流那一部分
流系列函數,官方手冊: http://php.net/manual/zh/ref.stream.php
常用的函數如下:
stream_bucket_append函數:為隊列添加資料
stream_bucket_make_writeable函數:從操作的隊列中返回一個資料對象
stream_bucket_new函數:為當前隊列建立一個新的資料
stream_bucket_prepend函數:預備資料到隊列
stream_context_create函數:建立資料流上下文
stream_context_get_default函數:擷取預設的資料流上下文
stream_context_get_options函數:擷取資料流的設定
stream_context_set_option函數:對資料流、資料包或者上下文進行設定
stream_context_set_params函數:為資料流、資料包或者上下文設定參數
stream_copy_to_stream函數:在資料流之間進行複製操作
stream_filter_append函數:為資料流添加過濾器
stream_filter_prepend函數:為資料流預備添加過濾器
stream_filter_register函數:註冊一個資料流的過濾器並作為PHP類執行
stream_filter_remove函數:從一個資料流中移除過濾器
stream_get_contents函數:讀取資料流中的剩餘資料到字串
stream_get_filters函數:返回已經註冊的資料流過濾器列表
stream_get_line函數:按照給定的定界符從資料流資源中擷取行
stream_get_meta_data函數:從封裝協議檔案指標中擷取前序/中繼資料
stream_get_transports函數:返回註冊的Socket傳輸列表
stream_get_wrappers函數:返回註冊的資料流列表
stream_register_wrapper函數:註冊一個用PHP類實現的URL封裝協議
stream_select函數:接收資料流數組並等待它們狀態的改變
stream_set_blocking函數:將一個資料流設定為堵塞或者非堵塞狀態
stream_set_timeout函數:對資料流進行逾時設定
stream_set_write_buffer函數:為資料流設定緩衝區
stream_socket_accept函數:接受由函數stream_ socket_server()建立的Socket串連
stream_socket_client函數:開啟網路或者UNIX主機的Socket串連
stream_socket_enable_crypto函數:為一個已經串連的Socket開啟或者關閉資料加密
stream_socket_get_name函數:擷取本地或者網路Socket的名稱
stream_socket_pair函數:建立兩個無區別的Socket資料流串連
stream_socket_recvfrom函數:從Socket擷取資料,不管其串連與否
stream_socket_sendto函數:向Socket發送資料,不管其串連與否
stream_socket_server函數:建立一個網路或者UNIX Socket服務端
stream_wrapper_restore函數:恢複一個事先登出的資料包
stream_wrapper_unregister函數:登出一個URL地址包
一個過濾器的列子及解釋:
相關連結:
使用者過濾器基類:http://php.net/manual/zh/class.php-user-filter.php
過濾器註冊:http://php.net/manual/zh/function.stream-filter-register.php
<?php/* 定義一個過濾器 */class strtoupper_filter extends php_user_filter { function filter($in, $out, &$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { //從流裡面取出一段資料 $bucket->data = strtoupper($bucket->data); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); //將修改後的資料送到輸出的地方 } return PSFS_PASS_ON; }}/* 註冊過濾器到php */stream_filter_register("strtoupper", "strtoupper_filter") or die("Failed to register filter");$fp = fopen("foo-bar.txt", "w");/* 應用過濾器到一個流 */stream_filter_append($fp, "strtoupper");fwrite($fp, "Line1\n");fwrite($fp, "Word - 2\n");fwrite($fp, "Easy As 123\n");fclose($fp);//讀取並顯示內容 將全部變為大寫readfile("foo-bar.txt");?>